commit 96130738b8260e21c8a7d0c427082526233ae57a Author: Furry12 Date: Fri Oct 23 00:07:07 2026 +0800 Initial code commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..089db36 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +> "A wise man never leaves debugging symbols in production firmware" +> +> Sun Tzu, Art of War \ No newline at end of file diff --git a/src/sega/a7/A7Firm.java b/src/sega/a7/A7Firm.java new file mode 100644 index 0000000..db7faae --- /dev/null +++ b/src/sega/a7/A7Firm.java @@ -0,0 +1,231 @@ +package sega.a7; + +import javacard.framework.APDU; +import javacard.framework.Applet; +import javacard.framework.Util; +import javacard.security.CryptoException; +import javacard.security.MessageDigest; +import javacard.security.RandomData; +import javacardx.apdu.ExtendedLength; + +/** + * SEGA A7 keychip applet. + * + *

The ISO 7816 command header is ignored. Every APDU carries a chunk of an + * internal A7 frame; the applet accumulates chunks until the total size in the + * frame header is available, then dispatches on the 16-bit ordinal at offset 4. + * Responses are placed at APDU buffer offset 0.

+ */ +public class A7Firm extends Applet implements ExtendedLength { + + private final CommandServer m_ee_server; + private final AppData m_ee_data; + private final MessageDigest m_ee_sha1; + private final HmacSha1 m_ee_hmacSha1; + private final RandomData m_ee_random; + private final MakePacket m_ee_makePacket; + private final Crypt m_ee_crypt; + private final AppBuffer m_ee_buffer; + + public A7Firm() { + m_ee_random = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM); + m_ee_data = new AppData(m_ee_random); + m_ee_server = new CommandServer(); + m_ee_sha1 = MessageDigest.getInstance(MessageDigest.ALG_SHA, false); + m_ee_hmacSha1 = new HmacSha1(); + m_ee_makePacket = new MakePacket(); + m_ee_crypt = new Crypt(); + m_ee_buffer = new AppBuffer(); + + byte[] modulus = m_ee_data.getRsaDefaultPriModulus_ee(); + byte[] exponent = m_ee_data.getRsaDefaultPriExponent_ee(); + + byte[] tmp = m_ee_data.getCryptBuffer_ramD(); + Util.arrayFillNonAtomic(tmp, (short) 0, (short) tmp.length, (byte) 0xFF); + check(m_ee_crypt.init(tmp, (short) 0, (short) tmp.length, + tmp, (short) 0, (short) tmp.length, AppConfig.CRYPT_CONN_DECRYPT)); + check(m_ee_crypt.init(tmp, (short) 0, (short) tmp.length, + tmp, (short) 0, (short) tmp.length, AppConfig.CRYPT_CONN_ENCRYPT)); + check(m_ee_crypt.init(tmp, (short) 0, (short) tmp.length, + tmp, (short) 0, (short) tmp.length, AppConfig.CRYPT_GKEY_DECRYPT)); + check(m_ee_crypt.init(tmp, (short) 0, (short) tmp.length, + tmp, (short) 0, (short) tmp.length, AppConfig.CRYPT_GKEY_ENCRYPT)); + check(m_ee_crypt.init(modulus, (short) 0, (short) modulus.length, + exponent, (short) 0, (short) exponent.length, + AppConfig.CRYPT_RSA_DECRYPT)); + + m_ee_server.setNoCommand(new NoCommand((short) -1, m_ee_data, + m_ee_makePacket, m_ee_sha1)); + + m_ee_server.attachCommand(new SessionOpen(AppConfig.ORD_SESSION_OPEN, + m_ee_data, m_ee_makePacket, m_ee_sha1)); + m_ee_server.attachCommand(new SessionClose(AppConfig.ORD_SESSION_CLOSE, + m_ee_data, m_ee_makePacket, m_ee_sha1)); + m_ee_server.attachCommand(new AuthStart(AppConfig.ORD_AUTH_START, + m_ee_data, m_ee_makePacket, m_ee_sha1, m_ee_hmacSha1, m_ee_crypt)); + m_ee_server.attachCommand(new HmacChange(AppConfig.ORD_HMAC_CHANGE, + m_ee_data, m_ee_makePacket, m_ee_sha1, m_ee_hmacSha1, m_ee_crypt)); + m_ee_server.attachCommand(new KeyChange(AppConfig.ORD_KEY_CHANGE, + m_ee_data, m_ee_makePacket, m_ee_sha1, m_ee_hmacSha1, m_ee_crypt)); + m_ee_server.attachCommand(new LvGet(AppConfig.ORD_LV_GET, + m_ee_data, m_ee_makePacket, m_ee_sha1)); + m_ee_server.attachCommand(new ErrorGet(AppConfig.ORD_ERROR_GET, + m_ee_data, m_ee_makePacket, m_ee_sha1)); + m_ee_server.attachCommand(new VerGet(AppConfig.ORD_VER_GET, + m_ee_data, m_ee_makePacket, m_ee_sha1)); + m_ee_server.attachCommand(new KeychipInfoWrite(AppConfig.ORD_KEYCHIP_INFO_WRITE, + m_ee_data, m_ee_makePacket, m_ee_sha1, m_ee_hmacSha1, m_ee_crypt)); + m_ee_server.attachCommand(new KeychipInfoRead(AppConfig.ORD_KEYCHIP_INFO_READ, + m_ee_data, m_ee_makePacket, m_ee_sha1, m_ee_hmacSha1, m_ee_crypt)); + m_ee_server.attachCommand(new GkeyWrite(AppConfig.ORD_GKEY_WRITE, + m_ee_data, m_ee_makePacket, m_ee_sha1, m_ee_hmacSha1, m_ee_crypt)); + m_ee_server.attachCommand(new GkeyEnc(AppConfig.ORD_GKEY_ENC, + m_ee_data, m_ee_makePacket, m_ee_sha1, m_ee_hmacSha1, m_ee_crypt)); + m_ee_server.attachCommand(new GkeyDec(AppConfig.ORD_GKEY_DEC, + m_ee_data, m_ee_makePacket, m_ee_sha1, m_ee_hmacSha1, m_ee_crypt)); + m_ee_server.attachCommand(new SflagWrite(AppConfig.ORD_SFLAG_WRITE, + m_ee_data, m_ee_makePacket, m_ee_sha1, m_ee_hmacSha1, m_ee_crypt)); + m_ee_server.attachCommand(new EepInit(AppConfig.ORD_EEP_INIT, + m_ee_data, m_ee_makePacket, m_ee_sha1, m_ee_hmacSha1, m_ee_crypt)); + m_ee_server.attachCommand(new LvEnable(AppConfig.ORD_LV_ENABLE, + m_ee_data, m_ee_makePacket, m_ee_sha1)); + m_ee_server.attachCommand(new PrikeyModulusWrite(AppConfig.ORD_PRIKEY_MODULUS_WRITE, + m_ee_data, m_ee_makePacket, m_ee_sha1, m_ee_hmacSha1, m_ee_crypt)); + m_ee_server.attachCommand(new PrikeyExponentWrite(AppConfig.ORD_PRIKEY_EXPONENT_WRITE, + m_ee_data, m_ee_makePacket, m_ee_sha1, m_ee_hmacSha1, m_ee_crypt)); + m_ee_server.attachCommand(new UdataWrite(AppConfig.ORD_UDATA_WRITE, + m_ee_data, m_ee_makePacket, m_ee_sha1, m_ee_hmacSha1, m_ee_crypt)); + m_ee_server.attachCommand(new UdataRead(AppConfig.ORD_UDATA_READ, + m_ee_data, m_ee_makePacket, m_ee_sha1, m_ee_hmacSha1, m_ee_crypt)); + m_ee_server.attachCommand(new StorageWrite(AppConfig.ORD_STORAGE_WRITE, + m_ee_data, m_ee_makePacket, m_ee_sha1, m_ee_hmacSha1, m_ee_crypt)); + m_ee_server.attachCommand(new StorageRead(AppConfig.ORD_STORAGE_READ, + m_ee_data, m_ee_makePacket, m_ee_sha1, m_ee_hmacSha1, m_ee_crypt)); + m_ee_server.attachCommand(new RanGet(AppConfig.ORD_RAN_GET, + m_ee_data, m_ee_makePacket, m_ee_sha1, m_ee_hmacSha1, m_ee_crypt, + m_ee_random)); + m_ee_server.attachCommand(new PlayCountIncrement(AppConfig.ORD_PLAY_COUNT_INCREMENT, + m_ee_data, m_ee_makePacket, m_ee_sha1, m_ee_hmacSha1, m_ee_crypt)); + m_ee_server.attachCommand(new PlayCountRead(AppConfig.ORD_PLAY_COUNT_READ, + m_ee_data, m_ee_makePacket, m_ee_sha1, m_ee_hmacSha1, m_ee_crypt, + m_ee_random)); + m_ee_server.attachCommand(new TraceDataInfoWrite(AppConfig.ORD_TRACE_DATA_INFO_WRITE, + m_ee_data, m_ee_makePacket, m_ee_sha1, m_ee_hmacSha1, m_ee_crypt)); + m_ee_server.attachCommand(new TraceDataInfoRead(AppConfig.ORD_TRACE_DATA_INFO_READ, + m_ee_data, m_ee_makePacket, m_ee_sha1, m_ee_hmacSha1, m_ee_crypt)); + m_ee_server.attachCommand(new StorageSizeGet(AppConfig.ORD_STORAGE_SIZE_GET, + m_ee_data, m_ee_makePacket, m_ee_sha1, m_ee_hmacSha1, m_ee_crypt, + m_ee_random)); + } + + private static void check(boolean ok) { + if (!ok) { + CryptoException.throwIt(CryptoException.ILLEGAL_USE); + } + } + + public boolean select() { + boolean ret = super.select(); + if (ret) { + m_ee_buffer.clear_ramD(); + m_ee_data.init(); + m_ee_server.select(); + } + return ret; + } + + public void deselect() { + super.deselect(); + } + + public static void install(byte[] bArray, short bOffset, byte bLength) { + A7Firm applet = new A7Firm(); + applet.register(bArray, (short) (bOffset + 1), bArray[bOffset]); + } + + public void process(APDU apdu) { + if (selectingApplet()) { + return; + } + + if (m_ee_data.getSflag_ee() + && m_ee_data.getLevel_ramD() == AppConfig.LEVEL_BLOCKED) { + return; + } + if (m_ee_data.getLevel_ramD() == AppConfig.LEVEL_INITIAL) { + m_ee_data.setLevel_ramD(AppConfig.LEVEL_SESSION); + } + + byte[] apduBuffer = apdu.getBuffer(); + short incomingLength = apdu.getIncomingLength(); + short dataOffset = apdu.getOffsetCdata(); + short received = apdu.setIncomingAndReceive(); + + while (received > 0) { + if (!m_ee_buffer.setData_ramD(apduBuffer, dataOffset, received)) { + m_ee_buffer.clear_ramD(); + short sendSize = makeErrorPacket(AppConfig.FAIL, (short) -1, + apduBuffer, (short) 0, (short) apduBuffer.length); + if (sendSize > 0) { + apdu.setOutgoingAndSend((short) 0, sendSize); + } + return; + } + short accumulated = m_ee_buffer.getBufferSize_ramD(); + if (accumulated >= incomingLength) { + break; + } + received = apdu.receiveBytes(dataOffset); + } + + short frameLength = m_ee_buffer.getBufferSize_ramD(); + if (frameLength == 0) { + return; + } + + byte[] frame = m_ee_buffer.getBuffer_ramD(); + short cmd = -1; + short sendSize; + + try { + if (frameLength >= AppConfig.HEADER_SIZE) { + cmd = Util.getShort(frame, AppConfig.CMD_OFFSET); + } + if (frameLength < 4) { + return; + } + + short declaredSize = Util.getShort(frame, AppConfig.SIZE_OFFSET); + if (declaredSize < AppConfig.MIN_FRAME_SIZE + || declaredSize > AppConfig.MAX_FRAME_SIZE) { + sendSize = makeErrorPacket(AppConfig.BAD_DATASIZE, cmd, + apduBuffer, (short) 0, (short) apduBuffer.length); + } else if (frameLength < declaredSize) { + return; + } else { + sendSize = m_ee_server.execute(frame, (short) 0, declaredSize, + apduBuffer, (short) 0, (short) apduBuffer.length); + } + } catch (Exception e) { + sendSize = makeErrorPacket(AppConfig.FAIL, cmd, + apduBuffer, (short) 0, (short) apduBuffer.length); + } + + m_ee_buffer.clear_ramD(); + if (sendSize > 0) { + apdu.setOutgoingAndSend((short) 0, sendSize); + } + } + + private short makeErrorPacket(short error, short cmd, byte[] sendPacket, + short packetOffset, short packetLength) { + m_ee_data.setLastError_ramD(error); + boolean ok = m_ee_makePacket.setData(null, (short) 0, (short) 0, + AppConfig.TAG_RSP_COMMAND, AppConfig.ERROR_PACKET_SIZE, error, cmd, + null, (short) 0, (short) 0, + null, (short) 0, (short) 0, + null, (short) 0, (short) 0, + sendPacket, packetOffset, packetLength, m_ee_sha1, null); + return ok ? AppConfig.ERROR_PACKET_SIZE : (short) 0; + } +} diff --git a/src/sega/a7/AppBuffer.java b/src/sega/a7/AppBuffer.java new file mode 100644 index 0000000..87d7981 --- /dev/null +++ b/src/sega/a7/AppBuffer.java @@ -0,0 +1,52 @@ +package sega.a7; + +import javacard.framework.JCSystem; +import javacard.framework.Util; + +/** + * Accumulates the bytes of one internal A7 frame across APDUs. + */ +public class AppBuffer { + + private final short[] m_ramD_size; + private final byte[] m_ramD_buffer; + + public AppBuffer() { + m_ramD_buffer = JCSystem.makeTransientByteArray(AppConfig.BUFFER_SIZE, JCSystem.CLEAR_ON_RESET); + m_ramD_size = JCSystem.makeTransientShortArray((short) 1, JCSystem.CLEAR_ON_RESET); + m_ramD_size[0] = 0; + } + + public byte[] getBuffer_ramD() { + return m_ramD_buffer; + } + + public short getBufferSize_ramD() { + return m_ramD_size[0]; + } + + public boolean setData_ramD(byte[] data, short offset, short length) { + if (data == null || offset < 0 || length < 0) { + return false; + } + if (offset > data.length || length > (short) (data.length - offset)) { + return false; + } + short used = m_ramD_size[0]; + if (used < 0 || used > AppConfig.BUFFER_SIZE) { + return false; + } + if (length > (short) (AppConfig.BUFFER_SIZE - used)) { + return false; + } + if (length != 0) { + Util.arrayCopyNonAtomic(data, offset, m_ramD_buffer, used, length); + } + m_ramD_size[0] = (short) (used + length); + return true; + } + + public void clear_ramD() { + m_ramD_size[0] = 0; + } +} diff --git a/src/sega/a7/AppConfig.java b/src/sega/a7/AppConfig.java new file mode 100644 index 0000000..65ef881 --- /dev/null +++ b/src/sega/a7/AppConfig.java @@ -0,0 +1,111 @@ +package sega.a7; + +/** + * Wire-format and state constants for the A7 keychip applet. + * + *

All multi-byte values on the wire are big-endian. A frame is: + * {@code tag(2) | totalSize(2) | command-or-result(2) | payload | footer}. + * "totalSize" covers the whole frame including the footer.

+ */ +public interface AppConfig { + + short VERSION = 0x0205; + + short HEADER_SIZE = 6; + short TAG_OFFSET = 0; + short SIZE_OFFSET = 2; + short CMD_OFFSET = 4; + + short SHA1_SIZE = 20; + short NONCE_SIZE = 20; + short HMAC_SIZE = 20; + short AUTH_FOOTER_SIZE = 40; + + short AES_KEY_SIZE = 16; + short AES_IV_SIZE = 16; + short AES_BLOCK_SIZE = 16; + + short RSA_KEY_SIZE = 256; + short RSA_UPDATE_EXPONENT = 8; + short RSA_UPDATE_MODULUS = 64; + + short UDATA_SIZE = 16; + short PLAY_COUNT_SIZE = 4; + short STORAGE_SIZE = 10240; + short STORAGE_ARRAY_SIZE = 10256; + short STORAGE_MAX_READ = 192; + short STORAGE_MAX_WRITE = 256; + short KEYCHIP_INFO_SIZE = 96; + short TRACE_DATA_INFO_SIZE = 16; + + short BUFFER_SIZE = 416; + short MIN_FRAME_SIZE = 26; + short MAX_FRAME_SIZE = 416; + short ERROR_PACKET_SIZE = 26; + + short TAG_RQU_COMMAND = 0x00C1; + short TAG_RQU_AUTH_COMMAND = 0x00C2; + short TAG_RSP_COMMAND = 0x00C3; + short TAG_RSP_AUTH_COMMAND = 0x00C4; + short TAG_RQU_RSA_COMMAND = 0x00C5; + short TAG_RSP_RSA_COMMAND = 0x00C6; + + short ORD_SESSION_OPEN = 1; + short ORD_SESSION_CLOSE = 2; + short ORD_AUTH_START = 3; + short ORD_HMAC_CHANGE = 4; + short ORD_KEY_CHANGE = 5; + short ORD_LV_GET = 6; + short ORD_ERROR_GET = 7; + short ORD_VER_GET = 8; + short ORD_KEYCHIP_INFO_WRITE = 9; + short ORD_KEYCHIP_INFO_READ = 10; + short ORD_GKEY_WRITE = 11; + short ORD_GKEY_ENC = 12; + short ORD_GKEY_DEC = 13; + short ORD_SFLAG_WRITE = 14; + short ORD_EEP_INIT = 15; + short ORD_LV_ENABLE = 16; + short ORD_PRIKEY_MODULUS_WRITE = 17; + short ORD_PRIKEY_EXPONENT_WRITE = 18; + short ORD_UDATA_WRITE = 19; + short ORD_UDATA_READ = 20; + short ORD_STORAGE_WRITE = 21; + short ORD_STORAGE_READ = 22; + short ORD_RAN_GET = 23; + short ORD_PLAY_COUNT_INCREMENT = 24; + short ORD_PLAY_COUNT_READ = 25; + short ORD_TRACE_DATA_INFO_WRITE = 26; + short ORD_TRACE_DATA_INFO_READ = 27; + short ORD_STORAGE_SIZE_GET = 28; + + short SUCCESS = 0; + short INVALID_AUTH = 1; + short AUTHFAIL = 2; + short LV_ERROR = 3; + short BAD_PARAMETER = 4; + short EEP_WRITEFAIL = 5; + short BAD_TAG = 6; + short BAD_ORDINAL = 7; + short SUMFAIL = 8; + short EEPWRITE_DISABLE = 9; + short BAD_DATASIZE = 10; + short FAIL = 11; + + byte LEVEL_BLOCKED = -1; + byte LEVEL_INITIAL = 0; + byte LEVEL_SESSION = 1; + byte LEVEL_AUTH = 2; + + byte SESSION_DISABLE = 0; + byte SESSION_ENABLE = 1; + + byte SFLAG_SHIPMENT = 4; + byte SFLAG_BLOCK = 32; + + short CRYPT_GKEY_ENCRYPT = 0; + short CRYPT_GKEY_DECRYPT = 1; + short CRYPT_CONN_ENCRYPT = 2; + short CRYPT_CONN_DECRYPT = 3; + short CRYPT_RSA_DECRYPT = 4; +} diff --git a/src/sega/a7/AppData.java b/src/sega/a7/AppData.java new file mode 100644 index 0000000..bf930c6 --- /dev/null +++ b/src/sega/a7/AppData.java @@ -0,0 +1,424 @@ +package sega.a7; + +import javacard.framework.JCSystem; +import javacard.framework.Util; +import javacard.security.RandomData; + +/** + * Persistent (EEPROM) and transient (RAM) state of the keychip. + * + *

Transient layout of {@code m_ramD_value}: + * [0]=auth level, [1]=session, [2..3]=last error (big-endian), + * [4]=consecutive HMAC failure count. The transient state is cleared on card + * reset; {@link #init()} rebuilds the level from the persistent block flag + * every time the applet is selected.

+ */ +public class AppData { + + /* + * Factory-default 2048-bit RSA key material from the original CAP. + * The modulus matches the public modulus published for hdd.sys; the second + * array is the matching private exponent. Keeping these defaults preserves + * wire compatibility, but they are public and MUST be replaced during + * personalisation on any real deployment. + */ + private static final byte[] m_ee_defaultPriModulus = new byte[]{-97, -48, -92, 58, 105, 115, 41, -59, 75, -82, -61, 60, -16, 115, 3, -69, 37, -12, -21, 15, 54, 107, 33, 70, 51, -44, -118, -65, 99, -100, 16, 87, 85, -67, -121, -12, 7, 26, 32, -27, -95, 57, -36, -14, -125, -56, 109, -27, -28, -47, 61, -93, 74, 2, 62, 33, -84, 27, 39, -92, 82, -60, -112, 68, -105, -26, 37, -61, 47, 111, -105, 98, -69, -128, 35, 93, 17, -43, -110, -106, 94, -90, -11, -125, -107, -122, -61, 6, 111, 32, 121, -25, 12, -106, 5, 90, -4, 90, 127, -62, 87, -119, -75, 77, -80, -125, 61, 54, 37, -72, -38, -31, 115, -95, 39, -90, -127, -52, -30, 16, 16, 59, -73, 47, -48, 32, 92, -45, -16, 36, -63, -69, 38, -30, 97, 102, 77, 99, -125, -112, -8, -17, 125, 81, 45, -49, 43, -57, 42, -126, 110, -20, 65, 63, 35, 55, -120, -70, 19, 9, 38, 64, 24, -17, -37, -40, -53, 116, -114, 8, -111, 124, -103, 42, 103, -73, -78, -64, 53, 126, 69, -85, -100, -98, 81, -49, 48, -114, -4, -64, 37, 111, 40, -103, -101, 7, -101, -69, 58, -33, 103, -41, 34, 26, 29, -24, -13, -11, -121, -95, -92, -106, 98, -32, -33, -26, -11, -6, 85, 115, 81, -16, 71, 121, 63, -50, 82, 100, 29, 44, 125, -58, -80, -22, -70, -118, -27, 41, -47, -33, -12, -36, 121, 59, -72, 41, -108, -53, 73, -74, -28, -17, -40, 15, 20, 43}; + private static final byte[] m_ee_defaultPriExponent = new byte[]{49, 10, 6, -70, -35, -52, -74, -50, -111, -80, -88, -68, 52, 84, 54, -102, -32, -60, -52, -76, 56, 80, -101, 63, 61, 124, 68, -81, 1, -45, -53, -73, -32, -39, 109, -76, 85, -47, -112, -78, -91, -52, 105, -98, 9, 14, 33, -102, -33, -95, -51, 54, 35, -23, -104, 67, 32, -38, -100, 73, 114, 20, 90, -24, -111, 107, -49, -66, -102, -7, -75, 12, 88, -40, -77, 1, 70, 13, -79, 19, 111, 43, -16, 62, 86, 77, -87, -34, 121, -113, 15, -104, -12, 101, -68, 114, -12, -24, -104, 55, 124, 65, 7, 95, 53, -128, -81, -15, -61, -107, 123, -92, -98, -92, -98, -44, 27, 20, 81, 113, -8, 105, 58, -122, -13, 104, 90, -45, 57, -66, -3, -93, 24, -127, 65, -103, 7, -121, -105, 127, -40, -66, 125, 88, -120, -122, -90, 126, 78, -21, -21, 56, -5, -80, -97, -81, 68, 70, -113, -7, -17, 116, -69, 121, 97, -19, 16, 99, 113, 76, -54, 110, -54, -20, -125, -119, -50, 74, 111, 49, 92, 48, 105, -22, 75, -14, -10, -54, -17, 32, 40, -94, 9, -80, 48, 114, 0, 71, 95, 119, 92, -54, -46, 72, -30, -23, 60, 66, -61, 18, -7, 84, 24, 61, 35, 62, -79, -78, -108, 9, 56, -116, 3, -26, 41, 97, -88, -125, 63, 90, 80, -125, 5, -73, -40, 1, 58, -85, -26, -51, 26, 1, 3, 74, 24, -28, 67, -115, -51, 70, -91, 40, 37, -11, 57, -15}; + + private final byte[] m_ee_sflag; + private final byte[] m_ee_keychipInfo; + private final byte[] m_ee_traceDataInfo; + private final byte[] m_ee_priModulus; + private final byte[] m_ee_priExponent; + private final byte[] m_ee_udata; + private final byte[] m_ee_playCount; + private final byte[] m_ee_storage; + private final byte[] m_ee_gkeyIv; + private final RandomData m_ee_rand; + + private final byte[] m_ramD_value; + private final byte[] m_ramD_rsa_update_status; + private final byte[] m_ramD_kh; + private final byte[] m_ramD_newNonceEvent; + private final byte[] m_ramD_lastNonceEvent; + private final byte[] m_ramD_cKeyIv; + private final byte[] m_ramD_generalBuffer; + private final byte[] m_ramD_footerBuffer; + private final byte[] m_ramD_nonceOddBuffer; + private final byte[] m_ramD_sendOutBuffer; + private final short[] m_ramD_sendOutInfo; + private final byte[] m_ramD_cryptBuffer; + private final byte[] m_ramD_zeroBuffer; + + public AppData(RandomData rand) { + m_ee_rand = rand; + m_ee_sflag = new byte[1]; + m_ee_keychipInfo = new byte[AppConfig.KEYCHIP_INFO_SIZE]; + m_ee_traceDataInfo = new byte[AppConfig.TRACE_DATA_INFO_SIZE]; + m_ee_priModulus = new byte[AppConfig.RSA_KEY_SIZE]; + m_ee_priExponent = new byte[AppConfig.RSA_KEY_SIZE]; + m_ee_udata = new byte[AppConfig.UDATA_SIZE]; + m_ee_playCount = new byte[AppConfig.PLAY_COUNT_SIZE]; + m_ee_storage = new byte[AppConfig.STORAGE_ARRAY_SIZE]; + m_ee_gkeyIv = new byte[AppConfig.AES_IV_SIZE]; + + Util.arrayCopy(m_ee_defaultPriModulus, (short) 0, m_ee_priModulus, + (short) 0, (short) m_ee_priModulus.length); + Util.arrayCopy(m_ee_defaultPriExponent, (short) 0, m_ee_priExponent, + (short) 0, (short) m_ee_priExponent.length); + + m_ee_sflag[0] = 0; + Util.arrayFillNonAtomic(m_ee_keychipInfo, (short) 0, + (short) m_ee_keychipInfo.length, (byte) 0xFF); + Util.arrayFillNonAtomic(m_ee_udata, (short) 0, + (short) m_ee_udata.length, (byte) 0xFF); + Util.arrayFillNonAtomic(m_ee_storage, (short) 0, + (short) m_ee_storage.length, (byte) 0xFF); + Util.arrayFillNonAtomic(m_ee_traceDataInfo, (short) 0, + (short) m_ee_traceDataInfo.length, (byte) 0xFF); + Util.arrayFillNonAtomic(m_ee_playCount, (short) 0, + (short) m_ee_playCount.length, (byte) 0); + Util.arrayFillNonAtomic(m_ee_gkeyIv, (short) 0, + (short) m_ee_gkeyIv.length, (byte) 0xFF); + + m_ramD_value = JCSystem.makeTransientByteArray((short) 6, JCSystem.CLEAR_ON_RESET); + m_ramD_rsa_update_status = JCSystem.makeTransientByteArray((short) 1, + JCSystem.CLEAR_ON_RESET); + m_ramD_kh = JCSystem.makeTransientByteArray(AppConfig.NONCE_SIZE, + JCSystem.CLEAR_ON_RESET); + m_ramD_newNonceEvent = JCSystem.makeTransientByteArray(AppConfig.NONCE_SIZE, + JCSystem.CLEAR_ON_RESET); + m_ramD_lastNonceEvent = JCSystem.makeTransientByteArray(AppConfig.NONCE_SIZE, + JCSystem.CLEAR_ON_RESET); + m_ramD_cKeyIv = JCSystem.makeTransientByteArray(AppConfig.AES_IV_SIZE, + JCSystem.CLEAR_ON_RESET); + m_ramD_generalBuffer = JCSystem.makeTransientByteArray((short) 256, + JCSystem.CLEAR_ON_RESET); + m_ramD_footerBuffer = JCSystem.makeTransientByteArray(AppConfig.SHA1_SIZE, + JCSystem.CLEAR_ON_RESET); + m_ramD_nonceOddBuffer = JCSystem.makeTransientByteArray(AppConfig.NONCE_SIZE, + JCSystem.CLEAR_ON_RESET); + m_ramD_sendOutBuffer = JCSystem.makeTransientByteArray((short) 256, + JCSystem.CLEAR_ON_RESET); + m_ramD_sendOutInfo = JCSystem.makeTransientShortArray((short) 2, + JCSystem.CLEAR_ON_RESET); + m_ramD_cryptBuffer = JCSystem.makeTransientByteArray(AppConfig.AES_BLOCK_SIZE, + JCSystem.CLEAR_ON_RESET); + m_ramD_zeroBuffer = JCSystem.makeTransientByteArray(AppConfig.AES_IV_SIZE, + JCSystem.CLEAR_ON_RESET); + } + + private static boolean fits(short arrayLength, short offset, short length) { + return offset >= 0 && length >= 0 && offset <= arrayLength + && length <= (short) (arrayLength - offset); + } + + public void init() { + m_ramD_value[0] = getBlockFlag_ee() ? AppConfig.LEVEL_BLOCKED + : AppConfig.LEVEL_INITIAL; + m_ramD_value[1] = AppConfig.SESSION_DISABLE; + m_ramD_value[4] = 0; + + Util.arrayFillNonAtomic(m_ramD_kh, (short) 0, + (short) m_ramD_kh.length, (byte) 0); + Util.arrayFillNonAtomic(m_ramD_newNonceEvent, (short) 0, + (short) m_ramD_newNonceEvent.length, (byte) 0); + Util.arrayFillNonAtomic(m_ramD_lastNonceEvent, (short) 0, + (short) m_ramD_lastNonceEvent.length, (byte) 0); + Util.arrayFillNonAtomic(m_ramD_cKeyIv, (short) 0, + (short) m_ramD_cKeyIv.length, (byte) 0); + } + + public void reset() { + m_ee_sflag[0] = 0; + Util.arrayFillNonAtomic(m_ee_keychipInfo, (short) 0, + (short) m_ee_keychipInfo.length, (byte) 0xFF); + Util.arrayFillNonAtomic(m_ee_udata, (short) 0, + (short) m_ee_udata.length, (byte) 0xFF); + Util.arrayFillNonAtomic(m_ee_storage, (short) 0, + (short) m_ee_storage.length, (byte) 0xFF); + Util.arrayFillNonAtomic(m_ee_traceDataInfo, (short) 0, + (short) m_ee_traceDataInfo.length, (byte) 0xFF); + Util.arrayFillNonAtomic(m_ee_playCount, (short) 0, + (short) m_ee_playCount.length, (byte) 0); + Util.arrayFillNonAtomic(m_ee_gkeyIv, (short) 0, + (short) m_ee_gkeyIv.length, (byte) 0xFF); + m_ramD_rsa_update_status[0] = 0; + } + + public void setLevel_ramD(byte setLevel) { + m_ramD_value[0] = setLevel; + } + + public byte getLevel_ramD() { + return m_ramD_value[0]; + } + + public void setSession_ramD(byte setSession) { + m_ramD_value[1] = setSession; + } + + public byte getSession_ramD() { + return m_ramD_value[1]; + } + + public void addValuePlayCounter_ee(byte value) { + short carry = (short) (value & 0xFF); + for (short i = (short) (m_ee_playCount.length - 1); i >= 0; i--) { + short sum = (short) (carry + (short) (m_ee_playCount[i] & 0xFF)); + m_ee_playCount[i] = (byte) (sum & 0xFF); + if (sum <= 0xFF) { + return; + } + carry = 1; + } + } + + public boolean getPlayCounter_ee(byte[] playCount, short offset, short length) { + if (playCount == null || !fits((short) playCount.length, offset, length) + || length < m_ee_playCount.length) { + return false; + } + Util.arrayCopyNonAtomic(m_ee_playCount, (short) 0, playCount, offset, + (short) m_ee_playCount.length); + return true; + } + + public final byte[] getNewNonceEvent_ramD() { + m_ee_rand.generateData(m_ramD_newNonceEvent, (short) 0, + (short) m_ramD_newNonceEvent.length); + return m_ramD_newNonceEvent; + } + + public final byte[] getLastNonceEvent_ramD() { + return m_ramD_lastNonceEvent; + } + + public boolean setLastNonceEvent_ramD(byte[] lastNonceEvent, short offset, short length) { + if (lastNonceEvent == null + || !fits((short) lastNonceEvent.length, offset, length) + || !fits((short) m_ramD_lastNonceEvent.length, (short) 0, length)) { + return false; + } + Util.arrayCopyNonAtomic(lastNonceEvent, offset, m_ramD_lastNonceEvent, + (short) 0, length); + if (length < AppConfig.NONCE_SIZE) { + Util.arrayFillNonAtomic(m_ramD_lastNonceEvent, length, + (short) (AppConfig.NONCE_SIZE - length), (byte) 0); + } + return true; + } + + public final byte[] getKh_ramD() { + return m_ramD_kh; + } + + public boolean setKh_ramD(byte[] kh, short offset, short length) { + if (kh == null || !fits((short) kh.length, offset, length) + || !fits((short) m_ramD_kh.length, (short) 0, length)) { + return false; + } + Util.arrayCopyNonAtomic(kh, offset, m_ramD_kh, (short) 0, length); + if (length < AppConfig.NONCE_SIZE) { + Util.arrayFillNonAtomic(m_ramD_kh, length, + (short) (AppConfig.NONCE_SIZE - length), (byte) 0); + } + return true; + } + + public final byte[] getGkeyIv_ee() { + return m_ee_gkeyIv; + } + + public boolean setGkeyIv_ee(byte[] newGkeyIv, short offset, short length) { + if (newGkeyIv == null || !fits((short) newGkeyIv.length, offset, length) + || !fits((short) m_ee_gkeyIv.length, (short) 0, length)) { + return false; + } + Util.arrayCopy(newGkeyIv, offset, m_ee_gkeyIv, (short) 0, length); + return true; + } + + public final byte[] getCkeyIv_ramD() { + return m_ramD_cKeyIv; + } + + public boolean setCkeyIv_ramD(byte[] newCkeyIv, short offset, short length) { + if (newCkeyIv == null || !fits((short) newCkeyIv.length, offset, length) + || !fits((short) m_ramD_cKeyIv.length, (short) 0, length)) { + return false; + } + Util.arrayCopyNonAtomic(newCkeyIv, offset, m_ramD_cKeyIv, (short) 0, length); + return true; + } + + public final byte[] getRsaDefaultPriModulus_ee() { + return m_ee_defaultPriModulus; + } + + public final byte[] getRsaDefaultPriExponent_ee() { + return m_ee_defaultPriExponent; + } + + public final byte[] getRsaPriModulus_ee() { + return m_ee_priModulus; + } + + public final byte[] getRsaPriExponent_ee() { + return m_ee_priExponent; + } + + public boolean setRsaPriKeyModulus_ee(byte[] modulus, short mOffset, short mLength) { + if (modulus == null || !fits((short) modulus.length, mOffset, mLength) + || !fits((short) m_ee_priModulus.length, (short) 0, mLength)) { + return false; + } + Util.arrayCopy(modulus, mOffset, m_ee_priModulus, (short) 0, mLength); + return true; + } + + public boolean setRsaPriKeyExponent_ee(byte[] exponent, short eOffset, short eLength) { + if (exponent == null || !fits((short) exponent.length, eOffset, eLength) + || !fits((short) m_ee_priExponent.length, (short) 0, eLength)) { + return false; + } + Util.arrayCopy(exponent, eOffset, m_ee_priExponent, (short) 0, eLength); + return true; + } + + public boolean getSflag_ee() { + return (m_ee_sflag[0] & AppConfig.SFLAG_SHIPMENT) != 0; + } + + public void activeSflag() { + m_ee_sflag[0] = (byte) (m_ee_sflag[0] | AppConfig.SFLAG_SHIPMENT); + } + + public boolean getRsaExponentUpdate_ramD() { + return (m_ramD_rsa_update_status[0] & AppConfig.RSA_UPDATE_EXPONENT) != 0; + } + + public void activeRsaExponentUpdate_ramD() { + m_ramD_rsa_update_status[0] = + (byte) (m_ramD_rsa_update_status[0] | AppConfig.RSA_UPDATE_EXPONENT); + } + + public boolean getRsaModulusUpdate_ramD() { + return (m_ramD_rsa_update_status[0] & AppConfig.RSA_UPDATE_MODULUS) != 0; + } + + public void activeRsaModulusUpdate_ramD() { + m_ramD_rsa_update_status[0] = + (byte) (m_ramD_rsa_update_status[0] | AppConfig.RSA_UPDATE_MODULUS); + } + + public boolean getBlockFlag_ee() { + return (m_ee_sflag[0] & AppConfig.SFLAG_BLOCK) != 0; + } + + public void activeBlockFlag() { + m_ee_sflag[0] = (byte) (m_ee_sflag[0] | AppConfig.SFLAG_BLOCK); + } + + public final byte[] getKeychipInfo_ee() { + return m_ee_keychipInfo; + } + + public boolean setKeychipInfo_ee(byte[] newKeychipInfo, short offset, short length) { + if (newKeychipInfo == null || !fits((short) newKeychipInfo.length, offset, length) + || !fits((short) m_ee_keychipInfo.length, (short) 0, length)) { + return false; + } + Util.arrayCopy(newKeychipInfo, offset, m_ee_keychipInfo, (short) 0, length); + return true; + } + + public final byte[] getTraceDataInfo_ee() { + return m_ee_traceDataInfo; + } + + public boolean setTraceDataInfo_ee(byte[] newTraceDataInfo, short offset, short length) { + if (newTraceDataInfo == null || !fits((short) newTraceDataInfo.length, offset, length) + || !fits((short) m_ee_traceDataInfo.length, (short) 0, length)) { + return false; + } + Util.arrayCopy(newTraceDataInfo, offset, m_ee_traceDataInfo, (short) 0, length); + return true; + } + + public final byte[] getUdata_ee() { + return m_ee_udata; + } + + public boolean setUdata_ee(byte[] newUdata, short offset, short length) { + if (newUdata == null || !fits((short) newUdata.length, offset, length) + || !fits((short) m_ee_udata.length, (short) 0, length)) { + return false; + } + Util.arrayCopy(newUdata, offset, m_ee_udata, (short) 0, length); + return true; + } + + public final byte[] getStorageData_ee() { + return m_ee_storage; + } + + public boolean setStorageData_ee(byte[] newStorage, short offset, short length, + short dstOffset) { + if (newStorage == null || !fits((short) newStorage.length, offset, length) + || dstOffset < 0 + || !fits((short) m_ee_storage.length, dstOffset, length)) { + return false; + } + Util.arrayCopy(newStorage, offset, m_ee_storage, dstOffset, length); + return true; + } + + public byte[] getGeneralBuffer_ramD() { + return m_ramD_generalBuffer; + } + + public byte[] getSendOutBuffer_ramD() { + return m_ramD_sendOutBuffer; + } + + public short[] getSendOutInfo_ramD() { + return m_ramD_sendOutInfo; + } + + public byte[] getCryptBuffer_ramD() { + return m_ramD_cryptBuffer; + } + + public byte[] getZeroBuffer_ramD() { + return m_ramD_zeroBuffer; + } + + public byte[] getFooterBuffer_ramD() { + return m_ramD_footerBuffer; + } + + public byte[] getNonceOddBuffer_ramD() { + return m_ramD_nonceOddBuffer; + } + + public void setLastError_ramD(short error) { + Util.setShort(m_ramD_value, (short) 2, error); + } + + public short getLastError_ramD() { + return Util.getShort(m_ramD_value, (short) 2); + } + + public void incrementErrorCount() { + m_ramD_value[4] = (byte) (m_ramD_value[4] + 1); + if (m_ramD_value[4] == (byte) 3) { + activeBlockFlag(); + m_ramD_value[0] = AppConfig.LEVEL_BLOCKED; + } + } + + public void clearErrorCount() { + m_ramD_value[4] = 0; + } +} diff --git a/src/sega/a7/AuthCommand.java b/src/sega/a7/AuthCommand.java new file mode 100644 index 0000000..e81df8f --- /dev/null +++ b/src/sega/a7/AuthCommand.java @@ -0,0 +1,301 @@ +package sega.a7; + +import javacard.framework.Util; + +/** + * Base class for authenticated commands (tag C2 / C4). + * + *

The session must be open and the current level must be exactly the level + * required by the command. The request footer is + * {@code nonceOdd(20) | HMAC-SHA1(Kh, SHA1(header||payload) || lastNonceEven || nonceOdd)}.

+ */ +public class AuthCommand extends Command { + + private final Crypt m_ee_crypt; + private final HmacSha1 m_ee_hmacSha1; + private final byte m_ee_authLevel; + + public AuthCommand(short cmd, short recvPacketSize, short sendPacketSize, byte authLevel, + AppData data, MakePacket packet, javacard.security.MessageDigest sha1, + HmacSha1 hmacSha1, Crypt crypt) { + super(cmd, recvPacketSize, sendPacketSize, data, packet, sha1); + m_ee_crypt = crypt; + m_ee_hmacSha1 = hmacSha1; + m_ee_authLevel = authLevel; + m_ee_tag = AppConfig.TAG_RQU_AUTH_COMMAND; + } + + public short execute(byte[] data, short dataOffset, short dataLength, + byte[] dst, short dstOffset, short dstLength) { + if (m_ee_data.getSession_ramD() != AppConfig.SESSION_ENABLE) { + return makeErrorPacket(AppConfig.INVALID_AUTH, dst, dstOffset, dstLength); + } + if (m_ee_data.getLevel_ramD() != m_ee_authLevel) { + return makeErrorPacket(AppConfig.LV_ERROR, dst, dstOffset, dstLength); + } + + short error = checkHeader(data, dataOffset, dataLength); + if (error != AppConfig.SUCCESS) { + return makeErrorPacket(error, dst, dstOffset, dstLength); + } + if (dataLength < AppConfig.AUTH_FOOTER_SIZE) { + return makeErrorPacket(AppConfig.BAD_DATASIZE, dst, dstOffset, dstLength); + } + + error = checkFooter(data, dataOffset, dataLength); + if (error != AppConfig.SUCCESS) { + return makeErrorPacket(error, dst, dstOffset, dstLength); + } + + m_ramD_outDataInfo[0] = 0; + m_ramD_outDataInfo[1] = 0; + + error = core(data, dataOffset, dataLength); + if (error != AppConfig.SUCCESS) { + return makeErrorPacket(error, dst, dstOffset, dstLength); + } + + short dataRef = m_ramD_outDataInfo[0]; + short dataSize = m_ramD_outDataInfo[1]; + if (dataRef < 0 || dataSize < 0 || dataRef > m_ramD_outData.length + || dataSize > (short) (m_ramD_outData.length - dataRef)) { + return makeErrorPacket(AppConfig.FAIL, dst, dstOffset, dstLength); + } + + short paramSize = (short) (AppConfig.HEADER_SIZE + dataSize + AppConfig.AUTH_FOOTER_SIZE); + m_ramD_setSendPacketSize[0] = paramSize; + short oddOffset = (short) (dataOffset + dataLength - AppConfig.AUTH_FOOTER_SIZE); + + return makeSendAuthPacket(AppConfig.TAG_RSP_AUTH_COMMAND, paramSize, + AppConfig.SUCCESS, m_ramD_outData, dataRef, dataSize, + data, oddOffset, AppConfig.NONCE_SIZE, dst, dstOffset, dstLength); + } + + protected boolean changeConnectionKey_ee(byte[] aesKey, short aesKeyOffset, short aesKeyLength, + byte[] aesIv, short aesIvOffset, short aesIvLength) { + if (aesKeyLength != AppConfig.AES_KEY_SIZE || aesIvLength != AppConfig.AES_IV_SIZE) { + return false; + } + byte[] zeroIv = m_ee_data.getZeroBuffer_ramD(); + Util.arrayFillNonAtomic(zeroIv, (short) 0, (short) zeroIv.length, (byte) 0); + + if (!m_ee_crypt.init(aesKey, aesKeyOffset, aesKeyLength, zeroIv, (short) 0, + (short) zeroIv.length, AppConfig.CRYPT_CONN_ENCRYPT)) { + return false; + } + if (!m_ee_crypt.init(aesKey, aesKeyOffset, aesKeyLength, zeroIv, (short) 0, + (short) zeroIv.length, AppConfig.CRYPT_CONN_DECRYPT)) { + return false; + } + return m_ee_data.setCkeyIv_ramD(aesIv, aesIvOffset, aesIvLength); + } + + protected boolean changeGKey_ee(byte[] aesKey, short aesKeyOffset, short aesKeyLength, + byte[] aesIv, short aesIvOffset, short aesIvLength) { + if (aesKeyLength != AppConfig.AES_KEY_SIZE || aesIvLength != AppConfig.AES_IV_SIZE) { + return false; + } + byte[] zeroIv = m_ee_data.getZeroBuffer_ramD(); + Util.arrayFillNonAtomic(zeroIv, (short) 0, (short) zeroIv.length, (byte) 0); + + if (!m_ee_crypt.init(aesKey, aesKeyOffset, aesKeyLength, zeroIv, (short) 0, + (short) zeroIv.length, AppConfig.CRYPT_GKEY_ENCRYPT)) { + return false; + } + if (!m_ee_crypt.init(aesKey, aesKeyOffset, aesKeyLength, zeroIv, (short) 0, + (short) zeroIv.length, AppConfig.CRYPT_GKEY_DECRYPT)) { + return false; + } + return m_ee_data.setGkeyIv_ee(aesIv, aesIvOffset, aesIvLength); + } + + /** + * One-shot AES-CBC decrypt with the connection key. The first plaintext + * block is XORed with the connection IV, matching the original CAP. + */ + protected short decData(byte[] src, short srcOffset, short srcLength, + byte[] dst, short dstOffset) { + short size = m_ee_crypt.doFinal(src, srcOffset, srcLength, dst, dstOffset, + AppConfig.CRYPT_CONN_DECRYPT); + if (size <= 0) { + return 0; + } + byte[] iv = m_ee_data.getCkeyIv_ramD(); + short xorLength = (size < AppConfig.AES_BLOCK_SIZE) ? size : AppConfig.AES_BLOCK_SIZE; + for (short i = 0; i < xorLength; i++) { + dst[(short) (dstOffset + i)] = (byte) (dst[(short) (dstOffset + i)] ^ iv[i]); + } + return size; + } + + /** + * Stateful GKEY CBC decrypt. {@code bInit} resets the chain and applies + * the persistent GKEY IV to the first block. + */ + protected short decUpdate_Gkey(byte[] src, short srcOffset, short srcLength, + byte[] dst, short dstOffset, boolean bInit) { + byte[] tmp = m_ee_data.getCryptBuffer_ramD(); + if (bInit) { + m_ee_crypt.doFinal(tmp, (short) 0, AppConfig.AES_BLOCK_SIZE, tmp, (short) 0, + AppConfig.CRYPT_GKEY_DECRYPT); + } + short size = m_ee_crypt.update(src, srcOffset, srcLength, dst, dstOffset, + AppConfig.CRYPT_GKEY_DECRYPT); + if (bInit && size > 0) { + byte[] iv = m_ee_data.getGkeyIv_ee(); + short xorLength = (size < AppConfig.AES_BLOCK_SIZE) ? size : AppConfig.AES_BLOCK_SIZE; + for (short i = 0; i < xorLength; i++) { + dst[(short) (dstOffset + i)] = (byte) (dst[(short) (dstOffset + i)] ^ iv[i]); + } + } + return size; + } + + /** + * One-shot AES-CBC encrypt with the connection key. The first block is + * XORed with the connection IV and zero padded to a full block. + */ + protected short encData(byte[] src, short srcOffset, short srcLength, + byte[] dst, short dstOffset) { + if (srcLength <= 0) { + return 0; + } + if (srcLength > AppConfig.AES_BLOCK_SIZE + && (short) ((srcLength - AppConfig.AES_BLOCK_SIZE) % AppConfig.AES_BLOCK_SIZE) != 0) { + return 0; + } + byte[] tmp = m_ee_data.getCryptBuffer_ramD(); + byte[] iv = m_ee_data.getCkeyIv_ramD(); + + for (short i = 0; i < AppConfig.AES_BLOCK_SIZE; i++) { + byte value = (i < srcLength) ? src[(short) (srcOffset + i)] : (byte) 0; + tmp[i] = (byte) (value ^ iv[i]); + } + + if (srcLength <= AppConfig.AES_BLOCK_SIZE) { + return m_ee_crypt.doFinal(tmp, (short) 0, AppConfig.AES_BLOCK_SIZE, + dst, dstOffset, AppConfig.CRYPT_CONN_ENCRYPT); + } + + short size = m_ee_crypt.update(tmp, (short) 0, AppConfig.AES_BLOCK_SIZE, + dst, dstOffset, AppConfig.CRYPT_CONN_ENCRYPT); + size = (short) (size + m_ee_crypt.doFinal(src, + (short) (srcOffset + AppConfig.AES_BLOCK_SIZE), + (short) (srcLength - AppConfig.AES_BLOCK_SIZE), + dst, (short) (dstOffset + AppConfig.AES_BLOCK_SIZE), + AppConfig.CRYPT_CONN_ENCRYPT)); + return size; + } + + /** + * Stateful GKEY CBC encrypt. {@code bInit} resets the chain and applies + * the persistent GKEY IV to the first block. + */ + protected short encUpdate_GKey(byte[] src, short srcOffset, short srcLength, + byte[] dst, short dstOffset, boolean bInit) { + if (!bInit) { + return m_ee_crypt.update(src, srcOffset, srcLength, dst, dstOffset, + AppConfig.CRYPT_GKEY_ENCRYPT); + } + if (srcLength <= 0) { + return 0; + } + byte[] tmp = m_ee_data.getCryptBuffer_ramD(); + byte[] iv = m_ee_data.getGkeyIv_ee(); + + m_ee_crypt.doFinal(tmp, (short) 0, AppConfig.AES_BLOCK_SIZE, tmp, (short) 0, + AppConfig.CRYPT_GKEY_ENCRYPT); + + for (short i = 0; i < AppConfig.AES_BLOCK_SIZE; i++) { + byte value = (i < srcLength) ? src[(short) (srcOffset + i)] : (byte) 0; + tmp[i] = (byte) (value ^ iv[i]); + } + + if (srcLength <= AppConfig.AES_BLOCK_SIZE) { + return m_ee_crypt.update(tmp, (short) 0, AppConfig.AES_BLOCK_SIZE, + dst, dstOffset, AppConfig.CRYPT_GKEY_ENCRYPT); + } + + short size = m_ee_crypt.update(tmp, (short) 0, AppConfig.AES_BLOCK_SIZE, + dst, dstOffset, AppConfig.CRYPT_GKEY_ENCRYPT); + size = (short) (size + m_ee_crypt.update(src, + (short) (srcOffset + AppConfig.AES_BLOCK_SIZE), + (short) (srcLength - AppConfig.AES_BLOCK_SIZE), + dst, (short) (dstOffset + AppConfig.AES_BLOCK_SIZE), + AppConfig.CRYPT_GKEY_ENCRYPT)); + return size; + } + + protected boolean changeRsaKey_ee(byte[] data1, short data1Offset, short data1Length, + byte[] data2, short data2Offset, short data2Length) { + return m_ee_crypt.init(data1, data1Offset, data1Length, + data2, data2Offset, data2Length, AppConfig.CRYPT_RSA_DECRYPT); + } + + protected short rsaDecData(byte[] src, short srcOffset, short srcLength, + byte[] dst, short dstOffset) { + return m_ee_crypt.doFinal(src, srcOffset, srcLength, dst, dstOffset, + AppConfig.CRYPT_RSA_DECRYPT); + } + + protected short checkFooter(byte[] data, short dataOffset, short dataLength) { + byte[] kh = m_ee_data.getKh_ramD(); + return checkFooterWithKey(kh, (short) 0, (short) kh.length, + data, dataOffset, dataLength); + } + + /** + * Verifies an authenticated request against an explicit HMAC key. Used by + * AuthStart so a failed handshake does not overwrite the active Kh. + */ + protected short checkFooterWithKey(byte[] kh, short khOffset, short khLength, + byte[] data, short dataOffset, short dataLength) { + if (data == null || dataLength < AppConfig.AUTH_FOOTER_SIZE) { + return AppConfig.BAD_DATASIZE; + } + short oddOffset = (short) (dataOffset + dataLength - AppConfig.AUTH_FOOTER_SIZE); + short authOffset = (short) (dataOffset + dataLength - AppConfig.NONCE_SIZE); + byte[] buffer = m_ee_data.getFooterBuffer_ramD(); + byte[] lastNonce = m_ee_data.getLastNonceEvent_ramD(); + + m_ee_sha1.reset(); + m_ee_sha1.doFinal(data, dataOffset, + (short) (dataLength - AppConfig.AUTH_FOOTER_SIZE), buffer, (short) 0); + + boolean ok = m_ee_hmacSha1.init(kh, khOffset, khLength); + ok = ok && m_ee_hmacSha1.update(buffer, (short) 0, AppConfig.SHA1_SIZE); + ok = ok && m_ee_hmacSha1.update(lastNonce, (short) 0, + (short) lastNonce.length); + ok = ok && m_ee_hmacSha1.sign(data, oddOffset, AppConfig.NONCE_SIZE, + buffer, (short) 0); + + if (!ok || Util.arrayCompare(data, authOffset, buffer, (short) 0, + AppConfig.NONCE_SIZE) != 0) { + m_ee_data.incrementErrorCount(); + return AppConfig.AUTHFAIL; + } + m_ee_data.clearErrorCount(); + return AppConfig.SUCCESS; + } + + protected short makeSendAuthPacket(short tag, short paramSize, short result, + byte[] data, short dataOffset, short dataSize, + byte[] nonceOdd, short oddOffset, short oddLength, + byte[] sendPacket, short packetOffset, short packetLength) { + byte[] kh = m_ee_data.getKh_ramD(); + byte[] nonceEvent = m_ee_data.getNewNonceEvent_ramD(); + + boolean ok = m_ee_packet.setData(kh, (short) 0, (short) kh.length, + tag, paramSize, result, m_ee_cmd, + data, dataOffset, dataSize, + nonceEvent, (short) 0, (short) nonceEvent.length, + nonceOdd, oddOffset, oddLength, + sendPacket, packetOffset, packetLength, m_ee_sha1, m_ee_hmacSha1); + if (!ok) { + return makeErrorPacket(AppConfig.FAIL, sendPacket, packetOffset, packetLength); + } + m_ee_data.setLastNonceEvent_ramD(nonceEvent, (short) 0, + (short) nonceEvent.length); + return paramSize; + } +} diff --git a/src/sega/a7/AuthStart.java b/src/sega/a7/AuthStart.java new file mode 100644 index 0000000..d6ad5c5 --- /dev/null +++ b/src/sega/a7/AuthStart.java @@ -0,0 +1,113 @@ +package sega.a7; + +import javacard.framework.Util; +import javacard.security.CryptoException; +import javacard.security.MessageDigest; + +/** + * RSA-authenticated session handshake (tag C5 / C6). + * + *

Request: header(6) | RSA-2048 ciphertext(256) | nonceOdd(20) | HMAC(20). + * The decrypted plaintext must contain AES key(16) | IV(16) | new Kh(20). + * The request HMAC is verified with the new Kh before it replaces the active + * key, so a failed handshake cannot corrupt an established session.

+ */ +public class AuthStart extends AuthCommand { + + private static final short RSA_CIPHERTEXT_OFFSET = AppConfig.HEADER_SIZE; + private static final short NONCE_ODD_OFFSET = + (short) (RSA_CIPHERTEXT_OFFSET + AppConfig.RSA_KEY_SIZE); + private static final short NEW_KH_OFFSET = + (short) (AppConfig.AES_KEY_SIZE + AppConfig.AES_IV_SIZE); + private static final short SESSION_MATERIAL_SIZE = + (short) (NEW_KH_OFFSET + AppConfig.NONCE_SIZE); + + public AuthStart(short cmd, AppData data, MakePacket packet, MessageDigest sha1, + HmacSha1 hmacSha1, Crypt crypt) { + super(cmd, (short) 302, (short) 46, AppConfig.LEVEL_SESSION, + data, packet, sha1, hmacSha1, crypt); + } + + public short execute(byte[] data, short dataOffset, short dataLength, + byte[] dst, short dstOffset, short dstLength) { + if (m_ee_data.getSession_ramD() != AppConfig.SESSION_ENABLE) { + return makeErrorPacket(AppConfig.INVALID_AUTH, dst, dstOffset, dstLength); + } + if (m_ee_data.getLevel_ramD() != AppConfig.LEVEL_SESSION) { + return makeErrorPacket(AppConfig.LV_ERROR, dst, dstOffset, dstLength); + } + + short error = checkHeader(data, dataOffset, dataLength); + if (error != AppConfig.SUCCESS) { + return makeErrorPacket(error, dst, dstOffset, dstLength); + } + + byte[] buffer = m_ee_data.getGeneralBuffer_ramD(); + if (buffer.length < AppConfig.RSA_KEY_SIZE) { + return makeErrorPacket(AppConfig.FAIL, dst, dstOffset, dstLength); + } + + short decSize; + try { + decSize = rsaDecData(data, + (short) (dataOffset + RSA_CIPHERTEXT_OFFSET), + AppConfig.RSA_KEY_SIZE, buffer, (short) 0); + } catch (CryptoException e) { + return makeErrorPacket(AppConfig.AUTHFAIL, dst, dstOffset, dstLength); + } + + if (decSize < SESSION_MATERIAL_SIZE) { + zeroize(buffer); + return makeErrorPacket(AppConfig.AUTHFAIL, dst, dstOffset, dstLength); + } + + error = checkFooterWithKey(buffer, NEW_KH_OFFSET, AppConfig.NONCE_SIZE, + data, dataOffset, dataLength); + if (error != AppConfig.SUCCESS) { + zeroize(buffer); + return makeErrorPacket(error, dst, dstOffset, dstLength); + } + + if (!m_ee_data.setKh_ramD(buffer, NEW_KH_OFFSET, AppConfig.NONCE_SIZE)) { + zeroize(buffer); + return makeErrorPacket(AppConfig.FAIL, dst, dstOffset, dstLength); + } + if (!changeConnectionKey_ee(buffer, (short) 0, AppConfig.AES_KEY_SIZE, + buffer, AppConfig.AES_KEY_SIZE, AppConfig.AES_IV_SIZE)) { + zeroize(buffer); + return makeErrorPacket(AppConfig.FAIL, dst, dstOffset, dstLength); + } + + m_ee_data.setLevel_ramD(AppConfig.LEVEL_AUTH); + short sendSize = makeSendAuthPacket(AppConfig.TAG_RSP_RSA_COMMAND, + (short) 46, AppConfig.SUCCESS, null, (short) 0, (short) 0, + data, (short) (dataOffset + NONCE_ODD_OFFSET), AppConfig.NONCE_SIZE, + dst, dstOffset, dstLength); + zeroize(buffer); + return sendSize; + } + + protected short checkHeader(byte[] data, short dataOffset, short dataLength) { + if (data == null || dataOffset < 0 || dataLength < 0 + || dataOffset > data.length + || dataLength > (short) (data.length - dataOffset)) { + return AppConfig.BAD_DATASIZE; + } + if (dataLength != (short) 302) { + return AppConfig.BAD_DATASIZE; + } + if (Util.getShort(data, dataOffset) != AppConfig.TAG_RQU_RSA_COMMAND) { + return AppConfig.BAD_TAG; + } + if (Util.getShort(data, (short) (dataOffset + AppConfig.SIZE_OFFSET)) + != (short) 302) { + return AppConfig.BAD_DATASIZE; + } + return AppConfig.SUCCESS; + } + + private static void zeroize(byte[] buffer) { + Util.arrayFillNonAtomic(buffer, (short) 0, (short) buffer.length, + (byte) 0); + } +} diff --git a/src/sega/a7/Command.java b/src/sega/a7/Command.java new file mode 100644 index 0000000..1f2ebe1 --- /dev/null +++ b/src/sega/a7/Command.java @@ -0,0 +1,162 @@ +package sega.a7; + +import javacard.framework.JCSystem; +import javacard.framework.Util; +import javacard.security.MessageDigest; + +/** + * Base class for unauthenticated commands (tag C1 / C3). + */ +public class Command { + + protected short m_ee_cmd; + protected short m_ee_recvPacketSize; + protected short m_ee_sendPacketSize; + protected short[] m_ramD_setSendPacketSize; + protected short m_ee_tag; + protected MakePacket m_ee_packet; + protected AppData m_ee_data; + protected MessageDigest m_ee_sha1; + + protected byte[] m_ramD_outData; + protected short[] m_ramD_outDataInfo; + + public Command(short cmd, short recvPacketSize, short sendPacketSize, + AppData data, MakePacket packet, MessageDigest sha1) { + m_ee_cmd = cmd; + m_ee_data = data; + m_ee_packet = packet; + m_ee_recvPacketSize = recvPacketSize; + m_ee_sendPacketSize = sendPacketSize; + m_ee_tag = AppConfig.TAG_RQU_COMMAND; + m_ee_sha1 = sha1; + m_ramD_setSendPacketSize = JCSystem.makeTransientShortArray((short) 1, + JCSystem.CLEAR_ON_RESET); + m_ramD_setSendPacketSize[0] = sendPacketSize; + m_ramD_outData = data.getSendOutBuffer_ramD(); + m_ramD_outDataInfo = data.getSendOutInfo_ramD(); + } + + public boolean select() { + m_ramD_setSendPacketSize[0] = m_ee_sendPacketSize; + return true; + } + + public short core(byte[] data, short dataOffset, short dataLength) { + return AppConfig.SUCCESS; + } + + public short execute(byte[] data, short dataOffset, short dataLength, + byte[] dst, short dstOffset, short dstLength) { + short error = checkHeader(data, dataOffset, dataLength); + if (error != AppConfig.SUCCESS) { + return makeErrorPacket(error, dst, dstOffset, dstLength); + } + + error = checkFooter(data, dataOffset, dataLength); + if (error != AppConfig.SUCCESS) { + return makeErrorPacket(error, dst, dstOffset, dstLength); + } + + m_ramD_outDataInfo[0] = 0; + m_ramD_outDataInfo[1] = 0; + + error = core(data, dataOffset, dataLength); + if (error != AppConfig.SUCCESS) { + return makeErrorPacket(error, dst, dstOffset, dstLength); + } + + short dataRef = m_ramD_outDataInfo[0]; + short dataSize = m_ramD_outDataInfo[1]; + if (dataRef < 0 || dataSize < 0 || dataRef > m_ramD_outData.length + || dataSize > (short) (m_ramD_outData.length - dataRef)) { + return makeErrorPacket(AppConfig.FAIL, dst, dstOffset, dstLength); + } + + short paramSize = (short) (AppConfig.HEADER_SIZE + dataSize + AppConfig.SHA1_SIZE); + m_ramD_setSendPacketSize[0] = paramSize; + return makeSendPacket(AppConfig.TAG_RSP_COMMAND, paramSize, AppConfig.SUCCESS, + m_ramD_outData, dataRef, dataSize, dst, dstOffset, dstLength); + } + + public short getCmd_ee() { + return m_ee_cmd; + } + + protected short setSendPacketSize_ramD(short sendSize) { + m_ramD_setSendPacketSize[0] = sendSize; + return m_ramD_setSendPacketSize[0]; + } + + protected short checkHeader(byte[] data, short dataOffset, short dataLength) { + if (data == null || dataOffset < 0 || dataLength < 0 + || dataOffset > data.length + || dataLength > (short) (data.length - dataOffset)) { + return AppConfig.BAD_DATASIZE; + } + if (dataLength < (short) (AppConfig.HEADER_SIZE + AppConfig.SHA1_SIZE)) { + return AppConfig.BAD_DATASIZE; + } + + short paramSize = Util.getShort(data, (short) (dataOffset + AppConfig.SIZE_OFFSET)); + if (paramSize != dataLength) { + return AppConfig.BAD_DATASIZE; + } + + short tag = Util.getShort(data, (short) (dataOffset + AppConfig.TAG_OFFSET)); + if (tag != m_ee_tag) { + return AppConfig.BAD_TAG; + } + + if (m_ee_recvPacketSize != 0 && dataLength != m_ee_recvPacketSize) { + return AppConfig.BAD_DATASIZE; + } + return AppConfig.SUCCESS; + } + + protected short checkFooter(byte[] data, short dataOffset, short dataLength) { + if (data == null || dataLength < AppConfig.SHA1_SIZE) { + return AppConfig.SUMFAIL; + } + short contentLength = (short) (dataLength - AppConfig.SHA1_SIZE); + short digestOffset = (short) (dataOffset + contentLength); + byte[] buffer = m_ee_data.getFooterBuffer_ramD(); + + m_ee_sha1.reset(); + m_ee_sha1.doFinal(data, dataOffset, contentLength, buffer, (short) 0); + if (Util.arrayCompare(data, digestOffset, buffer, (short) 0, + AppConfig.SHA1_SIZE) != 0) { + return AppConfig.SUMFAIL; + } + return AppConfig.SUCCESS; + } + + protected short makeSendPacket(short tag, short paramSize, short result, + byte[] data, short dataOffset, short dataSize, + byte[] sendPacket, short packetOffset, short packetLength) { + boolean ok = m_ee_packet.setData(null, (short) 0, (short) 0, tag, paramSize, + result, m_ee_cmd, data, dataOffset, dataSize, + null, (short) 0, (short) 0, null, (short) 0, (short) 0, + sendPacket, packetOffset, packetLength, m_ee_sha1, null); + if (!ok) { + return makeErrorPacket(AppConfig.FAIL, sendPacket, packetOffset, packetLength); + } + return paramSize; + } + + protected short makeErrorPacket(short error, byte[] sendPacket, + short packetOffset, short packetLength) { + return makeErrorPacket(error, m_ee_cmd, sendPacket, packetOffset, packetLength); + } + + protected short makeErrorPacket(short error, short cmd, byte[] sendPacket, + short packetOffset, short packetLength) { + m_ee_data.setLastError_ramD(error); + m_ee_packet.setData(null, (short) 0, (short) 0, AppConfig.TAG_RSP_COMMAND, + AppConfig.ERROR_PACKET_SIZE, error, cmd, + null, (short) 0, (short) 0, null, (short) 0, (short) 0, + null, (short) 0, (short) 0, + sendPacket, packetOffset, packetLength, m_ee_sha1, null); + return AppConfig.ERROR_PACKET_SIZE; + } +} diff --git a/src/sega/a7/CommandServer.java b/src/sega/a7/CommandServer.java new file mode 100644 index 0000000..e7cb615 --- /dev/null +++ b/src/sega/a7/CommandServer.java @@ -0,0 +1,78 @@ +package sega.a7; + +import javacard.framework.Util; + +/** + * Ordinal-to-command dispatcher. + */ +public class CommandServer { + + private Command m_ee_noCommand; + private Command[] m_ee_list; + private short m_ee_listCount; + + public CommandServer() { + m_ee_noCommand = null; + m_ee_list = new Command[4]; + m_ee_listCount = 0; + } + + public void setNoCommand(Command cmdClass) { + m_ee_noCommand = cmdClass; + } + + public void attachCommand(Command cmdClass) { + if (cmdClass == null) { + return; + } + if (m_ee_listCount >= m_ee_list.length) { + Command[] tmp = new Command[(short) (m_ee_list.length * 2)]; + for (short i = 0; i < m_ee_list.length; i++) { + tmp[i] = m_ee_list[i]; + } + m_ee_list = tmp; + } + m_ee_list[m_ee_listCount] = cmdClass; + m_ee_listCount = (short) (m_ee_listCount + 1); + } + + public short findCommand(short cmd) { + for (short i = 0; i < m_ee_listCount; i++) { + if (cmd == m_ee_list[i].getCmd_ee()) { + return i; + } + } + return -1; + } + + public boolean select() { + boolean ret = true; + for (short i = 0; i < m_ee_listCount; i++) { + if (!m_ee_list[i].select()) { + ret = false; + } + } + return ret; + } + + public short execute(byte[] data, short dataOffset, short dataLength, + byte[] dst, short dstOffset, short dstLength) { + short cmd = -1; + if (data != null && dataOffset >= 0 && dataLength >= AppConfig.HEADER_SIZE + && dataOffset <= (short) (data.length - AppConfig.HEADER_SIZE)) { + cmd = Util.getShort(data, + (short) (dataOffset + AppConfig.CMD_OFFSET)); + } + + short index = findCommand(cmd); + if (index >= 0) { + return m_ee_list[index].execute(data, dataOffset, dataLength, + dst, dstOffset, dstLength); + } + if (m_ee_noCommand != null) { + return m_ee_noCommand.execute(data, dataOffset, dataLength, + dst, dstOffset, dstLength); + } + return -1; + } +} diff --git a/src/sega/a7/Crypt.java b/src/sega/a7/Crypt.java new file mode 100644 index 0000000..5fb8671 --- /dev/null +++ b/src/sega/a7/Crypt.java @@ -0,0 +1,138 @@ +package sega.a7; + +import javacard.security.AESKey; +import javacard.security.CryptoException; +import javacard.security.KeyBuilder; +import javacard.security.RSAPrivateKey; +import javacardx.crypto.Cipher; + +/** + * Owns the four AES contexts (GKEY encrypt/decrypt, connection encrypt/decrypt) + * and the RSA private-key context. + * + *

GKEY contexts are persistent and intentionally keep their CBC chaining + * state between commands. Connection contexts are re-initialised for every + * command and therefore behave as independent CBC operations.

+ */ +public class Crypt { + + private final AESKey m_ee_aesKeyEnc; + private final AESKey m_ee_aesKeyDec; + private final RSAPrivateKey m_ee_rsaKey; + + private final Cipher m_ee_cipherAesEnc; + private final Cipher m_ee_cipherAesDec; + private final Cipher m_ee_cipherRsa; + + private final AESKey m_ee_aesConnectionKeyEnc; + private final AESKey m_ee_aesConnectionKeyDec; + private final Cipher m_ee_cipherConnectionAesEnc; + private final Cipher m_ee_cipherConnectionAesDec; + + public Crypt() { + m_ee_aesKeyEnc = (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES, + KeyBuilder.LENGTH_AES_128, false); + m_ee_aesKeyDec = (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES, + KeyBuilder.LENGTH_AES_128, false); + m_ee_rsaKey = (RSAPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_RSA_PRIVATE, + KeyBuilder.LENGTH_RSA_2048, false); + + m_ee_cipherAesEnc = Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_CBC_NOPAD, false); + m_ee_cipherAesDec = Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_CBC_NOPAD, false); + m_ee_cipherRsa = Cipher.getInstance(Cipher.ALG_RSA_PKCS1, false); + + m_ee_aesConnectionKeyEnc = (AESKey) KeyBuilder.buildKey( + KeyBuilder.TYPE_AES_TRANSIENT_RESET, KeyBuilder.LENGTH_AES_128, false); + m_ee_aesConnectionKeyDec = (AESKey) KeyBuilder.buildKey( + KeyBuilder.TYPE_AES_TRANSIENT_RESET, KeyBuilder.LENGTH_AES_128, false); + m_ee_cipherConnectionAesEnc = Cipher.getInstance( + Cipher.ALG_AES_BLOCK_128_CBC_NOPAD, false); + m_ee_cipherConnectionAesDec = Cipher.getInstance( + Cipher.ALG_AES_BLOCK_128_CBC_NOPAD, false); + } + + public boolean init(byte[] data1, short data1Offset, short data1Length, + byte[] data2, short data2Offset, short data2Length, + short mode) { + if (data1 == null || data2 == null) { + return false; + } + if (data1Offset < 0 || data1Length < 0 || data1Offset > data1.length + || data1Length > (short) (data1.length - data1Offset)) { + return false; + } + if (data2Offset < 0 || data2Length < 0 || data2Offset > data2.length + || data2Length > (short) (data2.length - data2Offset)) { + return false; + } + + try { + if (mode == AppConfig.CRYPT_GKEY_ENCRYPT) { + m_ee_aesKeyEnc.setKey(data1, data1Offset); + m_ee_cipherAesEnc.init(m_ee_aesKeyEnc, Cipher.MODE_ENCRYPT, + data2, data2Offset, data2Length); + } else if (mode == AppConfig.CRYPT_GKEY_DECRYPT) { + m_ee_aesKeyDec.setKey(data1, data1Offset); + m_ee_cipherAesDec.init(m_ee_aesKeyDec, Cipher.MODE_DECRYPT, + data2, data2Offset, data2Length); + } else if (mode == AppConfig.CRYPT_CONN_ENCRYPT) { + m_ee_aesConnectionKeyEnc.setKey(data1, data1Offset); + m_ee_cipherConnectionAesEnc.init(m_ee_aesConnectionKeyEnc, + Cipher.MODE_ENCRYPT, data2, data2Offset, data2Length); + } else if (mode == AppConfig.CRYPT_CONN_DECRYPT) { + m_ee_aesConnectionKeyDec.setKey(data1, data1Offset); + m_ee_cipherConnectionAesDec.init(m_ee_aesConnectionKeyDec, + Cipher.MODE_DECRYPT, data2, data2Offset, data2Length); + } else if (mode == AppConfig.CRYPT_RSA_DECRYPT) { + m_ee_rsaKey.setModulus(data1, data1Offset, data1Length); + m_ee_rsaKey.setExponent(data2, data2Offset, data2Length); + m_ee_cipherRsa.init(m_ee_rsaKey, Cipher.MODE_DECRYPT); + } else { + return false; + } + } catch (CryptoException e) { + return false; + } + return true; + } + + public short update(byte[] src, short srcOffset, short srcLength, + byte[] dst, short dstOffset, short mode) { + if (src == null || srcOffset < 0 || srcLength < 0 || srcOffset > src.length + || srcLength > (short) (src.length - srcOffset)) { + return 0; + } + if (mode == AppConfig.CRYPT_GKEY_ENCRYPT) { + return m_ee_cipherAesEnc.update(src, srcOffset, srcLength, dst, dstOffset); + } else if (mode == AppConfig.CRYPT_GKEY_DECRYPT) { + return m_ee_cipherAesDec.update(src, srcOffset, srcLength, dst, dstOffset); + } else if (mode == AppConfig.CRYPT_CONN_ENCRYPT) { + return m_ee_cipherConnectionAesEnc.update(src, srcOffset, srcLength, dst, dstOffset); + } else if (mode == AppConfig.CRYPT_CONN_DECRYPT) { + return m_ee_cipherConnectionAesDec.update(src, srcOffset, srcLength, dst, dstOffset); + } else if (mode == AppConfig.CRYPT_RSA_DECRYPT) { + return m_ee_cipherRsa.update(src, srcOffset, srcLength, dst, dstOffset); + } + return 0; + } + + public short doFinal(byte[] src, short srcOffset, short srcLength, + byte[] dst, short dstOffset, short mode) { + if (src == null || srcOffset < 0 || srcLength < 0 || srcOffset > src.length + || srcLength > (short) (src.length - srcOffset)) { + return 0; + } + if (mode == AppConfig.CRYPT_GKEY_ENCRYPT) { + return m_ee_cipherAesEnc.doFinal(src, srcOffset, srcLength, dst, dstOffset); + } else if (mode == AppConfig.CRYPT_GKEY_DECRYPT) { + return m_ee_cipherAesDec.doFinal(src, srcOffset, srcLength, dst, dstOffset); + } else if (mode == AppConfig.CRYPT_CONN_ENCRYPT) { + return m_ee_cipherConnectionAesEnc.doFinal(src, srcOffset, srcLength, dst, dstOffset); + } else if (mode == AppConfig.CRYPT_CONN_DECRYPT) { + return m_ee_cipherConnectionAesDec.doFinal(src, srcOffset, srcLength, dst, dstOffset); + } else if (mode == AppConfig.CRYPT_RSA_DECRYPT) { + return m_ee_cipherRsa.doFinal(src, srcOffset, srcLength, dst, dstOffset); + } + return 0; + } +} diff --git a/src/sega/a7/EepInit.java b/src/sega/a7/EepInit.java new file mode 100644 index 0000000..1d0fe85 --- /dev/null +++ b/src/sega/a7/EepInit.java @@ -0,0 +1,45 @@ +package sega.a7; + +import javacard.framework.Util; +import javacard.security.MessageDigest; + +public class EepInit extends AuthCommand { + + public EepInit(short cmd, AppData data, MakePacket packet, MessageDigest sha1, + HmacSha1 hmacSha1, Crypt crypt) { + super(cmd, (short) 46, (short) 46, AppConfig.LEVEL_AUTH, + data, packet, sha1, hmacSha1, crypt); + } + + public short core(byte[] data, short dataOffset, short dataLength) { + if (m_ee_data.getSflag_ee()) { + return AppConfig.EEPWRITE_DISABLE; + } + + m_ee_data.reset(); + + byte[] modulus = m_ee_data.getRsaDefaultPriModulus_ee(); + byte[] exponent = m_ee_data.getRsaDefaultPriExponent_ee(); + + if (!m_ee_data.setRsaPriKeyExponent_ee(exponent, (short) 0, + (short) exponent.length)) { + return AppConfig.EEP_WRITEFAIL; + } + if (!m_ee_data.setRsaPriKeyModulus_ee(modulus, (short) 0, + (short) modulus.length)) { + return AppConfig.EEP_WRITEFAIL; + } + if (!changeRsaKey_ee(modulus, (short) 0, (short) modulus.length, + exponent, (short) 0, (short) exponent.length)) { + return AppConfig.FAIL; + } + + byte[] tmp = m_ee_data.getCryptBuffer_ramD(); + Util.arrayFillNonAtomic(tmp, (short) 0, (short) tmp.length, (byte) 0xFF); + if (!changeGKey_ee(tmp, (short) 0, (short) tmp.length, + tmp, (short) 0, (short) tmp.length)) { + return AppConfig.FAIL; + } + return AppConfig.SUCCESS; + } +} diff --git a/src/sega/a7/ErrorGet.java b/src/sega/a7/ErrorGet.java new file mode 100644 index 0000000..87cc9b6 --- /dev/null +++ b/src/sega/a7/ErrorGet.java @@ -0,0 +1,18 @@ +package sega.a7; + +import javacard.framework.Util; +import javacard.security.MessageDigest; + +public class ErrorGet extends Command { + + public ErrorGet(short cmd, AppData data, MakePacket packet, MessageDigest sha1) { + super(cmd, (short) 26, (short) 28, data, packet, sha1); + } + + public short core(byte[] data, short dataOffset, short dataLength) { + Util.setShort(m_ramD_outData, (short) 0, m_ee_data.getLastError_ramD()); + m_ramD_outDataInfo[0] = 0; + m_ramD_outDataInfo[1] = 2; + return AppConfig.SUCCESS; + } +} diff --git a/src/sega/a7/GkeyDec.java b/src/sega/a7/GkeyDec.java new file mode 100644 index 0000000..3852ebb --- /dev/null +++ b/src/sega/a7/GkeyDec.java @@ -0,0 +1,43 @@ +package sega.a7; + +import javacard.framework.Util; +import javacard.security.MessageDigest; + +public class GkeyDec extends AuthCommand { + + public GkeyDec(short cmd, AppData data, MakePacket packet, MessageDigest sha1, + HmacSha1 hmacSha1, Crypt crypt) { + super(cmd, (short) 63, (short) 62, AppConfig.LEVEL_AUTH, + data, packet, sha1, hmacSha1, crypt); + } + + public short core(byte[] data, short dataOffset, short dataLength) { + byte resetFlag = data[(short) (dataOffset + AppConfig.HEADER_SIZE)]; + byte[] buffer = m_ee_data.getGeneralBuffer_ramD(); + + short decSize = decData(data, (short) (dataOffset + AppConfig.HEADER_SIZE + 1), + AppConfig.AES_BLOCK_SIZE, buffer, (short) 0); + if (decSize != AppConfig.AES_BLOCK_SIZE) { + return AppConfig.FAIL; + } + + boolean bInit = (resetFlag == AppConfig.SESSION_ENABLE); + short gkeySize = decUpdate_Gkey(buffer, (short) 0, AppConfig.AES_BLOCK_SIZE, + buffer, AppConfig.AES_BLOCK_SIZE, bInit); + if (gkeySize != AppConfig.AES_BLOCK_SIZE) { + return AppConfig.FAIL; + } + + short encSize = encData(buffer, AppConfig.AES_BLOCK_SIZE, AppConfig.AES_BLOCK_SIZE, + buffer, (short) 32); + if (encSize != AppConfig.AES_BLOCK_SIZE) { + return AppConfig.FAIL; + } + + m_ramD_outDataInfo[0] = 32; + m_ramD_outDataInfo[1] = AppConfig.AES_BLOCK_SIZE; + Util.arrayCopyNonAtomic(buffer, (short) 32, m_ramD_outData, (short) 32, + AppConfig.AES_BLOCK_SIZE); + return AppConfig.SUCCESS; + } +} diff --git a/src/sega/a7/GkeyEnc.java b/src/sega/a7/GkeyEnc.java new file mode 100644 index 0000000..b212f59 --- /dev/null +++ b/src/sega/a7/GkeyEnc.java @@ -0,0 +1,43 @@ +package sega.a7; + +import javacard.framework.Util; +import javacard.security.MessageDigest; + +public class GkeyEnc extends AuthCommand { + + public GkeyEnc(short cmd, AppData data, MakePacket packet, MessageDigest sha1, + HmacSha1 hmacSha1, Crypt crypt) { + super(cmd, (short) 63, (short) 62, AppConfig.LEVEL_AUTH, + data, packet, sha1, hmacSha1, crypt); + } + + public short core(byte[] data, short dataOffset, short dataLength) { + byte resetFlag = data[(short) (dataOffset + AppConfig.HEADER_SIZE)]; + byte[] buffer = m_ee_data.getGeneralBuffer_ramD(); + + short decSize = decData(data, (short) (dataOffset + AppConfig.HEADER_SIZE + 1), + AppConfig.AES_BLOCK_SIZE, buffer, (short) 0); + if (decSize != AppConfig.AES_BLOCK_SIZE) { + return AppConfig.FAIL; + } + + boolean bInit = (resetFlag == AppConfig.SESSION_ENABLE); + short gkeySize = encUpdate_GKey(buffer, (short) 0, AppConfig.AES_BLOCK_SIZE, + buffer, AppConfig.AES_BLOCK_SIZE, bInit); + if (gkeySize != AppConfig.AES_BLOCK_SIZE) { + return AppConfig.FAIL; + } + + short encSize = encData(buffer, AppConfig.AES_BLOCK_SIZE, AppConfig.AES_BLOCK_SIZE, + buffer, (short) 32); + if (encSize != AppConfig.AES_BLOCK_SIZE) { + return AppConfig.FAIL; + } + + m_ramD_outDataInfo[0] = 32; + m_ramD_outDataInfo[1] = AppConfig.AES_BLOCK_SIZE; + Util.arrayCopyNonAtomic(buffer, (short) 32, m_ramD_outData, (short) 32, + AppConfig.AES_BLOCK_SIZE); + return AppConfig.SUCCESS; + } +} diff --git a/src/sega/a7/GkeyWrite.java b/src/sega/a7/GkeyWrite.java new file mode 100644 index 0000000..53257ac --- /dev/null +++ b/src/sega/a7/GkeyWrite.java @@ -0,0 +1,31 @@ +package sega.a7; + +import javacard.framework.Util; +import javacard.security.MessageDigest; + +public class GkeyWrite extends AuthCommand { + + public GkeyWrite(short cmd, AppData data, MakePacket packet, MessageDigest sha1, + HmacSha1 hmacSha1, Crypt crypt) { + super(cmd, (short) 78, (short) 46, AppConfig.LEVEL_AUTH, + data, packet, sha1, hmacSha1, crypt); + } + + public short core(byte[] data, short dataOffset, short dataLength) { + if (m_ee_data.getSflag_ee()) { + return AppConfig.EEPWRITE_DISABLE; + } + byte[] buffer = m_ee_data.getGeneralBuffer_ramD(); + short decSize = decData(data, (short) (dataOffset + AppConfig.HEADER_SIZE), + (short) (AppConfig.AES_KEY_SIZE + AppConfig.AES_IV_SIZE), + buffer, (short) 0); + if (decSize != (short) (AppConfig.AES_KEY_SIZE + AppConfig.AES_IV_SIZE)) { + return AppConfig.FAIL; + } + if (!changeGKey_ee(buffer, (short) 0, AppConfig.AES_KEY_SIZE, + buffer, AppConfig.AES_KEY_SIZE, AppConfig.AES_IV_SIZE)) { + return AppConfig.EEP_WRITEFAIL; + } + return AppConfig.SUCCESS; + } +} diff --git a/src/sega/a7/HmacChange.java b/src/sega/a7/HmacChange.java new file mode 100644 index 0000000..e591bce --- /dev/null +++ b/src/sega/a7/HmacChange.java @@ -0,0 +1,29 @@ +package sega.a7; + +import javacard.security.MessageDigest; + +/** + * Replaces the HMAC key. The success response is already protected with the + * new key, so the host must switch keys before verifying it. + */ +public class HmacChange extends AuthCommand { + + public HmacChange(short cmd, AppData data, MakePacket packet, MessageDigest sha1, + HmacSha1 hmacSha1, Crypt crypt) { + super(cmd, (short) 78, (short) 46, AppConfig.LEVEL_AUTH, + data, packet, sha1, hmacSha1, crypt); + } + + public short core(byte[] data, short dataOffset, short dataLength) { + byte[] buffer = m_ee_data.getGeneralBuffer_ramD(); + short decSize = decData(data, (short) (dataOffset + AppConfig.HEADER_SIZE), + (short) (AppConfig.HMAC_SIZE + 12), buffer, (short) 0); + if (decSize < AppConfig.HMAC_SIZE) { + return AppConfig.FAIL; + } + if (!m_ee_data.setKh_ramD(buffer, (short) 0, AppConfig.HMAC_SIZE)) { + return AppConfig.FAIL; + } + return AppConfig.SUCCESS; + } +} diff --git a/src/sega/a7/HmacSha1.java b/src/sega/a7/HmacSha1.java new file mode 100644 index 0000000..0b35da4 --- /dev/null +++ b/src/sega/a7/HmacSha1.java @@ -0,0 +1,95 @@ +package sega.a7; + +import javacard.framework.JCSystem; +import javacard.framework.Util; +import javacard.security.MessageDigest; + +/** + * HMAC-SHA1 (RFC 2104). Keys longer than the 64-byte block are hashed first. + */ +public class HmacSha1 { + + private static final short BLOCK_SIZE = 64; + private static final byte IPAD = 0x36; + private static final byte OPAD = 0x5C; + + private final byte[] m_ramD_key; + private final byte[] m_ramD_padBuffer; + private final MessageDigest m_ee_sha1; + + public HmacSha1() { + m_ee_sha1 = MessageDigest.getInstance(MessageDigest.ALG_SHA, false); + m_ramD_key = JCSystem.makeTransientByteArray(BLOCK_SIZE, JCSystem.CLEAR_ON_RESET); + m_ramD_padBuffer = JCSystem.makeTransientByteArray(BLOCK_SIZE, JCSystem.CLEAR_ON_RESET); + } + + public boolean init(byte[] key, short offset, short length) { + if (key == null || length <= 0) { + return false; + } + if (offset < 0 || offset > key.length || length > (short) (key.length - offset)) { + return false; + } + + short effectiveLength; + if (length > BLOCK_SIZE) { + m_ee_sha1.reset(); + m_ee_sha1.doFinal(key, offset, length, m_ramD_key, (short) 0); + effectiveLength = AppConfig.SHA1_SIZE; + } else { + Util.arrayCopyNonAtomic(key, offset, m_ramD_key, (short) 0, length); + effectiveLength = length; + } + + if (effectiveLength < BLOCK_SIZE) { + Util.arrayFillNonAtomic(m_ramD_key, effectiveLength, + (short) (BLOCK_SIZE - effectiveLength), (byte) 0); + } + + for (short i = 0; i < BLOCK_SIZE; i++) { + m_ramD_padBuffer[i] = (byte) (m_ramD_key[i] ^ IPAD); + } + + m_ee_sha1.reset(); + m_ee_sha1.update(m_ramD_padBuffer, (short) 0, BLOCK_SIZE); + return true; + } + + public boolean update(byte[] inBuff, short inOffset, short inLength) { + if (inBuff == null || inLength < 0) { + return false; + } + if (inOffset < 0 || inOffset > inBuff.length + || inLength > (short) (inBuff.length - inOffset)) { + return false; + } + if (inLength != 0) { + m_ee_sha1.update(inBuff, inOffset, inLength); + } + return true; + } + + public boolean sign(byte[] inBuff, short inOffset, short inLength, + byte[] sigBuff, short sigOffset) { + if (inBuff == null || inLength < 0 || sigBuff == null) { + return false; + } + if (inOffset < 0 || inOffset > inBuff.length + || inLength > (short) (inBuff.length - inOffset)) { + return false; + } + if (sigOffset < 0 || sigOffset > sigBuff.length + || AppConfig.SHA1_SIZE > (short) (sigBuff.length - sigOffset)) { + return false; + } + + m_ee_sha1.doFinal(inBuff, inOffset, inLength, sigBuff, sigOffset); + + for (short i = 0; i < BLOCK_SIZE; i++) { + m_ramD_padBuffer[i] = (byte) (m_ramD_key[i] ^ OPAD); + } + m_ee_sha1.update(m_ramD_padBuffer, (short) 0, BLOCK_SIZE); + m_ee_sha1.doFinal(sigBuff, sigOffset, AppConfig.SHA1_SIZE, sigBuff, sigOffset); + return true; + } +} diff --git a/src/sega/a7/KeyChange.java b/src/sega/a7/KeyChange.java new file mode 100644 index 0000000..26283ed --- /dev/null +++ b/src/sega/a7/KeyChange.java @@ -0,0 +1,31 @@ +package sega.a7; + +import javacard.security.MessageDigest; + +/** + * Replaces the connection AES key and IV. The response is not encrypted, but + * all following commands use the new connection key. + */ +public class KeyChange extends AuthCommand { + + public KeyChange(short cmd, AppData data, MakePacket packet, MessageDigest sha1, + HmacSha1 hmacSha1, Crypt crypt) { + super(cmd, (short) 78, (short) 46, AppConfig.LEVEL_AUTH, + data, packet, sha1, hmacSha1, crypt); + } + + public short core(byte[] data, short dataOffset, short dataLength) { + byte[] buffer = m_ee_data.getGeneralBuffer_ramD(); + short decSize = decData(data, (short) (dataOffset + AppConfig.HEADER_SIZE), + (short) (AppConfig.AES_KEY_SIZE + AppConfig.AES_IV_SIZE), + buffer, (short) 0); + if (decSize != (short) (AppConfig.AES_KEY_SIZE + AppConfig.AES_IV_SIZE)) { + return AppConfig.FAIL; + } + if (!changeConnectionKey_ee(buffer, (short) 0, AppConfig.AES_KEY_SIZE, + buffer, AppConfig.AES_KEY_SIZE, AppConfig.AES_IV_SIZE)) { + return AppConfig.FAIL; + } + return AppConfig.SUCCESS; + } +} diff --git a/src/sega/a7/KeychipInfoRead.java b/src/sega/a7/KeychipInfoRead.java new file mode 100644 index 0000000..8951668 --- /dev/null +++ b/src/sega/a7/KeychipInfoRead.java @@ -0,0 +1,27 @@ +package sega.a7; + +import javacard.framework.Util; +import javacard.security.MessageDigest; + +public class KeychipInfoRead extends AuthCommand { + + public KeychipInfoRead(short cmd, AppData data, MakePacket packet, MessageDigest sha1, + HmacSha1 hmacSha1, Crypt crypt) { + super(cmd, (short) 46, (short) 142, AppConfig.LEVEL_AUTH, + data, packet, sha1, hmacSha1, crypt); + } + + public short core(byte[] data, short dataOffset, short dataLength) { + byte[] info = m_ee_data.getKeychipInfo_ee(); + byte[] buffer = m_ee_data.getGeneralBuffer_ramD(); + short encSize = encData(info, (short) 0, (short) info.length, + buffer, (short) 0); + if (encSize != (short) info.length) { + return AppConfig.FAIL; + } + m_ramD_outDataInfo[0] = 0; + m_ramD_outDataInfo[1] = encSize; + Util.arrayCopyNonAtomic(buffer, (short) 0, m_ramD_outData, (short) 0, encSize); + return AppConfig.SUCCESS; + } +} diff --git a/src/sega/a7/KeychipInfoWrite.java b/src/sega/a7/KeychipInfoWrite.java new file mode 100644 index 0000000..d594fac --- /dev/null +++ b/src/sega/a7/KeychipInfoWrite.java @@ -0,0 +1,30 @@ +package sega.a7; + +import javacard.framework.Util; +import javacard.security.MessageDigest; + +public class KeychipInfoWrite extends AuthCommand { + + public KeychipInfoWrite(short cmd, AppData data, MakePacket packet, MessageDigest sha1, + HmacSha1 hmacSha1, Crypt crypt) { + super(cmd, (short) 142, (short) 46, AppConfig.LEVEL_AUTH, + data, packet, sha1, hmacSha1, crypt); + } + + public short core(byte[] data, short dataOffset, short dataLength) { + if (m_ee_data.getSflag_ee()) { + return AppConfig.EEPWRITE_DISABLE; + } + byte[] buffer = m_ee_data.getGeneralBuffer_ramD(); + short decSize = decData(data, (short) (dataOffset + AppConfig.HEADER_SIZE), + AppConfig.KEYCHIP_INFO_SIZE, buffer, (short) 0); + if (decSize != AppConfig.KEYCHIP_INFO_SIZE) { + return AppConfig.FAIL; + } + if (!m_ee_data.setKeychipInfo_ee(buffer, (short) 0, + AppConfig.KEYCHIP_INFO_SIZE)) { + return AppConfig.EEP_WRITEFAIL; + } + return AppConfig.SUCCESS; + } +} diff --git a/src/sega/a7/LvEnable.java b/src/sega/a7/LvEnable.java new file mode 100644 index 0000000..c84dd82 --- /dev/null +++ b/src/sega/a7/LvEnable.java @@ -0,0 +1,25 @@ +package sega.a7; + +import javacard.security.MessageDigest; + +/** + * Recovers from the persistent block flag until the next card reset. + * The block bit itself is intentionally preserved. + */ +public class LvEnable extends Command { + + public LvEnable(short cmd, AppData data, MakePacket packet, MessageDigest sha1) { + super(cmd, (short) 26, (short) 26, data, packet, sha1); + } + + public short core(byte[] data, short dataOffset, short dataLength) { + if (m_ee_data.getLevel_ramD() != AppConfig.LEVEL_BLOCKED) { + return AppConfig.LV_ERROR; + } + if (m_ee_data.getSflag_ee()) { + return AppConfig.EEPWRITE_DISABLE; + } + m_ee_data.setLevel_ramD(AppConfig.LEVEL_INITIAL); + return AppConfig.SUCCESS; + } +} diff --git a/src/sega/a7/LvGet.java b/src/sega/a7/LvGet.java new file mode 100644 index 0000000..6827cc1 --- /dev/null +++ b/src/sega/a7/LvGet.java @@ -0,0 +1,18 @@ +package sega.a7; + +import javacard.framework.Util; +import javacard.security.MessageDigest; + +public class LvGet extends Command { + + public LvGet(short cmd, AppData data, MakePacket packet, MessageDigest sha1) { + super(cmd, (short) 26, (short) 27, data, packet, sha1); + } + + public short core(byte[] data, short dataOffset, short dataLength) { + m_ramD_outData[0] = m_ee_data.getLevel_ramD(); + m_ramD_outDataInfo[0] = 0; + m_ramD_outDataInfo[1] = 1; + return AppConfig.SUCCESS; + } +} diff --git a/src/sega/a7/MakePacket.java b/src/sega/a7/MakePacket.java new file mode 100644 index 0000000..702878a --- /dev/null +++ b/src/sega/a7/MakePacket.java @@ -0,0 +1,115 @@ +package sega.a7; + +import javacard.framework.JCSystem; +import javacard.framework.Util; +import javacard.security.MessageDigest; + +/** + * Serialises responses. The SHA-1/HMAC input includes the logical command + * number even though the wire header carries the result code at offset 4. + */ +public class MakePacket { + + private final byte[] m_ramD_buffer; + + public MakePacket() { + m_ramD_buffer = JCSystem.makeTransientByteArray(AppConfig.SHA1_SIZE, + JCSystem.CLEAR_ON_RESET); + } + + public boolean setData(byte[] kh, short khOffset, short khLength, + short tag, short paramSize, short result, short cmd, + byte[] data, short dataOffset, short dataLength, + byte[] nonceEvent, short eventOffset, short eventLength, + byte[] nonceOdd, short oddOffset, short oddLength, + byte[] packet, short packetOffset, short packetLength, + MessageDigest sha1, HmacSha1 hmacSha1) { + + if (packet == null || sha1 == null || packetOffset < 0 || packetLength < 0) { + return false; + } + if (packetOffset > packet.length || packetLength > (short) (packet.length - packetOffset)) { + return false; + } + + boolean normal = (tag == AppConfig.TAG_RSP_COMMAND); + boolean auth = (tag == AppConfig.TAG_RSP_AUTH_COMMAND); + boolean rsaAuth = (tag == AppConfig.TAG_RSP_RSA_COMMAND); + if (!normal && !auth && !rsaAuth) { + return false; + } + if (!normal) { + if (kh == null || nonceEvent == null || nonceOdd == null || hmacSha1 == null) { + return false; + } + if (khOffset < 0 || khLength <= 0 || khOffset > kh.length + || khLength > (short) (kh.length - khOffset)) { + return false; + } + if (eventOffset < 0 || eventLength <= 0 || eventOffset > nonceEvent.length + || eventLength > (short) (nonceEvent.length - eventOffset)) { + return false; + } + if (oddOffset < 0 || oddLength <= 0 || oddOffset > nonceOdd.length + || oddLength > (short) (nonceOdd.length - oddOffset)) { + return false; + } + } + + short payloadLength = 0; + if (data != null) { + if (dataOffset < 0 || dataLength < 0 || dataOffset > data.length + || dataLength > (short) (data.length - dataOffset)) { + return false; + } + payloadLength = dataLength; + } + + short footerLength = normal ? AppConfig.SHA1_SIZE : AppConfig.AUTH_FOOTER_SIZE; + short expectedSize = (short) (AppConfig.HEADER_SIZE + payloadLength + footerLength); + if (paramSize != expectedSize || paramSize > packetLength) { + return false; + } + + short offset = packetOffset; + + sha1.reset(); + Util.setShort(packet, offset, tag); + offset += 2; + Util.setShort(packet, offset, paramSize); + offset += 2; + Util.setShort(packet, offset, result); + offset += 2; + + Util.setShort(m_ramD_buffer, (short) 0, cmd); + sha1.update(packet, packetOffset, AppConfig.HEADER_SIZE); + + if (data == null) { + sha1.doFinal(m_ramD_buffer, (short) 0, (short) 2, packet, offset); + } else { + sha1.update(m_ramD_buffer, (short) 0, (short) 2); + if (dataLength != 0) { + Util.arrayCopyNonAtomic(data, dataOffset, packet, offset, dataLength); + } + offset = (short) (offset + dataLength); + sha1.doFinal(data, dataOffset, dataLength, packet, offset); + } + + if (normal) { + return true; + } + + Util.arrayCopyNonAtomic(packet, offset, m_ramD_buffer, (short) 0, + AppConfig.SHA1_SIZE); + Util.arrayCopyNonAtomic(nonceEvent, eventOffset, packet, offset, eventLength); + offset = (short) (offset + eventLength); + + if (!hmacSha1.init(kh, khOffset, khLength) + || !hmacSha1.update(m_ramD_buffer, (short) 0, AppConfig.SHA1_SIZE) + || !hmacSha1.update(nonceEvent, eventOffset, eventLength) + || !hmacSha1.sign(nonceOdd, oddOffset, oddLength, packet, offset)) { + return false; + } + return true; + } +} diff --git a/src/sega/a7/NoCommand.java b/src/sega/a7/NoCommand.java new file mode 100644 index 0000000..b4519a2 --- /dev/null +++ b/src/sega/a7/NoCommand.java @@ -0,0 +1,32 @@ +package sega.a7; + +import javacard.framework.Util; +import javacard.security.MessageDigest; + +/** + * Fallback for unknown ordinals. Mirrors the original behaviour: frames shorter + * than the six-byte header return BAD_PARAMETER, everything else BAD_ORDINAL. + */ +public class NoCommand extends Command { + + public NoCommand(short cmd, AppData data, MakePacket packet, MessageDigest sha1) { + super(cmd, (short) 0, AppConfig.ERROR_PACKET_SIZE, data, packet, sha1); + } + + public short execute(byte[] data, short dataOffset, short dataLength, + byte[] dst, short dstOffset, short dstLength) { + short cmd = -1; + if (data != null && dataOffset >= 0 && dataLength >= AppConfig.HEADER_SIZE + && dataOffset <= (short) (data.length - AppConfig.HEADER_SIZE)) { + cmd = Util.getShort(data, + (short) (dataOffset + AppConfig.CMD_OFFSET)); + } + + if (dataLength < AppConfig.HEADER_SIZE) { + return makeErrorPacket(AppConfig.BAD_PARAMETER, cmd, + dst, dstOffset, dstLength); + } + return makeErrorPacket(AppConfig.BAD_ORDINAL, cmd, + dst, dstOffset, dstLength); + } +} diff --git a/src/sega/a7/PlayCountIncrement.java b/src/sega/a7/PlayCountIncrement.java new file mode 100644 index 0000000..ae3fe4e --- /dev/null +++ b/src/sega/a7/PlayCountIncrement.java @@ -0,0 +1,25 @@ +package sega.a7; + +import javacard.security.MessageDigest; + +/** + * Adds one byte to the persistent 32-bit play counter. + * Payload: increment value (1 byte). + */ +public class PlayCountIncrement extends AuthCommand { + + public PlayCountIncrement(short cmd, AppData data, MakePacket packet, + MessageDigest sha1, HmacSha1 hmacSha1, Crypt crypt) { + super(cmd, (short) 47, (short) 46, AppConfig.LEVEL_AUTH, + data, packet, sha1, hmacSha1, crypt); + } + + public short core(byte[] data, short dataOffset, short dataLength) { + if (dataLength < (short) (AppConfig.HEADER_SIZE + 1)) { + return AppConfig.BAD_DATASIZE; + } + m_ee_data.addValuePlayCounter_ee( + data[(short) (dataOffset + AppConfig.HEADER_SIZE)]); + return AppConfig.SUCCESS; + } +} diff --git a/src/sega/a7/PlayCountRead.java b/src/sega/a7/PlayCountRead.java new file mode 100644 index 0000000..424d8d1 --- /dev/null +++ b/src/sega/a7/PlayCountRead.java @@ -0,0 +1,46 @@ +package sega.a7; + +import javacard.framework.Util; +import javacard.security.MessageDigest; +import javacard.security.RandomData; + +/** + * Returns the persistent play counter inside an encrypted 16-byte block: + * random(16) with the first four bytes replaced by the counter. + */ +public class PlayCountRead extends AuthCommand { + + private final RandomData m_ee_rand; + + public PlayCountRead(short cmd, AppData data, MakePacket packet, MessageDigest sha1, + HmacSha1 hmacSha1, Crypt crypt, RandomData rand) { + super(cmd, (short) 46, (short) 62, AppConfig.LEVEL_AUTH, + data, packet, sha1, hmacSha1, crypt); + m_ee_rand = rand; + } + + public short core(byte[] data, short dataOffset, short dataLength) { + byte[] buffer = m_ee_data.getGeneralBuffer_ramD(); + if (buffer.length < 32) { + return AppConfig.FAIL; + } + + m_ee_rand.generateData(buffer, (short) 0, AppConfig.AES_BLOCK_SIZE); + if (!m_ee_data.getPlayCounter_ee(buffer, (short) 0, + AppConfig.PLAY_COUNT_SIZE)) { + return AppConfig.FAIL; + } + + short encSize = encData(buffer, (short) 0, AppConfig.AES_BLOCK_SIZE, + buffer, AppConfig.AES_BLOCK_SIZE); + if (encSize != AppConfig.AES_BLOCK_SIZE) { + return AppConfig.FAIL; + } + + m_ramD_outDataInfo[0] = AppConfig.AES_BLOCK_SIZE; + m_ramD_outDataInfo[1] = encSize; + Util.arrayCopyNonAtomic(buffer, AppConfig.AES_BLOCK_SIZE, + m_ramD_outData, AppConfig.AES_BLOCK_SIZE, encSize); + return AppConfig.SUCCESS; + } +} diff --git a/src/sega/a7/PrikeyExponentWrite.java b/src/sega/a7/PrikeyExponentWrite.java new file mode 100644 index 0000000..30636fe --- /dev/null +++ b/src/sega/a7/PrikeyExponentWrite.java @@ -0,0 +1,50 @@ +package sega.a7; + +import javacard.security.MessageDigest; + +/** + * Replaces the RSA private exponent. Both the modulus and the exponent must be + * written before the new key becomes active; the update is staged in RAM. + */ +public class PrikeyExponentWrite extends AuthCommand { + + public PrikeyExponentWrite(short cmd, AppData data, MakePacket packet, + MessageDigest sha1, HmacSha1 hmacSha1, Crypt crypt) { + super(cmd, (short) 302, (short) 46, AppConfig.LEVEL_AUTH, + data, packet, sha1, hmacSha1, crypt); + } + + public short core(byte[] data, short dataOffset, short dataLength) { + if (m_ee_data.getSflag_ee()) { + return AppConfig.EEPWRITE_DISABLE; + } + + byte[] buffer = m_ee_data.getGeneralBuffer_ramD(); + if (buffer.length < AppConfig.RSA_KEY_SIZE) { + return AppConfig.FAIL; + } + + short decSize = decData(data, + (short) (dataOffset + AppConfig.HEADER_SIZE), + AppConfig.RSA_KEY_SIZE, buffer, (short) 0); + if (decSize < AppConfig.RSA_KEY_SIZE) { + return AppConfig.FAIL; + } + + if (!m_ee_data.setRsaPriKeyExponent_ee(buffer, (short) 0, + AppConfig.RSA_KEY_SIZE)) { + return AppConfig.EEP_WRITEFAIL; + } + m_ee_data.activeRsaExponentUpdate_ramD(); + + if (m_ee_data.getRsaModulusUpdate_ramD()) { + byte[] modulus = m_ee_data.getRsaPriModulus_ee(); + byte[] exponent = m_ee_data.getRsaPriExponent_ee(); + if (!changeRsaKey_ee(modulus, (short) 0, (short) modulus.length, + exponent, (short) 0, (short) exponent.length)) { + return AppConfig.FAIL; + } + } + return AppConfig.SUCCESS; + } +} diff --git a/src/sega/a7/PrikeyModulusWrite.java b/src/sega/a7/PrikeyModulusWrite.java new file mode 100644 index 0000000..ebe7353 --- /dev/null +++ b/src/sega/a7/PrikeyModulusWrite.java @@ -0,0 +1,50 @@ +package sega.a7; + +import javacard.security.MessageDigest; + +/** + * Replaces the RSA private modulus. Both the modulus and the exponent must be + * written before the new key becomes active; the update is staged in RAM. + */ +public class PrikeyModulusWrite extends AuthCommand { + + public PrikeyModulusWrite(short cmd, AppData data, MakePacket packet, + MessageDigest sha1, HmacSha1 hmacSha1, Crypt crypt) { + super(cmd, (short) 302, (short) 46, AppConfig.LEVEL_AUTH, + data, packet, sha1, hmacSha1, crypt); + } + + public short core(byte[] data, short dataOffset, short dataLength) { + if (m_ee_data.getSflag_ee()) { + return AppConfig.EEPWRITE_DISABLE; + } + + byte[] buffer = m_ee_data.getGeneralBuffer_ramD(); + if (buffer.length < AppConfig.RSA_KEY_SIZE) { + return AppConfig.FAIL; + } + + short decSize = decData(data, + (short) (dataOffset + AppConfig.HEADER_SIZE), + AppConfig.RSA_KEY_SIZE, buffer, (short) 0); + if (decSize < AppConfig.RSA_KEY_SIZE) { + return AppConfig.FAIL; + } + + if (!m_ee_data.setRsaPriKeyModulus_ee(buffer, (short) 0, + AppConfig.RSA_KEY_SIZE)) { + return AppConfig.EEP_WRITEFAIL; + } + m_ee_data.activeRsaModulusUpdate_ramD(); + + if (m_ee_data.getRsaExponentUpdate_ramD()) { + byte[] modulus = m_ee_data.getRsaPriModulus_ee(); + byte[] exponent = m_ee_data.getRsaPriExponent_ee(); + if (!changeRsaKey_ee(modulus, (short) 0, (short) modulus.length, + exponent, (short) 0, (short) exponent.length)) { + return AppConfig.FAIL; + } + } + return AppConfig.SUCCESS; + } +} diff --git a/src/sega/a7/RanGet.java b/src/sega/a7/RanGet.java new file mode 100644 index 0000000..dbb7bac --- /dev/null +++ b/src/sega/a7/RanGet.java @@ -0,0 +1,41 @@ +package sega.a7; + +import javacard.framework.Util; +import javacard.security.MessageDigest; +import javacard.security.RandomData; + +/** + * Returns 16 fresh random bytes, encrypted with the connection key. + * Payload: encrypted random block (16 bytes). + */ +public class RanGet extends AuthCommand { + + private final RandomData m_ee_rand; + + public RanGet(short cmd, AppData data, MakePacket packet, MessageDigest sha1, + HmacSha1 hmacSha1, Crypt crypt, RandomData rand) { + super(cmd, (short) 46, (short) 62, AppConfig.LEVEL_AUTH, + data, packet, sha1, hmacSha1, crypt); + m_ee_rand = rand; + } + + public short core(byte[] data, short dataOffset, short dataLength) { + byte[] buffer = m_ee_data.getGeneralBuffer_ramD(); + if (buffer.length < 32) { + return AppConfig.FAIL; + } + + m_ee_rand.generateData(buffer, (short) 0, AppConfig.AES_BLOCK_SIZE); + short encSize = encData(buffer, (short) 0, AppConfig.AES_BLOCK_SIZE, + buffer, AppConfig.AES_BLOCK_SIZE); + if (encSize != AppConfig.AES_BLOCK_SIZE) { + return AppConfig.FAIL; + } + + m_ramD_outDataInfo[0] = AppConfig.AES_BLOCK_SIZE; + m_ramD_outDataInfo[1] = encSize; + Util.arrayCopyNonAtomic(buffer, AppConfig.AES_BLOCK_SIZE, + m_ramD_outData, AppConfig.AES_BLOCK_SIZE, encSize); + return AppConfig.SUCCESS; + } +} diff --git a/src/sega/a7/SessionClose.java b/src/sega/a7/SessionClose.java new file mode 100644 index 0000000..3ef7242 --- /dev/null +++ b/src/sega/a7/SessionClose.java @@ -0,0 +1,15 @@ +package sega.a7; + +import javacard.security.MessageDigest; + +public class SessionClose extends Command { + + public SessionClose(short cmd, AppData data, MakePacket packet, MessageDigest sha1) { + super(cmd, (short) 26, (short) 26, data, packet, sha1); + } + + public short core(byte[] data, short dataOffset, short dataLength) { + m_ee_data.setSession_ramD(AppConfig.SESSION_DISABLE); + return AppConfig.SUCCESS; + } +} diff --git a/src/sega/a7/SessionOpen.java b/src/sega/a7/SessionOpen.java new file mode 100644 index 0000000..07e0bfb --- /dev/null +++ b/src/sega/a7/SessionOpen.java @@ -0,0 +1,25 @@ +package sega.a7; + +import javacard.framework.Util; +import javacard.security.MessageDigest; + +public class SessionOpen extends Command { + + public SessionOpen(short cmd, AppData data, MakePacket packet, MessageDigest sha1) { + super(cmd, (short) 26, (short) 46, data, packet, sha1); + } + + public short core(byte[] data, short dataOffset, short dataLength) { + byte[] nonce = m_ee_data.getNewNonceEvent_ramD(); + if (!m_ee_data.setLastNonceEvent_ramD(nonce, (short) 0, + (short) nonce.length)) { + return AppConfig.FAIL; + } + m_ee_data.setSession_ramD(AppConfig.SESSION_ENABLE); + m_ramD_outDataInfo[0] = 0; + m_ramD_outDataInfo[1] = AppConfig.NONCE_SIZE; + Util.arrayCopyNonAtomic(nonce, (short) 0, m_ramD_outData, (short) 0, + AppConfig.NONCE_SIZE); + return AppConfig.SUCCESS; + } +} diff --git a/src/sega/a7/SflagWrite.java b/src/sega/a7/SflagWrite.java new file mode 100644 index 0000000..e14842d --- /dev/null +++ b/src/sega/a7/SflagWrite.java @@ -0,0 +1,20 @@ +package sega.a7; + +import javacard.security.MessageDigest; + +public class SflagWrite extends AuthCommand { + + public SflagWrite(short cmd, AppData data, MakePacket packet, MessageDigest sha1, + HmacSha1 hmacSha1, Crypt crypt) { + super(cmd, (short) 46, (short) 46, AppConfig.LEVEL_AUTH, + data, packet, sha1, hmacSha1, crypt); + } + + public short core(byte[] data, short dataOffset, short dataLength) { + if (m_ee_data.getSflag_ee()) { + return AppConfig.EEPWRITE_DISABLE; + } + m_ee_data.activeSflag(); + return AppConfig.SUCCESS; + } +} diff --git a/src/sega/a7/StorageRead.java b/src/sega/a7/StorageRead.java new file mode 100644 index 0000000..9d93a36 --- /dev/null +++ b/src/sega/a7/StorageRead.java @@ -0,0 +1,58 @@ +package sega.a7; + +import javacard.framework.Util; +import javacard.security.MessageDigest; + +/** + * Reads up to 192 bytes from the persistent storage. + * + *

Payload: storageOffset(2) | readSize(2). The read size is rounded up to + * an AES block and the resulting ciphertext is returned as the payload.

+ */ +public class StorageRead extends AuthCommand { + + public StorageRead(short cmd, AppData data, MakePacket packet, MessageDigest sha1, + HmacSha1 hmacSha1, Crypt crypt) { + super(cmd, (short) 50, (short) 46, AppConfig.LEVEL_AUTH, + data, packet, sha1, hmacSha1, crypt); + } + + public short core(byte[] data, short dataOffset, short dataLength) { + short storageOffset = Util.getShort(data, + (short) (dataOffset + AppConfig.HEADER_SIZE)); + short readSize = Util.getShort(data, + (short) (dataOffset + AppConfig.HEADER_SIZE + 2)); + + if (readSize <= 0 || readSize > AppConfig.STORAGE_MAX_READ + || storageOffset < 0) { + return AppConfig.BAD_PARAMETER; + } + + short remainder = (short) (readSize % AppConfig.AES_BLOCK_SIZE); + if (remainder != 0) { + readSize = (short) (readSize + + (AppConfig.AES_BLOCK_SIZE - remainder)); + } + + if (storageOffset > (short) (AppConfig.STORAGE_SIZE - readSize)) { + return AppConfig.BAD_PARAMETER; + } + + byte[] buffer = m_ee_data.getGeneralBuffer_ramD(); + if (buffer.length < readSize) { + return AppConfig.BAD_PARAMETER; + } + + short encSize = encData(m_ee_data.getStorageData_ee(), storageOffset, + readSize, buffer, (short) 0); + if (encSize < readSize) { + return AppConfig.FAIL; + } + + m_ramD_outDataInfo[0] = 0; + m_ramD_outDataInfo[1] = encSize; + Util.arrayCopyNonAtomic(buffer, (short) 0, m_ramD_outData, + (short) 0, encSize); + return AppConfig.SUCCESS; + } +} diff --git a/src/sega/a7/StorageSizeGet.java b/src/sega/a7/StorageSizeGet.java new file mode 100644 index 0000000..cc323df --- /dev/null +++ b/src/sega/a7/StorageSizeGet.java @@ -0,0 +1,44 @@ +package sega.a7; + +import javacard.framework.Util; +import javacard.security.MessageDigest; +import javacard.security.RandomData; + +/** + * Returns the storage capacity (10240) inside an encrypted 16-byte block: + * random(16) with the first two bytes replaced by the big-endian size. + */ +public class StorageSizeGet extends AuthCommand { + + private final RandomData m_ee_rand; + + public StorageSizeGet(short cmd, AppData data, MakePacket packet, + MessageDigest sha1, HmacSha1 hmacSha1, Crypt crypt, + RandomData rand) { + super(cmd, (short) 46, (short) 62, AppConfig.LEVEL_AUTH, + data, packet, sha1, hmacSha1, crypt); + m_ee_rand = rand; + } + + public short core(byte[] data, short dataOffset, short dataLength) { + byte[] buffer = m_ee_data.getGeneralBuffer_ramD(); + if (buffer.length < 32) { + return AppConfig.FAIL; + } + + m_ee_rand.generateData(buffer, (short) 0, AppConfig.AES_BLOCK_SIZE); + Util.setShort(buffer, (short) 0, AppConfig.STORAGE_SIZE); + + short encSize = encData(buffer, (short) 0, AppConfig.AES_BLOCK_SIZE, + buffer, AppConfig.AES_BLOCK_SIZE); + if (encSize != AppConfig.AES_BLOCK_SIZE) { + return AppConfig.FAIL; + } + + m_ramD_outDataInfo[0] = AppConfig.AES_BLOCK_SIZE; + m_ramD_outDataInfo[1] = encSize; + Util.arrayCopyNonAtomic(buffer, AppConfig.AES_BLOCK_SIZE, + m_ramD_outData, AppConfig.AES_BLOCK_SIZE, encSize); + return AppConfig.SUCCESS; + } +} diff --git a/src/sega/a7/StorageWrite.java b/src/sega/a7/StorageWrite.java new file mode 100644 index 0000000..aa6395e --- /dev/null +++ b/src/sega/a7/StorageWrite.java @@ -0,0 +1,59 @@ +package sega.a7; + +import javacard.framework.Util; +import javacard.security.MessageDigest; + +/** + * Writes up to 256 bytes into the persistent 10 KiB storage. + * + *

Payload: writeSize(2) | storageOffset(2) | AES-CBC ciphertext. + * The ciphertext length is {@code frameSize - 50}; it must be a positive + * multiple of 16 and the plaintext must cover writeSize bytes.

+ */ +public class StorageWrite extends AuthCommand { + + private static final short PAYLOAD_SIZE = 4; + + public StorageWrite(short cmd, AppData data, MakePacket packet, MessageDigest sha1, + HmacSha1 hmacSha1, Crypt crypt) { + super(cmd, (short) 0, (short) 46, AppConfig.LEVEL_AUTH, + data, packet, sha1, hmacSha1, crypt); + } + + public short core(byte[] data, short dataOffset, short dataLength) { + short encDataSize = (short) (dataLength + - AppConfig.HEADER_SIZE - AppConfig.AUTH_FOOTER_SIZE - PAYLOAD_SIZE); + if (encDataSize <= 0 || encDataSize > AppConfig.STORAGE_MAX_WRITE + || (short) (encDataSize % AppConfig.AES_BLOCK_SIZE) != 0) { + return AppConfig.BAD_PARAMETER; + } + + byte[] buffer = m_ee_data.getGeneralBuffer_ramD(); + if (buffer.length < encDataSize) { + return AppConfig.BAD_PARAMETER; + } + + short writeSize = Util.getShort(data, + (short) (dataOffset + AppConfig.HEADER_SIZE)); + short storageOffset = Util.getShort(data, + (short) (dataOffset + AppConfig.HEADER_SIZE + 2)); + + if (writeSize <= 0 || storageOffset < 0 + || storageOffset > (short) (AppConfig.STORAGE_SIZE - writeSize)) { + return AppConfig.BAD_PARAMETER; + } + + short decSize = decData(data, + (short) (dataOffset + AppConfig.HEADER_SIZE + PAYLOAD_SIZE), + encDataSize, buffer, (short) 0); + if (decSize < writeSize) { + return AppConfig.FAIL; + } + + if (!m_ee_data.setStorageData_ee(buffer, (short) 0, writeSize, + storageOffset)) { + return AppConfig.EEP_WRITEFAIL; + } + return AppConfig.SUCCESS; + } +} diff --git a/src/sega/a7/TraceDataInfoRead.java b/src/sega/a7/TraceDataInfoRead.java new file mode 100644 index 0000000..45a2c62 --- /dev/null +++ b/src/sega/a7/TraceDataInfoRead.java @@ -0,0 +1,27 @@ +package sega.a7; + +import javacard.framework.Util; +import javacard.security.MessageDigest; + +public class TraceDataInfoRead extends AuthCommand { + + public TraceDataInfoRead(short cmd, AppData data, MakePacket packet, MessageDigest sha1, + HmacSha1 hmacSha1, Crypt crypt) { + super(cmd, (short) 46, (short) 62, AppConfig.LEVEL_AUTH, + data, packet, sha1, hmacSha1, crypt); + } + + public short core(byte[] data, short dataOffset, short dataLength) { + byte[] info = m_ee_data.getTraceDataInfo_ee(); + byte[] buffer = m_ee_data.getGeneralBuffer_ramD(); + short encSize = encData(info, (short) 0, AppConfig.TRACE_DATA_INFO_SIZE, + buffer, (short) 0); + if (encSize != AppConfig.TRACE_DATA_INFO_SIZE) { + return AppConfig.FAIL; + } + m_ramD_outDataInfo[0] = 0; + m_ramD_outDataInfo[1] = encSize; + Util.arrayCopyNonAtomic(buffer, (short) 0, m_ramD_outData, (short) 0, encSize); + return AppConfig.SUCCESS; + } +} diff --git a/src/sega/a7/TraceDataInfoWrite.java b/src/sega/a7/TraceDataInfoWrite.java new file mode 100644 index 0000000..e22dd15 --- /dev/null +++ b/src/sega/a7/TraceDataInfoWrite.java @@ -0,0 +1,26 @@ +package sega.a7; + +import javacard.security.MessageDigest; + +public class TraceDataInfoWrite extends AuthCommand { + + public TraceDataInfoWrite(short cmd, AppData data, MakePacket packet, MessageDigest sha1, + HmacSha1 hmacSha1, Crypt crypt) { + super(cmd, (short) 62, (short) 46, AppConfig.LEVEL_AUTH, + data, packet, sha1, hmacSha1, crypt); + } + + public short core(byte[] data, short dataOffset, short dataLength) { + byte[] buffer = m_ee_data.getGeneralBuffer_ramD(); + short decSize = decData(data, (short) (dataOffset + AppConfig.HEADER_SIZE), + AppConfig.TRACE_DATA_INFO_SIZE, buffer, (short) 0); + if (decSize != AppConfig.TRACE_DATA_INFO_SIZE) { + return AppConfig.FAIL; + } + if (!m_ee_data.setTraceDataInfo_ee(buffer, (short) 0, + AppConfig.TRACE_DATA_INFO_SIZE)) { + return AppConfig.EEP_WRITEFAIL; + } + return AppConfig.SUCCESS; + } +} diff --git a/src/sega/a7/UdataRead.java b/src/sega/a7/UdataRead.java new file mode 100644 index 0000000..7b6ddd2 --- /dev/null +++ b/src/sega/a7/UdataRead.java @@ -0,0 +1,26 @@ +package sega.a7; + +import javacard.framework.Util; +import javacard.security.MessageDigest; + +public class UdataRead extends AuthCommand { + + public UdataRead(short cmd, AppData data, MakePacket packet, MessageDigest sha1, + HmacSha1 hmacSha1, Crypt crypt) { + super(cmd, (short) 46, (short) 62, AppConfig.LEVEL_AUTH, + data, packet, sha1, hmacSha1, crypt); + } + + public short core(byte[] data, short dataOffset, short dataLength) { + byte[] udata = m_ee_data.getUdata_ee(); + byte[] buffer = m_ee_data.getGeneralBuffer_ramD(); + short encSize = encData(udata, (short) 0, AppConfig.UDATA_SIZE, buffer, (short) 0); + if (encSize != AppConfig.UDATA_SIZE) { + return AppConfig.FAIL; + } + m_ramD_outDataInfo[0] = 0; + m_ramD_outDataInfo[1] = encSize; + Util.arrayCopyNonAtomic(buffer, (short) 0, m_ramD_outData, (short) 0, encSize); + return AppConfig.SUCCESS; + } +} diff --git a/src/sega/a7/UdataWrite.java b/src/sega/a7/UdataWrite.java new file mode 100644 index 0000000..80c912d --- /dev/null +++ b/src/sega/a7/UdataWrite.java @@ -0,0 +1,28 @@ +package sega.a7; + +import javacard.security.MessageDigest; + +public class UdataWrite extends AuthCommand { + + public UdataWrite(short cmd, AppData data, MakePacket packet, MessageDigest sha1, + HmacSha1 hmacSha1, Crypt crypt) { + super(cmd, (short) 62, (short) 46, AppConfig.LEVEL_AUTH, + data, packet, sha1, hmacSha1, crypt); + } + + public short core(byte[] data, short dataOffset, short dataLength) { + if (m_ee_data.getSflag_ee()) { + return AppConfig.EEPWRITE_DISABLE; + } + byte[] buffer = m_ee_data.getGeneralBuffer_ramD(); + short decSize = decData(data, (short) (dataOffset + AppConfig.HEADER_SIZE), + AppConfig.UDATA_SIZE, buffer, (short) 0); + if (decSize != AppConfig.UDATA_SIZE) { + return AppConfig.FAIL; + } + if (!m_ee_data.setUdata_ee(buffer, (short) 0, AppConfig.UDATA_SIZE)) { + return AppConfig.EEP_WRITEFAIL; + } + return AppConfig.SUCCESS; + } +} diff --git a/src/sega/a7/VerGet.java b/src/sega/a7/VerGet.java new file mode 100644 index 0000000..ba664c7 --- /dev/null +++ b/src/sega/a7/VerGet.java @@ -0,0 +1,18 @@ +package sega.a7; + +import javacard.framework.Util; +import javacard.security.MessageDigest; + +public class VerGet extends Command { + + public VerGet(short cmd, AppData data, MakePacket packet, MessageDigest sha1) { + super(cmd, (short) 26, (short) 28, data, packet, sha1); + } + + public short core(byte[] data, short dataOffset, short dataLength) { + Util.setShort(m_ramD_outData, (short) 0, AppConfig.VERSION); + m_ramD_outDataInfo[0] = 0; + m_ramD_outDataInfo[1] = 2; + return AppConfig.SUCCESS; + } +}