From e1dc83a9903309ce4b219b3c7cd9fa7ae64a5fc7 Mon Sep 17 00:00:00 2001 From: kaichaosun Date: Tue, 3 Sep 2024 17:26:22 +0800 Subject: [PATCH 1/6] add ed25519 sign --- src/main/java/im/status/keycard/Consts.java | 50 + .../java/im/status/keycard/JCEd25519.java | 330 +++ .../java/im/status/keycard/jcmathlib.java | 2591 +++++++++++++++++ src/main/java/im/status/keycard/swalgs.java | 774 +++++ 4 files changed, 3745 insertions(+) create mode 100644 src/main/java/im/status/keycard/Consts.java create mode 100755 src/main/java/im/status/keycard/JCEd25519.java create mode 100644 src/main/java/im/status/keycard/jcmathlib.java create mode 100644 src/main/java/im/status/keycard/swalgs.java diff --git a/src/main/java/im/status/keycard/Consts.java b/src/main/java/im/status/keycard/Consts.java new file mode 100644 index 0000000..802611c --- /dev/null +++ b/src/main/java/im/status/keycard/Consts.java @@ -0,0 +1,50 @@ +package im.status.keycard; + +public class Consts { + public static final byte CLA_ED25519 = (byte) 0x00; + public static final byte INS_KEYGEN = (byte) 0xD0; + public static final byte INS_GET_PRIV = (byte) 0xD2; + public static final byte INS_SET_PUB = (byte) 0xD3; + public static final byte INS_SIGN_INIT = (byte) 0xD4; + public static final byte INS_SIGN_NONCE = (byte) 0xD5; + public static final byte INS_SIGN_FINALIZE = (byte) 0xD6; + public static final byte INS_SIGN_UPDATE = (byte) 0xD7; + public static final byte INS_GET_PRIV_NONCE = (byte) 0xD8; + + public final static short E_ALREADY_INITIALIZED = (short) 0xee00; + public final static short E_UNINITIALIZED = (short) 0xee01; + public final static short E_DEBUG_DISABLED = (short) 0xee02; + + public final static short SW_Exception = (short) 0xff01; + public final static short SW_ArrayIndexOutOfBoundsException = (short) 0xff02; + public final static short SW_ArithmeticException = (short) 0xff03; + public final static short SW_ArrayStoreException = (short) 0xff04; + public final static short SW_NullPointerException = (short) 0xff05; + public final static short SW_NegativeArraySizeException = (short) 0xff06; + public final static short SW_CryptoException_prefix = (short) 0xf100; + public final static short SW_SystemException_prefix = (short) 0xf200; + public final static short SW_PINException_prefix = (short) 0xf300; + public final static short SW_TransactionException_prefix = (short) 0xf400; + public final static short SW_CardRuntimeException_prefix = (short) 0xf500; + + public final static byte[] TRANSFORM_C = { + (byte) 0x70, (byte) 0xd9, (byte) 0x12, (byte) 0x0b, + (byte) 0x9f, (byte) 0x5f, (byte) 0xf9, (byte) 0x44, + (byte) 0x2d, (byte) 0x84, (byte) 0xf7, (byte) 0x23, + (byte) 0xfc, (byte) 0x03, (byte) 0xb0, (byte) 0x81, + (byte) 0x3a, (byte) 0x5e, (byte) 0x2c, (byte) 0x2e, + (byte) 0xb4, (byte) 0x82, (byte) 0xe5, (byte) 0x7d, + (byte) 0x33, (byte) 0x91, (byte) 0xfb, (byte) 0x55, + (byte) 0x00, (byte) 0xba, (byte) 0x81, (byte) 0xe7 + }; + public final static byte[] TRANSFORM_A3 = { + (byte) 0x2a, (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, + (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, + (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, + (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, + (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, + (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, + (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, + (byte) 0xaa, (byte) 0xad, (byte) 0x24, (byte) 0x51 + }; +} diff --git a/src/main/java/im/status/keycard/JCEd25519.java b/src/main/java/im/status/keycard/JCEd25519.java new file mode 100755 index 0000000..e8445df --- /dev/null +++ b/src/main/java/im/status/keycard/JCEd25519.java @@ -0,0 +1,330 @@ +package im.status.keycard; + +import javacard.framework.*; +import javacard.security.*; +import im.status.keycard.jcmathlib.*; +import im.status.keycard.swalgs.*; + +public class JCEd25519 extends Applet { + public final static boolean DEBUG = true; + public final static short CARD = OperationSupport.SIMULATOR; // TODO set your card + // public final static short CARD = OperationSupport.JCOP4_P71; // NXP J3Rxxx + // public final static short CARD = OperationSupport.JCOP3_P60; // NXP J3H145 + // public final static short CARD = OperationSupport.JCOP21; // NXP J2E145 + // public final static short CARD = OperationSupport.SECORA; // Infineon Secora ID S + + + private ResourceManager rm; + private ECCurve curve; + private BigNat privateKey, privateNonce, signature; + private BigNat transformC, transformA3, transformX, transformY, eight; + private ECPoint point; + + private final byte[] masterKey = new byte[32]; + private final byte[] prefix = new byte[32]; + private final byte[] publicKey = new byte[32]; + private final byte[] publicNonce = new byte[32]; + + private MessageDigest hasher; + + private final byte[] ramArray = JCSystem.makeTransientByteArray((short) Wei25519.G.length, JCSystem.CLEAR_ON_DESELECT); + private final RandomData random = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM); + + private boolean initialized = false; + + public static void install(byte[] bArray, short bOffset, byte bLength) { + new JCEd25519(bArray, bOffset, bLength); + } + + public JCEd25519(byte[] buffer, short offset, byte length) { + OperationSupport.getInstance().setCard(CARD); + register(); + } + + public void process(APDU apdu) { + if (selectingApplet()) + return; + + if (!initialized) { + initialize(apdu); + } + + if (apdu.getBuffer()[ISO7816.OFFSET_CLA] != Consts.CLA_ED25519) + ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED); + + try { + switch (apdu.getBuffer()[ISO7816.OFFSET_INS]) { + case Consts.INS_KEYGEN: + generateKeypair(apdu); + break; + + case Consts.INS_SET_PUB: + setPublicKey(apdu); + break; + case Consts.INS_SIGN_INIT: + signInit(apdu); + break; + case Consts.INS_SIGN_NONCE: + signNonce(apdu); + break; + case Consts.INS_SIGN_FINALIZE: + signFinalize(apdu); + break; + case Consts.INS_SIGN_UPDATE: + signUpdate(apdu); + break; + + case Consts.INS_GET_PRIV: + if(!DEBUG) { + ISOException.throwIt(Consts.E_DEBUG_DISABLED); + } + privateKey.copyToByteArray(apdu.getBuffer(), (short) 0); + apdu.setOutgoingAndSend((short) 0, (short) 32); + break; + case Consts.INS_GET_PRIV_NONCE: + if(!DEBUG) { + ISOException.throwIt(Consts.E_DEBUG_DISABLED); + } + privateNonce.copyToByteArray(apdu.getBuffer(), (short) 0); + apdu.setOutgoingAndSend((short) 0, (short) 32); + break; + default: + ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); + } + } catch (ISOException e) { + throw e; // Our exception from code, just re-emit + } catch (ArrayIndexOutOfBoundsException e) { + ISOException.throwIt(Consts.SW_ArrayIndexOutOfBoundsException); + } catch (ArithmeticException e) { + ISOException.throwIt(Consts.SW_ArithmeticException); + } catch (ArrayStoreException e) { + ISOException.throwIt(Consts.SW_ArrayStoreException); + } catch (NullPointerException e) { + ISOException.throwIt(Consts.SW_NullPointerException); + } catch (NegativeArraySizeException e) { + ISOException.throwIt(Consts.SW_NegativeArraySizeException); + } catch (CryptoException e) { + ISOException.throwIt((short) (Consts.SW_CryptoException_prefix | e.getReason())); + } catch (SystemException e) { + ISOException.throwIt((short) (Consts.SW_SystemException_prefix | e.getReason())); + } catch (PINException e) { + ISOException.throwIt((short) (Consts.SW_PINException_prefix | e.getReason())); + } catch (TransactionException e) { + ISOException.throwIt((short) (Consts.SW_TransactionException_prefix | e.getReason())); + } catch (CardRuntimeException e) { + ISOException.throwIt((short) (Consts.SW_CardRuntimeException_prefix | e.getReason())); + } catch (Exception e) { + ISOException.throwIt(Consts.SW_Exception); + } + } + + public boolean select() { + if (initialized) { + curve.updateAfterReset(); + } + return true; + } + + private void initialize(APDU apdu) { + if (initialized) + ISOException.throwIt(Consts.E_ALREADY_INITIALIZED); + + try { + hasher = MessageDigest.getInstance(MessageDigest.ALG_SHA_512, false); + } catch (CryptoException e) { + hasher = new Sha2(Sha2.SHA_512); + } + + rm = new ResourceManager((short) 256); + + privateKey = new BigNat((short) 32, JCSystem.MEMORY_TYPE_PERSISTENT, rm); + + privateNonce = new BigNat((short) 64, JCSystem.MEMORY_TYPE_TRANSIENT_DESELECT, rm); + signature = new BigNat((short) 64, JCSystem.MEMORY_TYPE_TRANSIENT_DESELECT, rm); + + transformC = new BigNat((short) Consts.TRANSFORM_C.length, JCSystem.MEMORY_TYPE_PERSISTENT, rm); + transformC.fromByteArray(Consts.TRANSFORM_C, (short) 0, (short) Consts.TRANSFORM_C.length); + transformA3 = new BigNat((short) Consts.TRANSFORM_A3.length, JCSystem.MEMORY_TYPE_PERSISTENT, rm); + transformA3.fromByteArray(Consts.TRANSFORM_A3, (short) 0, (short) Consts.TRANSFORM_A3.length); + transformX = new BigNat((short) 32, JCSystem.MEMORY_TYPE_TRANSIENT_RESET, rm); + transformY = new BigNat((short) 32, JCSystem.MEMORY_TYPE_TRANSIENT_RESET, rm); + + eight = new BigNat((short) 1, JCSystem.MEMORY_TYPE_PERSISTENT, rm); + eight.setValue((byte) 8); + + curve = new ECCurve(Wei25519.p, Wei25519.a, Wei25519.b, Wei25519.G, Wei25519.r, Wei25519.k, rm); + point = new ECPoint(curve); + + initialized = true; + } + + private void generateKeypair(APDU apdu) { + if (!initialized) + ISOException.throwIt(Consts.E_UNINITIALIZED); + + byte[] apduBuffer = apdu.getBuffer(); + boolean offload = apduBuffer[ISO7816.OFFSET_P1] != (byte) 0x00; + + random.generateData(masterKey, (short) 0, (short) 32); + hasher.reset(); + hasher.doFinal(masterKey, (short) 0, (short) 32, ramArray, (short) 0); + ramArray[0] &= (byte) 0xf8; // Clear lowest three bits + ramArray[31] &= (byte) 0x7f; // Clear highest bit + ramArray[31] |= (byte) 0x40; // Set second-highest bit + changeEndianity(ramArray, (short) 0, (short) 32); + + Util.arrayCopyNonAtomic(ramArray, (short) 32, prefix, (short) 0, (short) 32); + + privateKey.fromByteArray(ramArray, (short) 0, (short) 32); + privateKey.shiftRight((short) 3); // Required by smartcards (scalar must be lesser than r) + point.setW(curve.G, (short) 0, curve.POINT_SIZE); + point.multiplication(privateKey); + privateKey.fromByteArray(ramArray, (short) 0, (short) 32); // Reload private key + privateKey.mod(curve.rBN); + + if(!offload) { + point.multiplication(eight); // Compensate bit shift + + encodeEd25519(point, publicKey, (short) 0); + + Util.arrayCopyNonAtomic(publicKey, (short) 0, apduBuffer, (short) 0, (short) 32); + apdu.setOutgoingAndSend((short) 0, (short) 32); + } else { + point.getW(apduBuffer, (short) 0); + apdu.setOutgoingAndSend((short) 0, curve.POINT_SIZE); + } + } + + private void setPublicKey(APDU apdu) { + if (!initialized) + ISOException.throwIt(Consts.E_UNINITIALIZED); + + byte[] apduBuffer = apdu.getBuffer(); + Util.arrayCopyNonAtomic(apduBuffer, ISO7816.OFFSET_CDATA, publicKey, (short) 0, (short) publicKey.length); + apdu.setOutgoing(); + } + + private void signInit(APDU apdu) { + if (!initialized) + ISOException.throwIt(Consts.E_UNINITIALIZED); + + byte[] apduBuffer = apdu.getBuffer(); + boolean offload = apduBuffer[ISO7816.OFFSET_P1] != (byte) 0x00; + + // Generate nonce R + randomNonce(); + point.setW(curve.G, (short) 0, curve.POINT_SIZE); + point.multiplication(privateNonce); + hasher.reset(); + if (offload) { + point.getW(apduBuffer, (short) 0); + apdu.setOutgoingAndSend((short) 0, curve.POINT_SIZE); + } else { + encodeEd25519(point, ramArray, (short) 0); + Util.arrayCopyNonAtomic(ramArray, (short) 0, publicNonce, (short) 0, curve.COORD_SIZE); + hasher.update(ramArray, (short) 0, curve.COORD_SIZE); // R + hasher.update(publicKey, (short) 0, curve.COORD_SIZE); // A + apdu.setOutgoing(); + } + } + + private void signNonce(APDU apdu) { + if (!initialized) + ISOException.throwIt(Consts.E_UNINITIALIZED); + + byte[] apduBuffer = apdu.getBuffer(); + hasher.reset(); + Util.arrayCopyNonAtomic(apduBuffer, ISO7816.OFFSET_CDATA, publicNonce, (short) 0, curve.COORD_SIZE); + hasher.update(apduBuffer, ISO7816.OFFSET_CDATA, curve.COORD_SIZE); // R + hasher.update(publicKey, (short) 0, curve.COORD_SIZE); // A + apdu.setOutgoing(); + } + + private void signFinalize(APDU apdu) { + if (!initialized) + ISOException.throwIt(Consts.E_UNINITIALIZED); + + byte[] apduBuffer = apdu.getBuffer(); + short len = (short) ((short) apduBuffer[ISO7816.OFFSET_P1] & (short) 0xff); + hasher.doFinal(apduBuffer, ISO7816.OFFSET_CDATA, len, apduBuffer, (short) 0); // m + changeEndianity(apduBuffer, (short) 0, (short) 64); + signature.fromByteArray(apduBuffer, (short) 0, (short) 64); + signature.mod(curve.rBN); + signature.resize((short) 32); + + // Compute signature s = r + ex + signature.modMult(privateKey, curve.rBN); + signature.modAdd(privateNonce, curve.rBN); + + // Return signature (R, s) + Util.arrayCopyNonAtomic(publicNonce, (short) 0, apduBuffer, (short) 0, curve.COORD_SIZE); + signature.prependZeros(curve.COORD_SIZE, apduBuffer, curve.COORD_SIZE); + changeEndianity(apduBuffer, curve.COORD_SIZE, curve.COORD_SIZE); + apdu.setOutgoingAndSend((short) 0, (short) (curve.COORD_SIZE + curve.COORD_SIZE)); + } + + private void signUpdate(APDU apdu) { + if (!initialized) + ISOException.throwIt(Consts.E_UNINITIALIZED); + + byte[] apduBuffer = apdu.getBuffer(); + short len = (short) ((short) apduBuffer[ISO7816.OFFSET_P1] & (short) 0xff); + hasher.update(apduBuffer, ISO7816.OFFSET_CDATA, len); + apdu.setOutgoing(); + } + + private void encodeEd25519(ECPoint point, byte[] buffer, short offset) { + point.getW(ramArray, (short) 0); + + // Compute X + transformX.fromByteArray(ramArray, (short) 1, (short) 32); + transformY.fromByteArray(ramArray, (short) 33, (short) 32); + transformX.modSub(transformA3, curve.pBN); + transformX.modMult(transformC, curve.pBN); + transformY.modInv(curve.pBN); + transformX.modMult(transformY, curve.pBN); + + boolean xBit = transformX.isOdd(); + + // Compute Y + transformX.fromByteArray(ramArray, (short) 1, (short) 32); + transformX.modSub(transformA3, curve.pBN); + transformY.clone(transformX); + transformX.decrement(); + transformY.increment(); + transformY.mod(curve.pBN); + transformY.modInv(curve.pBN); + transformX.modMult(transformY, curve.pBN); + transformX.prependZeros(curve.COORD_SIZE, buffer, offset); + + buffer[offset] |= xBit ? (byte) 0x80 : (byte) 0x00; + + changeEndianity(buffer, offset, (short) 32); + } + + private void changeEndianity(byte[] array, short offset, short len) { + for (short i = 0; i < (short) (len / 2); ++i) { + byte tmp = array[(short) (offset + len - i - 1)]; + array[(short) (offset + len - i - 1)] = array[(short) (offset + i)]; + array[(short) (offset + i)] = tmp; + } + } + + // CAN BE USED ONLY IF NO OFFLOADING IS USED; OTHERWISE INSECURE! + private void deterministicNonce(byte[] msg, short offset, short len) { + hasher.reset(); + hasher.update(prefix, (short) 0, (short) 32); + hasher.doFinal(msg, offset, len, ramArray, (short) 0); + changeEndianity(ramArray, (short) 0, (short) 64); + privateNonce.fromByteArray(ramArray, (short) 0, (short) 64); + privateNonce.mod(curve.rBN); + privateNonce.resize((short) 32); + } + + private void randomNonce() { + random.generateData(ramArray, (short) 0, (short) 32); + privateNonce.fromByteArray(ramArray, (short) 0, (short) 32); + privateNonce.mod(curve.rBN); + privateNonce.resize((short) 32); + } +} diff --git a/src/main/java/im/status/keycard/jcmathlib.java b/src/main/java/im/status/keycard/jcmathlib.java new file mode 100644 index 0000000..d8c0ed8 --- /dev/null +++ b/src/main/java/im/status/keycard/jcmathlib.java @@ -0,0 +1,2591 @@ +package im.status.keycard; + +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.Util; +import javacard.security.*; +import javacardx.crypto.Cipher; + +/** + * Packaged JCMathLib v2.1-rc.1 (https://github.com/OpenCryptoProject/JCMathLib). + */ +public class jcmathlib { + /** + * @author Vasilios Mavroudis and Petr Svenda and Antonin Dufka + */ + public static class BigNat extends BigNatInternal { + + /** + * Construct a BigNat of a given size in bytes. + */ + public BigNat(short size, byte allocatorType, ResourceManager rm) { + super(size, allocatorType, rm); + } + + /** + * Division of this BigNat by provided other BigNat. + */ + public void divide(BigNat other) { + BigNat tmp = rm.BN_E; + + tmp.clone(this); + tmp.remainderDivide(other, this); + copy(tmp); + } + + /** + * Greatest common divisor of this BigNat with other BigNat. Result is stored into this. + */ + public void gcd(BigNat other) { + BigNat tmp = rm.BN_A; + BigNat tmpOther = rm.BN_B; + + + tmpOther.clone(other); + + // TODO: optimise? + while (!other.equals((byte) 0)) { + tmp.clone(tmpOther); + mod(tmpOther); + tmpOther.clone(this); + clone(tmp); + } + + } + + /** + * Decides whether the arguments are co-prime or not. + */ + public boolean isCoprime(BigNat a, BigNat b) { + BigNat tmp = rm.BN_C; + + tmp.clone(a); + + tmp.gcd(b); + boolean result = tmp.equals((byte) 1); + return result; + } + + /** + * Square computation supporting base greater than MAX_BIGNAT_LENGTH. + */ + public void sq() { + if (!OperationSupport.getInstance().RSA_SQ) { + BigNat tmp = rm.BN_E; + tmp.setSize(length()); + tmp.copy(this); + super.mult(tmp); + return; + } + if ((short) (rm.MAX_SQ_LENGTH - 1) < (short) (2 * length())) { + ISOException.throwIt(ReturnCodes.SW_BIGNAT_INVALIDSQ); + } + + byte[] resultBuffer = rm.ARRAY_A; + short offset = (short) (rm.MAX_SQ_LENGTH - length()); + + Util.arrayFillNonAtomic(resultBuffer, (short) 0, offset, (byte) 0x00); + copyToByteArray(resultBuffer, offset); + short len = rm.sqCiph.doFinal(resultBuffer, (short) 0, rm.MAX_SQ_LENGTH, resultBuffer, (short) 0); + if (len != rm.MAX_SQ_LENGTH) { + if (OperationSupport.getInstance().RSA_PREPEND_ZEROS) { + Util.arrayCopyNonAtomic(resultBuffer, (short) 0, resultBuffer, (short) (rm.MAX_SQ_LENGTH - len), len); + Util.arrayFillNonAtomic(resultBuffer, (short) 0, (short) (rm.MAX_SQ_LENGTH - len), (byte) 0); + } else { + ISOException.throwIt(ReturnCodes.SW_ECPOINT_UNEXPECTED_KA_LEN); + } + } + short zeroPrefix = (short) (rm.MAX_SQ_LENGTH - (short) 2 * length()); + fromByteArray(resultBuffer, zeroPrefix, (short) (rm.MAX_SQ_LENGTH - zeroPrefix)); + shrink(); + } + + /** + * Computes this * other and stores the result into this. + */ + public void mult(BigNat other) { + if (OperationSupport.getInstance().RSA_CHECK_ONE && equals((byte) 1)) { + clone(other); + return; + } + if (!OperationSupport.getInstance().RSA_SQ || length() <= (short) 16) { + super.mult(other); + return; + } + + BigNat result = rm.BN_F; + BigNat tmp = rm.BN_G; + + result.setSize((short) ((length() > other.length() ? length() : other.length()) + 1)); + result.copy(this); + result.add(other); + result.sq(); + + if (isLesser(other)) { + tmp.clone(other); + tmp.subtract(this); + } else { + tmp.clone(this); + tmp.subtract(other); + } + tmp.sq(); + + result.subtract(tmp); + result.shiftRight((short) 2); + + setSizeToMax(false); + copy(result); + shrink(); + } + + /** + * Computes modulo and stores the result in this. + */ + public void mod(BigNat mod) { + remainderDivide(mod, null); + } + + /** + * Negate current BigNat modulo provided modulus. + */ + public void modNegate(BigNat mod) { + BigNat tmp = rm.BN_B; + + tmp.clone(mod); + tmp.subtract(this); + setSize(mod.length()); + copy(tmp); + } + + /** + * Modular addition of a BigNat to this. + */ + public void modAdd(BigNat other, BigNat mod) { + resize((short) (mod.length() + 1)); + add(other); + if (!isLesser(mod)) { + subtract(mod); + } + setSize(mod.length()); + } + + /** + * Modular subtraction of a BigNat from this. + */ + public void modSub(BigNat other, BigNat mod) { + resize((short) (mod.length() + 1)); + if (isLesser(other)) { + add(mod); + } + subtract(other); + setSize(mod.length()); + } + + /** + * Square this mod a modulus fixed with fixModSqMod method. + */ + private void modSqFixed() { + BigNat tmpMod = rm.BN_F; + byte[] tmpBuffer = rm.ARRAY_A; + short modLength; + + tmpMod.setSize(rm.MAX_EXP_LENGTH); + if (OperationSupport.getInstance().RSA_RESIZE_MOD) { + modLength = rm.MAX_EXP_LENGTH; + } else { + modLength = rm.fixedMod.length(); + } + + prependZeros(modLength, tmpBuffer, (short) 0); + short len = rm.modSqCiph.doFinal(tmpBuffer, (short) 0, modLength, tmpBuffer, (short) 0); + + if (len != rm.MAX_EXP_LENGTH) { + if (OperationSupport.getInstance().RSA_PREPEND_ZEROS) { + Util.arrayCopyNonAtomic(tmpBuffer, (short) 0, tmpBuffer, (short) (rm.MAX_EXP_LENGTH - len), len); + Util.arrayFillNonAtomic(tmpBuffer, (short) 0, (short) (rm.MAX_EXP_LENGTH - len), (byte) 0); + } else { + ISOException.throwIt(ReturnCodes.SW_ECPOINT_UNEXPECTED_KA_LEN); + } + } + tmpMod.fromByteArray(tmpBuffer, (short) 0, rm.MAX_EXP_LENGTH); + + if (OperationSupport.getInstance().RSA_EXTRA_MOD) { + tmpMod.mod(rm.fixedMod); + } + setSize(rm.fixedMod.length()); + copy(tmpMod); + } + + /** + * Computes (this ^ exp % mod) using RSA algorithm and store results into this. + */ + public void modExp(BigNat exp, BigNat mod) { + if (!OperationSupport.getInstance().RSA_EXP) + ISOException.throwIt(ReturnCodes.SW_OPERATION_NOT_SUPPORTED); + if (OperationSupport.getInstance().RSA_CHECK_EXP_ONE && exp.equals((byte) 1)) + return; + if (!OperationSupport.getInstance().RSA_SQ && exp.equals((byte) 2)) { + modMult(this, mod); + return; + } + + BigNat tmpMod = rm.BN_F; // modExp is called from modSqrt => requires BN_F not being locked when modExp is called + byte[] tmpBuffer = rm.ARRAY_A; + short modLength; + + tmpMod.setSize(rm.MAX_EXP_LENGTH); + + if (OperationSupport.getInstance().RSA_PUB) { + // Verify if pre-allocated engine match the required values + if (rm.expPub.getSize() < (short) (mod.length() * 8) || rm.expPub.getSize() < (short) (length() * 8)) { + ISOException.throwIt(ReturnCodes.SW_BIGNAT_MODULOTOOLARGE); + } + if (OperationSupport.getInstance().RSA_KEY_REFRESH) { + // Simulator fails when reusing the original object + rm.expPub = (RSAPublicKey) KeyBuilder.buildKey(KeyBuilder.TYPE_RSA_PUBLIC, rm.MAX_EXP_BIT_LENGTH, false); + } + short len = exp.copyToByteArray(tmpBuffer, (short) 0); + rm.expPub.setExponent(tmpBuffer, (short) 0, len); + if (OperationSupport.getInstance().RSA_RESIZE_MOD) { + if (OperationSupport.getInstance().RSA_APPEND_MOD) { + mod.appendZeros(rm.MAX_EXP_LENGTH, tmpBuffer, (short) 0); + } else { + mod.prependZeros(rm.MAX_EXP_LENGTH, tmpBuffer, (short) 0); + } + rm.expPub.setModulus(tmpBuffer, (short) 0, rm.MAX_EXP_LENGTH); + modLength = rm.MAX_EXP_LENGTH; + } else { + modLength = mod.copyToByteArray(tmpBuffer, (short) 0); + rm.expPub.setModulus(tmpBuffer, (short) 0, modLength); + } + rm.expCiph.init(rm.expPub, Cipher.MODE_DECRYPT); + } else { + // Verify if pre-allocated engine match the required values + if (rm.expPriv.getSize() < (short) (mod.length() * 8) || rm.expPriv.getSize() < (short) (length() * 8)) { + ISOException.throwIt(ReturnCodes.SW_BIGNAT_MODULOTOOLARGE); + } + if (OperationSupport.getInstance().RSA_KEY_REFRESH) { + // Simulator fails when reusing the original object + rm.expPriv = (RSAPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_RSA_PRIVATE, rm.MAX_EXP_BIT_LENGTH, false); + } + short len = exp.copyToByteArray(tmpBuffer, (short) 0); + rm.expPriv.setExponent(tmpBuffer, (short) 0, len); + if (OperationSupport.getInstance().RSA_RESIZE_MOD) { + if (OperationSupport.getInstance().RSA_APPEND_MOD) { + mod.appendZeros(rm.MAX_EXP_LENGTH, tmpBuffer, (short) 0); + } else { + mod.prependZeros(rm.MAX_EXP_LENGTH, tmpBuffer, (short) 0); + + } + rm.expPriv.setModulus(tmpBuffer, (short) 0, rm.MAX_EXP_LENGTH); + modLength = rm.MAX_EXP_LENGTH; + } else { + modLength = mod.copyToByteArray(tmpBuffer, (short) 0); + rm.expPriv.setModulus(tmpBuffer, (short) 0, modLength); + } + rm.expCiph.init(rm.expPriv, Cipher.MODE_DECRYPT); + } + + prependZeros(modLength, tmpBuffer, (short) 0); + short len = rm.expCiph.doFinal(tmpBuffer, (short) 0, modLength, tmpBuffer, (short) 0); + + if (len != rm.MAX_EXP_LENGTH) { + if (OperationSupport.getInstance().RSA_PREPEND_ZEROS) { + // Decrypted length can be either tmp_size or less because of leading zeroes consumed by simulator engine implementation + // Move obtained value into proper position with zeroes prepended + Util.arrayCopyNonAtomic(tmpBuffer, (short) 0, tmpBuffer, (short) (rm.MAX_EXP_LENGTH - len), len); + Util.arrayFillNonAtomic(tmpBuffer, (short) 0, (short) (rm.MAX_EXP_LENGTH - len), (byte) 0); + } else { + // real cards should keep whole length of block + ISOException.throwIt(ReturnCodes.SW_ECPOINT_UNEXPECTED_KA_LEN); + } + } + tmpMod.fromByteArray(tmpBuffer, (short) 0, rm.MAX_EXP_LENGTH); + + if (OperationSupport.getInstance().RSA_EXTRA_MOD) { + tmpMod.mod(mod); + } + setSize(mod.length()); + copy(tmpMod); + } + + /** + * Computes modular inversion. The result is stored into this. + */ + public void modInv(BigNat mod) { + BigNat tmp = rm.BN_B; + tmp.clone(mod); + tmp.decrement(); + tmp.decrement(); + + modExp(tmp, mod); + } + + /** + * Multiplication of this and other modulo mod. The result is stored to this. + */ + public void modMult(BigNat other, BigNat mod) { + BigNat tmp = rm.BN_D; + BigNat result = rm.BN_E; + + if (OperationSupport.getInstance().RSA_CHECK_ONE && equals((byte) 1)) { + copy(other); + return; + } + + if (!OperationSupport.getInstance().RSA_SQ || OperationSupport.getInstance().RSA_EXTRA_MOD) { + result.clone(this); + result.mult(other); + result.mod(mod); + } else { + result.setSize((short) (mod.length() + 1)); + result.copy(this); + result.add(other); + + short carry = (byte) 0; + if (result.isOdd()) { + if (result.isLesser(mod)) { + carry = result.add(mod); + } else { + result.subtract(mod); + } + } + result.shiftRight((short) 1, carry); + result.resize(mod.length()); + + tmp.clone(result); + tmp.modSub(other, mod); + + result.modSq(mod); + tmp.modSq(mod); + + result.modSub(tmp, mod); + } + setSize(mod.length()); + copy(result); + } + + /** + * Computes modulo square of this BigNat. + */ + public void modSq(BigNat mod) { + if (OperationSupport.getInstance().RSA_SQ) { + if (rm.fixedMod != null && rm.fixedMod == mod) { + modSqFixed(); + } else { + modExp(ResourceManager.TWO, mod); + } + } else { + modMult(this, mod); + } + } + + /** + * Computes square root of provided BigNat which MUST be prime using Tonelli Shanks Algorithm. The result (one of + * the two roots) is stored to this. + */ + public void modSqrt(BigNat p) { + BigNat exp = rm.BN_G; + BigNat p1 = rm.BN_B; + BigNat q = rm.BN_C; + BigNat tmp = rm.BN_D; + BigNat z = rm.BN_A; + BigNat t = rm.BN_B; + BigNat b = rm.BN_C; + + // 1. Find Q and S such that p - 1 = Q * 2^S and Q is odd + p1.clone(p); + p1.decrement(); + + q.clone(p1); + + short s = 0; + while (!q.isOdd()) { + ++s; + q.shiftRight((short) 1); + } + + // 2. Find the first quadratic non-residue z by brute-force search + exp.clone(p1); + exp.shiftRight((short) 1); + + + z.setSize(p.length()); + z.setValue((byte) 1); + tmp.setSize(p.length()); + tmp.setValue((byte) 1); + + while (!tmp.equals(p1)) { + z.increment(); + tmp.copy(z); + tmp.modExp(exp, p); // Euler's criterion + } + + // 3. Compute the first candidate + exp.clone(q); + exp.increment(); + exp.shiftRight((short) 1); + + t.clone(this); + t.modExp(q, p); + + if (t.equals((byte) 0)) { + zero(); + return; + } + + mod(p); + modExp(exp, p); + + if (t.equals((byte) 1)) { + return; + } + + // 4. Search for further candidates + z.modExp(q, p); + + while(true) { + tmp.clone(t); + short i = 0; + + do { + tmp.modSq(p); + ++i; + } while (!tmp.equals((byte) 1)); + + + b.clone(z); + s -= i; + --s; + + tmp.setSize((short) 1); + tmp.setValue((byte) 1); + while(s != 0) { + tmp.shiftLeft((short) 1); + --s; + } + b.modExp(tmp, p); + s = i; + z.clone(b); + z.modSq(p); + t.modMult(z, p); + modMult(b, p); + + if(t.equals((byte) 0)) { + zero(); + break; + } + if(t.equals((byte) 1)) { + break; + } + } + } + } + + /** + * Based on BigNat library from OV-chip project. by Radboud University Nijmegen + * + * @author Vasilios Mavroudis and Petr Svenda + */ + public static class BigNatInternal { + protected final ResourceManager rm; + private static final short DIGIT_MASK = 0xff, DIGIT_LEN = 8, DOUBLE_DIGIT_LEN = 16, POSITIVE_DOUBLE_DIGIT_MASK = 0x7fff; + + private byte[] value; + private short size; // The current size of internal representation in bytes. + private short offset; + + /** + * Construct a BigNat of at least a given size in bytes. + */ + public BigNatInternal(short size, byte allocatorType, ResourceManager rm) { + this.rm = rm; + this.offset = 1; + this.size = size; + this.value = rm.memAlloc.allocateByteArray((short) (size + 1), allocatorType); + } + + /** + * Set value of this from a byte array representation. + * + * @param source the byte array + * @param sourceOffset offset in the byte array + * @param length length of the value representation + * @return number of bytes read + */ + public short fromByteArray(byte[] source, short sourceOffset, short length) { + short read = length <= (short) value.length ? length : (short) value.length; + setSize(read); + Util.arrayCopyNonAtomic(source, sourceOffset, value, offset, size); + return size; + } + + /** + * Serialize this BigNat value into a provided byte array. + * + * @param dst the byte array + * @param dstOffset offset in the byte array + * @return number of bytes written + */ + public short copyToByteArray(byte[] dst, short dstOffset) { + Util.arrayCopyNonAtomic(value, offset, dst, dstOffset, size); + return size; + } + + /** + * Get size of this BigNat in bytes. + * + * @return size in bytes + */ + public short length() { + return size; + } + + /** + * Sets the size of this BigNat in bytes. + * + * Previous value is kept so value is either non-destructively trimmed or enlarged. + * + * @param newSize the new size + */ + public void setSize(short newSize) { + if (newSize < 0 || newSize > value.length) { + ISOException.throwIt(ReturnCodes.SW_BIGNAT_RESIZETOLONGER); + } + size = newSize; + offset = (short) (value.length - size); + } + + /** + * Set size of this BigNat to the maximum size given during object creation. + * + * @param erase flag indicating whether to set internal representation to zero + */ + public void setSizeToMax(boolean erase) { + setSize((short) value.length); + if (erase) { + erase(); + } + } + + /** + * Resize this BigNat value to given size in bytes. May result in truncation. + * + * @param newSize new size in bytes + */ + public void resize(short newSize) { + if (newSize > (short) value.length) { + ISOException.throwIt(ReturnCodes.SW_BIGNAT_REALLOCATIONNOTALLOWED); + } + + short diff = (short) (newSize - size); + setSize(newSize); + if (diff > 0) { + Util.arrayFillNonAtomic(value, offset, diff, (byte) 0); + } + } + + /** + * Append zeros to reach the defined byte length and store the result in an output buffer. + * + * @param targetLength required length including appended zeroes + * @param outBuffer output buffer for value with appended zeroes + * @param outOffset start offset inside outBuffer for write + */ + public void appendZeros(short targetLength, byte[] outBuffer, short outOffset) { + Util.arrayCopyNonAtomic(value, offset, outBuffer, outOffset, size); + Util.arrayFillNonAtomic(outBuffer, (short) (outOffset + size), (short) (targetLength - size), (byte) 0); + } + + /** + * Prepend zeros to reach the defined byte length and store the result in an output buffer. + * + * @param targetLength required length including prepended zeroes + * @param outBuffer output buffer for value with prepended zeroes + * @param outOffset start offset inside outBuffer for write + */ + public void prependZeros(short targetLength, byte[] outBuffer, short outOffset) { + short start = (short) (targetLength - size); + if (start > 0) { + Util.arrayFillNonAtomic(outBuffer, outOffset, start, (byte) 0); + } + Util.arrayCopyNonAtomic(value, offset, outBuffer, (short) (outOffset + start), size); + } + + /** + * Remove leading zeroes from this BigNat and decrease its byte size accordingly. + */ + public void shrink() { + short i; + for (i = offset; i < value.length; i++) { // Find first non-zero byte + if (value[i] != 0) { + break; + } + } + + short newSize = (short) (value.length - i); + if (newSize < 0) { + ISOException.throwIt(ReturnCodes.SW_BIGNAT_INVALIDRESIZE); + } + resize(newSize); + } + + /** + * Set this BigNat value to zero. Previous size is kept. + */ + public void zero() { + Util.arrayFillNonAtomic(value, offset, size, (byte) 0); + } + + /** + * Erase the internal array of this BigNat. + */ + public void erase() { + Util.arrayFillNonAtomic(value, (short) 0, (short) value.length, (byte) 0); + } + + /** + * Set this BigNat to a given value. Previous size is kept. + */ + public void setValue(byte newValue) { + zero(); + value[(short) (value.length - 1)] = (byte) (newValue & DIGIT_MASK); + } + + /** + * Set this BigNat to a given value. Previous size is kept. + */ + public void setValue(short newValue) { + zero(); + value[(short) (value.length - 1)] = (byte) (newValue & DIGIT_MASK); + value[(short) (value.length - 2)] = (byte) ((short) (newValue >> 8) & DIGIT_MASK); + } + + /** + * Copies a BigNat into this without changing size. May throw an exception if this is too small. + */ + public void copy(BigNatInternal other) { + short thisStart, otherStart, len; + short diff = (short) (size - other.size); + if (diff >= 0) { + thisStart = (short) (diff + offset); + otherStart = other.offset; + len = other.size; + + if (diff > 0) { + Util.arrayFillNonAtomic(value, offset, diff, (byte) 0); + } + } else { + thisStart = offset; + otherStart = (short) (other.offset - diff); + len = size; + // Verify here that other have leading zeroes up to otherStart + for (short i = other.offset; i < otherStart; i++) { + if (other.value[i] != 0) { + ISOException.throwIt(ReturnCodes.SW_BIGNAT_INVALIDCOPYOTHER); + } + } + } + Util.arrayCopyNonAtomic(other.value, otherStart, value, thisStart, len); + } + + /** + * Copies a BigNat into this including its size. May require reallocation. + */ + public void clone(BigNatInternal other) { + if (other.size > (short) value.length) { + ISOException.throwIt(ReturnCodes.SW_BIGNAT_REALLOCATIONNOTALLOWED); + } + + short diff = (short) ((short) value.length - other.size); + other.copyToByteArray(value, diff); + if (diff > 0) { + Util.arrayFillNonAtomic(value, (short) 0, diff, (byte) 0); + } + setSize(other.size); + } + + /** + * Check if stored BigNat is odd. + */ + public boolean isOdd() { + return (byte) (value[(short) (value.length - 1)] & (byte) 1) != (byte) 0; + } + + /** + * Returns true if this BigNat is lesser than the other. + */ + public boolean isLesser(BigNatInternal other) { + return isLesser(other, (short) 0, (short) 0); + } + + /** + * Returns true if this is lesser than other shifted by a given number of digits. + */ + private boolean isLesser(BigNatInternal other, short shift, short start) { + short j = (short) (other.size + shift - size + start + other.offset); + + for (short i = (short) (start + other.offset); i < j; ++i) { + if (other.value[i] != 0) { + return true; + } + } + + for (short i = (short) (start + offset); i < (short) value.length; i++, j++) { + short thisValue = (short) (value[i] & DIGIT_MASK); + short otherValue = (j >= other.offset && j < (short) other.value.length) ? (short) (other.value[j] & DIGIT_MASK) : (short) 0; + if (thisValue < otherValue) { + return true; // CTO + } + if (thisValue > otherValue) { + return false; + } + } + return false; + } + + /** + * Value equality check. + * + * @param other BigNat to compare + * @return true if this and other have the same value, false otherwise. + */ + public boolean equals(BigNatInternal other) { + short diff = (short) (size - other.size); + + if (diff == 0) { + return Util.arrayCompare(value, offset, other.value, other.offset, size) == 0; + } + + + if (diff < 0) { + short end = (short) (other.offset - diff); + for (short i = other.offset; i < end; ++i) { + if (other.value[i] != (byte) 0) { + return false; + } + } + return Util.arrayCompare(value, (short) 0, other.value, end, size) == 0; + } + + short end = diff; + for (short i = (short) 0; i < end; ++i) { + if (value[i] != (byte) 0) { + return false; + } + } + return Util.arrayCompare(value, end, other.value, other.offset, other.size) == 0; + } + + /** + * Test equality with a byte. + */ + public boolean equals(byte b) { + for (short i = offset; i < (short) (value.length - 1); i++) { + if (value[i] != 0) { + return false; // CTO + } + } + return value[(short) (value.length - 1)] == b; + } + + /** + * Increment this BigNat. + */ + public void increment() { + for (short i = (short) (value.length - 1); i >= offset; i--) { + short tmp = (short) (value[i] & 0xff); + value[i] = (byte) (tmp + 1); + if (tmp < 255) { + break; // CTO + } + } + } + + /** + * Decrement this BigNat. + */ + public void decrement() { + short tmp; + for (short i = (short) (value.length - 1); i >= offset; i--) { + tmp = (short) (value[i] & 0xff); + value[i] = (byte) (tmp - 1); + if (tmp != 0) { + break; // CTO + } + } + } + + /** + * Add short value to this BigNat + * + * @param other short value to add + */ + public byte add(short other) { + rm.BN_WORD.setValue(other); + byte carry = add(rm.BN_WORD); + return carry; + } + + /** + * Adds other to this. Outputs carry bit. + * + * @param other BigNat to add + * @return true if carry occurs, false otherwise + */ + public byte add(BigNatInternal other) { + return add(other, (short) 0, (short) 1); + } + + /** + * Computes other * multiplier, shifts the results by shift and adds it to this. + * Multiplier must be in range [0; 2^8 - 1]. + * This must be large enough to fit the results. + */ + private byte add(BigNatInternal other, short shift, short multiplier) { + short acc = 0; + short i = (short) (other.size - 1 + other.offset); + short j = (short) (size - 1 - shift + offset); + for (; i >= other.offset && j >= offset; i--, j--) { + acc += (short) ((short) (value[j] & DIGIT_MASK) + (short) (multiplier * (other.value[i] & DIGIT_MASK))); + + value[j] = (byte) (acc & DIGIT_MASK); + acc = (short) ((acc >> DIGIT_LEN) & DIGIT_MASK); + } + + for (; acc > 0 && j >= offset; --j) { + acc += (short) (value[j] & DIGIT_MASK); + value[j] = (byte) (acc & DIGIT_MASK); + acc = (short) ((acc >> DIGIT_LEN) & DIGIT_MASK); + } + + // output carry bit if present + return (byte) (((byte) (((short) (acc | -acc) & (short) 0xFFFF) >>> 15) & 0x01) << 7); + } + + /** + * Subtract provided other BigNat from this BigNat. + * + * @param other BigNat to be subtracted from this + */ + public void subtract(BigNatInternal other) { + subtract(other, (short) 0, (short) 1); + } + + /** + * Computes other * multiplier, shifts the results by shift and subtract it from this. + * Multiplier must be in range [0; 2^8 - 1]. + */ + private void subtract(BigNatInternal other, short shift, short multiplier) { + short acc = 0; + short i = (short) (size - 1 - shift + offset); + short j = (short) (other.size - 1 + other.offset); + for (; i >= offset && j >= other.offset; i--, j--) { + acc += (short) (multiplier * (other.value[j] & DIGIT_MASK)); + short tmp = (short) ((value[i] & DIGIT_MASK) - (acc & DIGIT_MASK)); + + value[i] = (byte) (tmp & DIGIT_MASK); + acc = (short) ((acc >> DIGIT_LEN) & DIGIT_MASK); + if (tmp < 0) { + acc++; + } + } + + // deal with carry as long as there are digits left in this + for (; i >= offset && acc != 0; --i) { + short tmp = (short) ((value[i] & DIGIT_MASK) - (acc & DIGIT_MASK)); + value[i] = (byte) (tmp & DIGIT_MASK); + acc = (short) ((acc >> DIGIT_LEN) & DIGIT_MASK); + if (tmp < 0) { + acc++; + } + } + } + + /** + * Multiplies this and other using software multiplications and stores results into this. + */ + public void mult(BigNatInternal other) { + BigNatInternal tmp = rm.BN_F; + tmp.clone(this); + setSizeToMax(true); + for (short i = (short) (other.value.length - 1); i >= other.offset; i--) { + add(tmp, (short) (other.value.length - 1 - i), (short) (other.value[i] & DIGIT_MASK)); + } + shrink(); + } + + /** + * Right bit shift with carry + * + * @param bits number of bits to shift by + * @param carry ORed into the highest byte + */ + protected void shiftRight(short bits, short carry) { + // assumes 0 <= bits < 8 + short mask = (short) ((short) (1 << bits) - 1); // lowest `bits` bits set to 1 + for (short i = offset; i < (short) value.length; i++) { + short current = (short) (value[i] & 0xff); + short previous = current; + current >>= bits; + value[i] = (byte) (current | carry); + carry = (short) (previous & mask); + carry <<= (short) (8 - bits); + } + } + + /** + * Right bit shift + * + * @param bits number of bits to shift by + */ + public void shiftRight(short bits) { + shiftRight(bits, (short) 0); + } + + /** + * Left bit shift with carry + * + * @param bits number of bits to shift by + * @param carry ORed into the lowest byte + */ + protected void shiftLeft(short bits, short carry) { + // assumes 0 <= bits < 8 + short mask = (short) ((-1 << (8 - bits)) & 0xff); // highest `bits` bits set to 1 + for (short i = (short) (value.length - 1); i >= offset; --i) { + short current = (short) (value[i] & 0xff); + short previous = current; + current <<= bits; + value[i] = (byte) (current | carry); + carry = (short) (previous & mask); + carry >>= (8 - bits); + } + + if (carry != 0) { + setSize((short) (size + 1)); + value[offset] = (byte) carry; + } + } + + /** + * Right bit shift + * + * @param bits number of bits to shift by + */ + public void shiftLeft(short bits) { + shiftLeft(bits, (short) 0); + } + + /** + * Divide this by divisor and store the remained in this and quotient in quotient. + * + * Quadratic complexity in digit difference of this and divisor. + * + * @param divisor non-zero number + * @param quotient may be null + */ + public void remainderDivide(BigNatInternal divisor, BigNatInternal quotient) { + if (quotient != null) { + quotient.zero(); + } + + short divisorIndex = divisor.offset; + while (divisor.value[divisorIndex] == 0) { + divisorIndex++; + } + + short divisorShift = (short) (size - divisor.size + divisorIndex - divisor.offset); + short divisionRound = 0; + short firstDivisorDigit = (short) (divisor.value[divisorIndex] & DIGIT_MASK); + short divisorBitShift = (short) (highestOneBit((short) (firstDivisorDigit + 1)) - 1); + byte secondDivisorDigit = divisorIndex < (short) (divisor.value.length - 1) ? divisor.value[(short) (divisorIndex + 1)] : 0; + byte thirdDivisorDigit = divisorIndex < (short) (divisor.value.length - 2) ? divisor.value[(short) (divisorIndex + 2)] : 0; + + while (divisorShift >= 0) { + while (!isLesser(divisor, divisorShift, (short) (divisionRound > 0 ? divisionRound - 1 : 0))) { + short divisionRoundOffset = (short) (divisionRound + offset); + short dividentDigits = divisionRound == 0 ? 0 : (short) ((short) (value[(short) (divisionRoundOffset - 1)]) << DIGIT_LEN); + dividentDigits |= (short) (value[(short) (divisionRound + offset)] & DIGIT_MASK); + + short divisorDigit; + if (dividentDigits < 0) { + dividentDigits = (short) ((dividentDigits >>> 1) & POSITIVE_DOUBLE_DIGIT_MASK); + divisorDigit = (short) ((firstDivisorDigit >>> 1) & POSITIVE_DOUBLE_DIGIT_MASK); + } else { + short dividentBitShift = (short) (highestOneBit(dividentDigits) - 1); + short bitShift = dividentBitShift <= divisorBitShift ? dividentBitShift : divisorBitShift; + + dividentDigits = shiftBits( + dividentDigits, divisionRound < (short) (size - 1) ? value[(short) (divisionRoundOffset + 1)] : 0, + divisionRound < (short) (size - 2) ? value[(short) (divisionRoundOffset + 2)] : 0, + bitShift + ); + divisorDigit = shiftBits(firstDivisorDigit, secondDivisorDigit, thirdDivisorDigit, bitShift); + } + + short multiple = (short) (dividentDigits / (short) (divisorDigit + 1)); + if (multiple < 1) { + multiple = 1; + } + + subtract(divisor, divisorShift, multiple); + + if (quotient != null) { + short divisorShiftOffset = (short) (divisorShift - quotient.offset); + short quotientDigit = (short) ((quotient.value[(short) (quotient.size - 1 - divisorShiftOffset)] & DIGIT_MASK) + multiple); + quotient.value[(short) (quotient.size - 1 - divisorShiftOffset)] = (byte) quotientDigit; + } + } + divisionRound++; + divisorShift--; + } + } + + /** + * Get the index of the highest bit set to 1. Used in remainderDivide. + */ + private static short highestOneBit(short x) { + for (short i = 0; i < DOUBLE_DIGIT_LEN; ++i) { + if (x < 0) { + return i; + } + x <<= 1; + } + return DOUBLE_DIGIT_LEN; + } + + /** + * Shift to the left and fill. Used in remainderDivide. + * + * @param high most significant 16 bits + * @param middle middle 8 bits + * @param low least significant 8 bits + * @param shift the left shift + * @return most significant 16 bits as short + */ + private static short shiftBits(short high, byte middle, byte low, short shift) { + // shift high + high <<= shift; + + // merge middle bits + byte mask = (byte) (DIGIT_MASK << (shift >= DIGIT_LEN ? 0 : DIGIT_LEN - shift)); + short bits = (short) ((short) (middle & mask) & DIGIT_MASK); + if (shift > DIGIT_LEN) { + bits <<= shift - DIGIT_LEN; + } else { + bits >>>= DIGIT_LEN - shift; + } + high |= bits; + + if (shift <= DIGIT_LEN) { + return high; + } + + // merge low bits + mask = (byte) (DIGIT_MASK << DOUBLE_DIGIT_LEN - shift); + bits = (short) ((((short) (low & mask) & DIGIT_MASK) >> DOUBLE_DIGIT_LEN - shift)); + high |= bits; + + return high; + } + + } + + /** + * @author Vasilios Mavroudis and Petr Svenda + */ + public static class ECCurve { + public final short KEY_BIT_LENGTH, POINT_SIZE, COORD_SIZE; + public ResourceManager rm; + + public byte[] p, a, b, G, r; + public short k; + public BigNat pBN, aBN, bBN, rBN; + + + public KeyPair disposablePair; + public ECPrivateKey disposablePriv; + public ECPublicKey disposablePub; + + /** + * Creates new curve object from provided parameters. Parameters are not copied, the + * arrays must not be changed. + * + * @param p array with p + * @param a array with a + * @param b array with b + * @param G array with base point G + * @param r array with r + */ + public ECCurve(byte[] p, byte[] a, byte[] b, byte[] G, byte[] r, short k, ResourceManager rm) { + short bits = (short) (p.length * 8); + if (OperationSupport.getInstance().EC_PRECISE_BITLENGTH) { + for (short i = 0; i < (short) p.length; ++i) { + bits -= 8; + if (p[i] != (byte) 0x00) { + short tmp = (short) (p[i] & 0xff); + while (tmp != (short) 0x00) { + tmp >>= (short) 1; + ++bits; + } + break; + } + } + } + KEY_BIT_LENGTH = bits; + POINT_SIZE = (short) G.length; + COORD_SIZE = (short) ((short) (G.length - 1) / 2); + + this.p = p; + this.a = a; + this.b = b; + this.G = G; + this.r = r; + this.k = k; + this.rm = rm; + + pBN = new BigNat(COORD_SIZE, JCSystem.MEMORY_TYPE_TRANSIENT_RESET, rm); + pBN.fromByteArray(p, (short) 0, (short) p.length); + aBN = new BigNat(COORD_SIZE, JCSystem.MEMORY_TYPE_PERSISTENT, rm); + aBN.fromByteArray(a, (short) 0, (short) a.length); + bBN = new BigNat(COORD_SIZE, JCSystem.MEMORY_TYPE_PERSISTENT, rm); + bBN.fromByteArray(b, (short) 0, (short) b.length); + rBN = new BigNat(COORD_SIZE, JCSystem.MEMORY_TYPE_TRANSIENT_RESET, rm); + rBN.fromByteArray(r, (short) 0, (short) r.length); + + disposablePair = newKeyPair(null); + disposablePriv = (ECPrivateKey) disposablePair.getPrivate(); + disposablePub = (ECPublicKey) disposablePair.getPublic(); + } + + /** + * Refresh critical information stored in RAM for performance reasons after a card reset (RAM was cleared). + */ + public void updateAfterReset() { + pBN.fromByteArray(p, (short) 0, (short) p.length); + aBN.fromByteArray(a, (short) 0, (short) a.length); + bBN.fromByteArray(b, (short) 0, (short) b.length); + rBN.fromByteArray(r, (short) 0, (short) r.length); + } + + /** + * Creates a new keyPair based on this curve parameters. KeyPair object is reused if provided. Fresh keyPair value is generated. + * @param keyPair existing KeyPair object which is reused if required. If null, new KeyPair is allocated + * @return new or existing object with fresh key pair value + */ + KeyPair newKeyPair(KeyPair keyPair) { + ECPublicKey pubKey; + ECPrivateKey privKey; + if (keyPair == null) { + pubKey = (ECPublicKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PUBLIC, KEY_BIT_LENGTH, false); + privKey = (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, KEY_BIT_LENGTH, false); + keyPair = new KeyPair(pubKey, privKey); + } else { + pubKey = (ECPublicKey) keyPair.getPublic(); + privKey = (ECPrivateKey) keyPair.getPrivate(); + } + + privKey.setFieldFP(p, (short) 0, (short) p.length); + privKey.setA(a, (short) 0, (short) a.length); + privKey.setB(b, (short) 0, (short) b.length); + privKey.setG(G, (short) 0, (short) G.length); + privKey.setR(r, (short) 0, (short) r.length); + privKey.setK(OperationSupport.getInstance().EC_SET_COFACTOR ? k : (short) 1); + + pubKey.setFieldFP(p, (short) 0, (short) p.length); + pubKey.setA(a, (short) 0, (short) a.length); + pubKey.setB(b, (short) 0, (short) b.length); + pubKey.setG(G, (short) 0, (short) G.length); + pubKey.setR(r, (short) 0, (short) r.length); + pubKey.setK(OperationSupport.getInstance().EC_SET_COFACTOR ? k : (short) 1); + + if (OperationSupport.getInstance().EC_GEN) { + keyPair.genKeyPair(); + } else { + privKey.setS(ResourceManager.CONST_ONE, (short) 0, (short) 1); + pubKey.setW(G, (short) 0, (short) G.length); + } + + return keyPair; + } + } + + /** + * @author Vasilios Mavroudis and Petr Svenda and Antonin Dufka + */ + public static class ECPoint { + private final ResourceManager rm; + + private ECPublicKey point; + private KeyPair pointKeyPair; + private final ECCurve curve; + + /** + * Creates new ECPoint object for provided {@code curve}. Random initial point value is generated. + * + * @param curve point's elliptic curve + */ + public ECPoint(ECCurve curve) { + this.curve = curve; + this.rm = curve.rm; + updatePointObjects(); + } + + /** + * Returns length of this point in bytes. + * + * @return length of this point in bytes + */ + public short length() { + return (short) (point.getSize() / 8); + } + + /** + * Properly updates all point values in case of a change of an underlying curve. + * New random point value is generated. + */ + public final void updatePointObjects() { + pointKeyPair = curve.newKeyPair(pointKeyPair); + point = (ECPublicKey) pointKeyPair.getPublic(); + } + + /** + * Generates new random point value. + */ + public void randomize() { + if (OperationSupport.getInstance().EC_GEN) { + pointKeyPair.genKeyPair(); // Fails for some curves on some cards + } else { + BigNat tmp = rm.EC_BN_A; + rm.rng.generateData(rm.ARRAY_A, (short) 0, (short) (curve.KEY_BIT_LENGTH / 8 + 16)); + tmp.fromByteArray(rm.ARRAY_A, (short) 0, (short) (curve.KEY_BIT_LENGTH / 8 + 16)); + tmp.mod(curve.rBN); + tmp.shrink(); + point.setW(curve.G, (short) 0, (short) curve.G.length); + multiplication(tmp); + } + } + + /** + * Copy value of provided point into this. This and other point must have + * curve with same parameters, only length is checked. + * + * @param other point to be copied + */ + public void copy(ECPoint other) { + if (length() != other.length()) { + ISOException.throwIt(ReturnCodes.SW_ECPOINT_INVALIDLENGTH); + } + byte[] pointBuffer = rm.POINT_ARRAY_A; + + short len = other.getW(pointBuffer, (short) 0); + setW(pointBuffer, (short) 0, len); + } + + /** + * Set this point value (parameter W) from array with value encoded as per ANSI X9.62. + * The uncompressed form is always supported. If underlying native JavaCard implementation + * of {@code ECPublicKey} supports compressed points, then this method accepts also compressed points. + * + * @param buffer array with serialized point + * @param offset start offset within input array + * @param length length of point + */ + public void setW(byte[] buffer, short offset, short length) { + point.setW(buffer, offset, length); + } + + /** + * Returns current value of this point. + * + * @param buffer memory array where to store serailized point value + * @param offset start offset for output serialized point + * @return length of serialized point (number of bytes) + */ + public short getW(byte[] buffer, short offset) { + return point.getW(buffer, offset); + } + + /** + * Returns this point value as ECPublicKey object. No copy of point is made + * before return, so change of returned object will also change this point value. + * + * @return point as ECPublicKey object + */ + public ECPublicKey asPublicKey() { + return point; + } + + /** + * Returns curve associated with this point. No copy of curve is made + * before return, so change of returned object will also change curve for + * this point. + * + * @return curve as ECCurve object + */ + public ECCurve getCurve() { + return curve; + } + + /** + * Returns the X coordinate of this point in uncompressed form. + * + * @param buffer output array for X coordinate + * @param offset start offset within output array + * @return length of X coordinate (in bytes) + */ + public short getX(byte[] buffer, short offset) { + byte[] pointBuffer = rm.POINT_ARRAY_A; + + point.getW(pointBuffer, (short) 0); + Util.arrayCopyNonAtomic(pointBuffer, (short) 1, buffer, offset, curve.COORD_SIZE); + return curve.COORD_SIZE; + } + + /** + * Returns the Y coordinate of this point in uncompressed form. + * + * @param buffer output array for Y coordinate + * @param offset start offset within output array + * @return length of Y coordinate (in bytes) + */ + public short getY(byte[] buffer, short offset) { + byte[] pointBuffer = rm.POINT_ARRAY_A; + + point.getW(pointBuffer, (short) 0); + Util.arrayCopyNonAtomic(pointBuffer, (short) (1 + curve.COORD_SIZE), buffer, offset, curve.COORD_SIZE); + return curve.COORD_SIZE; + } + + /** + * Double this point. Pure implementation without KeyAgreement. + */ + public void swDouble() { + byte[] pointBuffer = rm.POINT_ARRAY_A; + BigNat pX = rm.EC_BN_B; + BigNat pY = rm.EC_BN_C; + BigNat lambda = rm.EC_BN_D; + BigNat tmp = rm.EC_BN_E; + + getW(pointBuffer, (short) 0); + + pX.fromByteArray(pointBuffer, (short) 1, curve.COORD_SIZE); + + pY.fromByteArray(pointBuffer, (short) (1 + curve.COORD_SIZE), curve.COORD_SIZE); + + lambda.clone(pX); + lambda.modSq(curve.pBN); + lambda.modMult(ResourceManager.THREE, curve.pBN); + lambda.modAdd(curve.aBN, curve.pBN); + + tmp.clone(pY); + tmp.modAdd(tmp, curve.pBN); + tmp.modInv(curve.pBN); + lambda.modMult(tmp, curve.pBN); + tmp.clone(lambda); + tmp.modSq(curve.pBN); + tmp.modSub(pX, curve.pBN); + tmp.modSub(pX, curve.pBN); + tmp.prependZeros(curve.COORD_SIZE, pointBuffer, (short) 1); + + tmp.modSub(pX, curve.pBN); + tmp.modMult(lambda, curve.pBN); + tmp.modAdd(pY, curve.pBN); + tmp.modNegate(curve.pBN); + tmp.prependZeros(curve.COORD_SIZE, pointBuffer, (short) (1 + curve.COORD_SIZE)); + + setW(pointBuffer, (short) 0, curve.POINT_SIZE); + } + + + /** + * Doubles the current value of this point. + */ + public void makeDouble() { + // doubling via add sometimes causes exception inside KeyAgreement engine + // this.add(this); + // Use bit slower, but more robust version via multiplication by 2 + this.multiplication(ResourceManager.TWO); + } + + /** + * Adds this (P) and provided (Q) point. Stores a resulting value into this point. + * + * @param other point to be added to this. + */ + public void add(ECPoint other) { + if (OperationSupport.getInstance().EC_HW_ADD) { + hwAdd(other); + } else { + swAdd(other); + } + } + + /** + * Implements adding of two points without ALG_EC_PACE_GM. + * + * @param other point to be added to this. + */ + private void swAdd(ECPoint other) { + boolean samePoint = this == other || isEqual(other); + if (samePoint && OperationSupport.getInstance().EC_HW_XY) { + multiplication(ResourceManager.TWO); + return; + } + + byte[] pointBuffer = rm.POINT_ARRAY_A; + BigNat xR = rm.EC_BN_B; + BigNat yR = rm.EC_BN_C; + BigNat xP = rm.EC_BN_D; + BigNat yP = rm.EC_BN_E; + BigNat xQ = rm.EC_BN_F; + BigNat nominator = rm.EC_BN_B; + BigNat denominator = rm.EC_BN_C; + BigNat lambda = rm.EC_BN_A; + + point.getW(pointBuffer, (short) 0); + xP.setSize(curve.COORD_SIZE); + xP.fromByteArray(pointBuffer, (short) 1, curve.COORD_SIZE); + yP.setSize(curve.COORD_SIZE); + yP.fromByteArray(pointBuffer, (short) (1 + curve.COORD_SIZE), curve.COORD_SIZE); + + + // l = (y_q-y_p)/(x_q-x_p)) + // x_r = l^2 - x_p -x_q + // y_r = l(x_p-x_r)-y_p + + // P + Q = R + if (samePoint) { + // lambda = (3(x_p^2)+a)/(2y_p) + // (3(x_p^2)+a) + nominator.clone(xP); + nominator.modSq(curve.pBN); + nominator.modMult(ResourceManager.THREE, curve.pBN); + nominator.modAdd(curve.aBN, curve.pBN); + // (2y_p) + denominator.clone(yP); + denominator.modMult(ResourceManager.TWO, curve.pBN); + denominator.modInv(curve.pBN); + + } else { + // lambda = (y_q-y_p) / (x_q-x_p) mod p + other.point.getW(pointBuffer, (short) 0); + xQ.setSize(curve.COORD_SIZE); + xQ.fromByteArray(pointBuffer, (short) 1, other.curve.COORD_SIZE); + nominator.setSize(curve.COORD_SIZE); + nominator.fromByteArray(pointBuffer, (short) (1 + curve.COORD_SIZE), curve.COORD_SIZE); + + nominator.mod(curve.pBN); + + nominator.modSub(yP, curve.pBN); + + // (x_q-x_p) + denominator.clone(xQ); + denominator.mod(curve.pBN); + denominator.modSub(xP, curve.pBN); + denominator.modInv(curve.pBN); + } + + lambda.clone(nominator); + lambda.modMult(denominator, curve.pBN); + + // (x_p, y_p) + (x_q, y_q) = (x_r, y_r) + // lambda = (y_q - y_p) / (x_q - x_p) + + // x_r = lambda^2 - x_p - x_q + if (samePoint) { + short len = multXKA(ResourceManager.TWO, pointBuffer, (short) 0); + xR.fromByteArray(pointBuffer, (short) 0, len); + } else { + xR.clone(lambda); + xR.modSq(curve.pBN); + xR.modSub(xP, curve.pBN); + xR.modSub(xQ, curve.pBN); + } + + // y_r = lambda(x_p - x_r) - y_p + yR.clone(xP); + yR.modSub(xR, curve.pBN); + yR.modMult(lambda, curve.pBN); + yR.modSub(yP, curve.pBN); + + pointBuffer[0] = (byte) 0x04; + // If x_r.length() and y_r.length() is smaller than curve.COORD_SIZE due to leading zeroes which were shrunk before, then we must add these back + xR.prependZeros(curve.COORD_SIZE, pointBuffer, (short) 1); + yR.prependZeros(curve.COORD_SIZE, pointBuffer, (short) (1 + curve.COORD_SIZE)); + setW(pointBuffer, (short) 0, curve.POINT_SIZE); + } + + /** + * Implements adding of two points via ALG_EC_PACE_GM. + * + * @param other point to be added to this. + */ + private void hwAdd(ECPoint other) { + byte[] pointBuffer = rm.POINT_ARRAY_A; + + setW(pointBuffer, (short) 0, multAndAddKA(ResourceManager.ONE_COORD, other, pointBuffer, (short) 0)); + } + + /** + * Multiply value of this point by provided scalar. Stores the result into this point. + * + * @param scalarBytes value of scalar for multiplication + */ + public void multiplication(byte[] scalarBytes, short scalarOffset, short scalarLen) { + BigNat scalar = rm.EC_BN_F; + + scalar.setSize(scalarLen); + scalar.fromByteArray(scalarBytes, scalarOffset, scalarLen); + multiplication(scalar); + } + + /** + * Multiply value of this point by provided scalar. Stores the result into this point. + * + * @param scalar value of scalar for multiplication + */ + public void multiplication(BigNat scalar) { + if (OperationSupport.getInstance().EC_SW_DOUBLE && scalar.equals(ResourceManager.TWO)) { + swDouble(); + // } else if (rm.ecMultKA.getAlgorithm() == KeyAgreement.ALG_EC_SVDP_DH_PLAIN_XY) { + } else if (rm.ecMultKA.getAlgorithm() == (byte) 6) { + multXY(scalar); + //} else if (rm.ecMultKA.getAlgorithm() == KeyAgreement.ALG_EC_SVDP_DH_PLAIN) { + } else if (rm.ecMultKA.getAlgorithm() == (byte) 3) { + multX(scalar); + } else { + ISOException.throwIt(ReturnCodes.SW_OPERATION_NOT_SUPPORTED); + } + } + + /** + * Multiply this point by a given scalar and add another point to the result. + * + * @param scalar value of scalar for multiplication + * @param point the other point + */ + public void multAndAdd(BigNat scalar, ECPoint point) { + if (OperationSupport.getInstance().EC_HW_ADD) { + byte[] pointBuffer = rm.POINT_ARRAY_A; + + setW(pointBuffer, (short) 0, multAndAddKA(scalar, point, pointBuffer, (short) 0)); + } else { + multiplication(scalar); + add(point); + } + } + + /** + * Multiply this point by a given scalar and add another point to the result and store the result into outBuffer. + * + * @param scalar value of scalar for multiplication + * @param point the other point + * @param outBuffer output buffer + * @param outBufferOffset offset in the output buffer + */ + private short multAndAddKA(BigNat scalar, ECPoint point, byte[] outBuffer, short outBufferOffset) { + byte[] pointBuffer = rm.POINT_ARRAY_B; + + short len = getW(pointBuffer, (short) 0); + curve.disposablePriv.setG(pointBuffer, (short) 0, len); + scalar.prependZeros((short) curve.r.length, pointBuffer, (short) 0); + curve.disposablePriv.setS(pointBuffer, (short) 0, (short) curve.r.length); + rm.ecAddKA.init(curve.disposablePriv); + + len = point.getW(pointBuffer, (short) 0); + len = rm.ecAddKA.generateSecret(pointBuffer, (short) 0, len, outBuffer, outBufferOffset); + return len; + } + + /** + * Multiply value of this point by provided scalar using XY key agreement. Stores the result into this point. + * + * @param scalar value of scalar for multiplication + */ + public void multXY(BigNat scalar) { + byte[] pointBuffer = rm.POINT_ARRAY_A; + + short len = multXYKA(scalar, pointBuffer, (short) 0); + setW(pointBuffer, (short) 0, len); + } + + /** + * Multiplies this point value with provided scalar and stores result into + * provided array. No modification of this point is performed. + * Native XY KeyAgreement engine is used. + * + * @param scalar value of scalar for multiplication + * @param outBuffer output array for resulting value + * @param outBufferOffset offset within output array + * @return length of resulting value (in bytes) + */ + public short multXYKA(BigNat scalar, byte[] outBuffer, short outBufferOffset) { + byte[] pointBuffer = rm.POINT_ARRAY_B; + + scalar.prependZeros((short) curve.r.length, pointBuffer, (short) 0); + curve.disposablePriv.setS(pointBuffer, (short) 0, (short) curve.r.length); + rm.ecMultKA.init(curve.disposablePriv); + + short len = getW(pointBuffer, (short) 0); + len = rm.ecMultKA.generateSecret(pointBuffer, (short) 0, len, outBuffer, outBufferOffset); + return len; + } + + /** + * Multiply value of this point by provided scalar using X-only key agreement. Stores the result into this point. + * + * @param scalar value of scalar for multiplication + */ + private void multX(BigNat scalar) { + byte[] pointBuffer = rm.POINT_ARRAY_A; + byte[] pointBuffer2 = rm.POINT_ARRAY_B; + byte[] resultBuffer = rm.ARRAY_A; + BigNat x = rm.EC_BN_B; + BigNat ySq = rm.EC_BN_C; + BigNat y = rm.EC_BN_D; + BigNat lambda = rm.EC_BN_E; + BigNat tmp = rm.EC_BN_F; + BigNat denominator = rm.EC_BN_D; + + short len = multXKA(scalar, pointBuffer, (short) 0); + x.fromByteArray(pointBuffer, (short) 0, len); + + // Solve for Y in Weierstrass equation: Y^2 = X^3 + XA + B = x(x^2+A)+B + ySq.clone(x); + ySq.modExp(ResourceManager.TWO, curve.pBN); + ySq.modAdd(curve.aBN, curve.pBN); + ySq.modMult(x, curve.pBN); + ySq.modAdd(curve.bBN, curve.pBN); + y.clone(ySq); + y.modSqrt(curve.pBN); + + // Construct public key with + pointBuffer[0] = 0x04; + x.prependZeros(curve.COORD_SIZE, pointBuffer, (short) 1); + y.prependZeros(curve.COORD_SIZE, pointBuffer, (short) (1 + curve.COORD_SIZE)); + + boolean negate; + if (OperationSupport.getInstance().EC_HW_X_ECDSA) { + getW(pointBuffer2, (short) 0); + curve.disposablePriv.setG(pointBuffer2, (short) 0, curve.POINT_SIZE); + curve.disposablePub.setG(pointBuffer2, (short) 0, curve.POINT_SIZE); + + setW(pointBuffer, (short) 0, curve.POINT_SIZE); + + // Check if corresponds to the "secret" (i.e., our scalar) + scalar.prependZeros((short) curve.r.length, resultBuffer, (short) 0); + curve.disposablePriv.setS(resultBuffer, (short) 0, (short) curve.r.length); + curve.disposablePub.setW(pointBuffer, (short) 0, curve.POINT_SIZE); + negate = !SignVerifyECDSA(curve.disposablePriv, curve.disposablePub, rm.verifyEcdsa, resultBuffer); + } else { + // Check that ( + P)_x == ((scalar + 1)P)_x + scalar.increment(); + len = multXKA(scalar, resultBuffer, (short) 0); + x.fromByteArray(resultBuffer, (short) 0, len); + scalar.decrement(); // keep the original + + getW(pointBuffer2, (short) 0); + setW(pointBuffer, (short) 0, curve.POINT_SIZE); + + // y_1 - y_2 + lambda.fromByteArray(pointBuffer2, (short) (1 + curve.COORD_SIZE), curve.COORD_SIZE); + tmp.fromByteArray(pointBuffer, (short) (1 + curve.COORD_SIZE), curve.COORD_SIZE); + lambda.modSub(tmp, curve.pBN); + + // (x_1 - x_2)^-1 + denominator.fromByteArray(pointBuffer2, (short) 1, curve.COORD_SIZE); + tmp.fromByteArray(pointBuffer, (short) 1, curve.COORD_SIZE); + denominator.modSub(tmp, curve.pBN); + denominator.modInv(curve.pBN); + + // λ = (y_1 - y_2)/(x_1 - x_2) + lambda.modMult(denominator, curve.pBN); + + // x_3 = λ^2 - x_1 - x_2 + lambda.modSq(curve.pBN); + tmp.fromByteArray(pointBuffer2, (short) 1, curve.COORD_SIZE); + lambda.modSub(tmp, curve.pBN); + tmp.fromByteArray(pointBuffer, (short) 1, curve.COORD_SIZE); + lambda.modSub(tmp, curve.pBN); + + // If + P != (scalar + 1)P, negate the point + negate = !lambda.equals(x); + } + if (negate) + negate(); + } + + /** + * Multiplies this point value with provided scalar and stores result into + * provided array. No modification of this point is performed. + * Native X-only KeyAgreement engine is used. + * + * @param scalar value of scalar for multiplication + * @param outBuffer output array for resulting value + * @param outBufferOffset offset within output array + * @return length of resulting value (in bytes) + */ + private short multXKA(BigNat scalar, byte[] outBuffer, short outBufferOffset) { + byte[] pointBuffer = rm.POINT_ARRAY_B; + // NOTE: potential problem on real cards (j2e) - when small scalar is used (e.g., BigNat.TWO), operation sometimes freezes + scalar.prependZeros((short) curve.r.length, pointBuffer, (short) 0); + curve.disposablePriv.setS(pointBuffer, (short) 0, (short) curve.r.length); + + rm.ecMultKA.init(curve.disposablePriv); + + short len = getW(pointBuffer, (short) 0); + rm.ecMultKA.generateSecret(pointBuffer, (short) 0, len, outBuffer, outBufferOffset); + // Return always length of whole coordinate X instead of len - some real cards returns shorter value equal to SHA-1 output size although PLAIN results is filled into buffer (GD60) + return curve.COORD_SIZE; + } + + /** + * Computes negation of this point. + * The operation will dump point into uncompressed_point_arr, negate Y and restore back + */ + public void negate() { + byte[] pointBuffer = rm.POINT_ARRAY_A; + BigNat y = rm.EC_BN_C; + + point.getW(pointBuffer, (short) 0); + y.setSize(curve.COORD_SIZE); + y.fromByteArray(pointBuffer, (short) (1 + curve.COORD_SIZE), curve.COORD_SIZE); + y.modNegate(curve.pBN); + y.prependZeros(curve.COORD_SIZE, pointBuffer, (short) (1 + curve.COORD_SIZE)); + setW(pointBuffer, (short) 0, curve.POINT_SIZE); + } + + /** + * Restore point from X coordinate. Stores one of the two results into this point. + * + * @param xCoord byte array containing the X coordinate + * @param xOffset offset in the byte array + * @param xLen length of the X coordinate + */ + public void fromX(byte[] xCoord, short xOffset, short xLen) { + BigNat x = rm.EC_BN_F; + + x.setSize(xLen); + x.fromByteArray(xCoord, xOffset, xLen); + fromX(x); + } + + /** + * Restore point from X coordinate. Stores one of the two results into this point. + * + * @param x the x coordinate + */ + private void fromX(BigNat x) { + BigNat ySq = rm.EC_BN_C; + BigNat y = rm.EC_BN_D; + byte[] pointBuffer = rm.POINT_ARRAY_A; + + //Y^2 = X^3 + XA + B = x(x^2+A)+B + ySq.clone(x); + ySq.modSq(curve.pBN); + ySq.modAdd(curve.aBN, curve.pBN); + ySq.modMult(x, curve.pBN); + ySq.modAdd(curve.bBN, curve.pBN); + y.clone(ySq); + y.modSqrt(curve.pBN); + + // Construct public key with + pointBuffer[0] = 0x04; + x.prependZeros(curve.COORD_SIZE, pointBuffer, (short) 1); + y.prependZeros(curve.COORD_SIZE, pointBuffer, (short) (1 + curve.COORD_SIZE)); + setW(pointBuffer, (short) 0, curve.POINT_SIZE); + } + + /** + * Returns true if Y coordinate is even; false otherwise. + * + * @return true if Y coordinate is even; false otherwise + */ + public boolean isYEven() { + byte[] pointBuffer = rm.POINT_ARRAY_A; + + point.getW(pointBuffer, (short) 0); + boolean result = pointBuffer[(short) (curve.POINT_SIZE - 1)] % 2 == 0; + return result; + } + + /** + * Compares this and provided point for equality. The comparison is made using hash of both values to prevent leak of position of mismatching byte. + * + * @param other second point for comparison + * @return true if both point are exactly equal (same length, same value), false otherwise + */ + public boolean isEqual(ECPoint other) { + if (length() != other.length()) { + return false; + } + // The comparison is made with hash of point values instead of directly values. + // This way, offset of first mismatching byte is not leaked via timing side-channel. + // Additionally, only single array is required for storage of plain point values thus saving some RAM. + byte[] pointBuffer = rm.POINT_ARRAY_A; + byte[] hashBuffer = rm.HASH_ARRAY; + + short len = getW(pointBuffer, (short) 0); + rm.hashEngine.doFinal(pointBuffer, (short) 0, len, hashBuffer, (short) 0); + len = other.getW(pointBuffer, (short) 0); + len = rm.hashEngine.doFinal(pointBuffer, (short) 0, len, pointBuffer, (short) 0); + boolean bResult = Util.arrayCompare(hashBuffer, (short) 0, pointBuffer, (short) 0, len) == 0; + + return bResult; + } + + static byte[] msg = {(byte) 0x01, (byte) 0x01, (byte) 0x02, (byte) 0x03}; + + public static boolean SignVerifyECDSA(ECPrivateKey privateKey, ECPublicKey publicKey, Signature signEngine, byte[] tmpSignArray) { + signEngine.init(privateKey, Signature.MODE_SIGN); + short signLen = signEngine.sign(msg, (short) 0, (short) msg.length, tmpSignArray, (short) 0); + signEngine.init(publicKey, Signature.MODE_VERIFY); + return signEngine.verify(msg, (short) 0, (short) msg.length, tmpSignArray, (short) 0, signLen); + } + + + /** + * Decode SEC1-encoded point and load it into this. + * + * @param point array containing SEC1-encoded point + * @param offset offset within the output buffer + * @param length length of the encoded point + * @return true if the point was compressed; false otherwise + */ + public boolean decode(byte[] point, short offset, short length) { + if(length == (short) (1 + 2 * curve.COORD_SIZE) && point[offset] == 0x04) { + setW(point, offset, length); + return false; + } + if (length == (short) (1 + curve.COORD_SIZE)) { + BigNat y = rm.EC_BN_C; + BigNat x = rm.EC_BN_D; + BigNat p = rm.EC_BN_E; + byte[] pointBuffer = rm.POINT_ARRAY_A; + + x.fromByteArray(point, (short) (offset + 1), curve.COORD_SIZE); + + //Y^2 = X^3 + XA + B = x(x^2+A)+B + y.clone(x); + y.modSq(curve.pBN); + y.modAdd(curve.aBN, curve.pBN); + y.modMult(x, curve.pBN); + y.modAdd(curve.bBN, curve.pBN); + y.modSqrt(curve.pBN); + + pointBuffer[0] = 0x04; + x.prependZeros(curve.COORD_SIZE, pointBuffer, (short) 1); + + boolean odd = y.isOdd(); + if ((!odd && point[offset] != (byte) 0x02) || (odd && point[offset] != (byte) 0x03)) { + p.clone(curve.pBN); + p.subtract(y); + p.prependZeros(curve.COORD_SIZE, pointBuffer, (short) (curve.COORD_SIZE + 1)); + } else { + y.prependZeros(curve.COORD_SIZE, pointBuffer, (short) (curve.COORD_SIZE + 1)); + } + setW(pointBuffer, (short) 0, curve.POINT_SIZE); + return true; + } + ISOException.throwIt(ReturnCodes.SW_ECPOINT_INVALID); + return true; // unreachable + } + + /** + * Encode this point into the output buffer. + * + * @param output output buffer; MUST be able to store offset + uncompressed size bytes + * @param offset offset within the output buffer + * @param compressed output compressed point if true; uncompressed otherwise + * @return length of output point + */ + public short encode(byte[] output, short offset, boolean compressed) { + getW(output, offset); + + if(compressed) { + if(output[offset] == (byte) 0x04) { + output[offset] = (byte) (((output[(short) (offset + 2 * curve.COORD_SIZE)] & 0xff) % 2) == 0 ? 2 : 3); + } + return (short) (curve.COORD_SIZE + 1); + } + + if(output[offset] != (byte) 0x04) { + BigNat y = rm.EC_BN_C; + BigNat x = rm.EC_BN_D; + BigNat p = rm.EC_BN_E; + x.fromByteArray(output, (short) (offset + 1), curve.COORD_SIZE); + + //Y^2 = X^3 + XA + B = x(x^2+A)+B + y.clone(x); + y.modSq(curve.pBN); + y.modAdd(curve.aBN, curve.pBN); + y.modMult(x, curve.pBN); + y.modAdd(curve.bBN, curve.pBN); + y.modSqrt(curve.pBN); + boolean odd = y.isOdd(); + if ((!odd && output[offset] != (byte) 0x02) || (odd && output[offset] != (byte) 0x03)) { + p.clone(curve.pBN); + p.subtract(y); + p.prependZeros(curve.COORD_SIZE, output, (short) (offset + curve.COORD_SIZE + 1)); + } else { + y.prependZeros(curve.COORD_SIZE, output, (short) (offset + curve.COORD_SIZE + 1)); + } + output[offset] = (byte) 0x04; + } + return (short) (2 * curve.COORD_SIZE + 1); + } + + + + // + // ECKey methods + // + public void setFieldFP(byte[] bytes, short s, short s1) throws CryptoException { + point.setFieldFP(bytes, s, s1); + } + + public void setFieldF2M(short s) throws CryptoException { + point.setFieldF2M(s); + } + + public void setFieldF2M(short s, short s1, short s2) throws CryptoException { + point.setFieldF2M(s, s1, s2); + } + + public void setA(byte[] bytes, short s, short s1) throws CryptoException { + point.setA(bytes, s, s1); + } + + public void setB(byte[] bytes, short s, short s1) throws CryptoException { + point.setB(bytes, s, s1); + } + + public void setG(byte[] bytes, short s, short s1) throws CryptoException { + point.setG(bytes, s, s1); + } + + public void setR(byte[] bytes, short s, short s1) throws CryptoException { + point.setR(bytes, s, s1); + } + + public void setK(short s) { + point.setK(s); + } + + public short getField(byte[] bytes, short s) throws CryptoException { + return point.getField(bytes, s); + } + + public short getA(byte[] bytes, short s) throws CryptoException { + return point.getA(bytes, s); + } + + public short getB(byte[] bytes, short s) throws CryptoException { + return point.getB(bytes, s); + } + + public short getG(byte[] bytes, short s) throws CryptoException { + return point.getG(bytes, s); + } + + public short getR(byte[] bytes, short s) throws CryptoException { + return point.getR(bytes, s); + } + + public short getK() throws CryptoException { + return point.getK(); + } + } + + /** + * The control point for unified allocation of arrays and objects with customable + * specification of allocator type (RAM/EEPROM) for particular array. Allows for + * quick personalization and optimization of memory use when compiling for cards + * with more/less available memory. + * + * @author Petr Svenda + */ + public static class ObjectAllocator { + short allocatedInRAM = 0; + short allocatedInEEPROM = 0; + byte[] ALLOCATOR_TYPE_ARRAY; + + public static final byte ARRAY_A = 0; + public static final byte ARRAY_B = 1; + public static final byte BN_WORD = 2; + public static final byte BN_A = 3; + public static final byte BN_B = 4; + public static final byte BN_C = 5; + public static final byte BN_D = 6; + public static final byte BN_E = 7; + public static final byte BN_F = 8; + public static final byte BN_G = 9; + + public static final byte EC_BN_A = 10; + public static final byte EC_BN_B = 11; + public static final byte EC_BN_C = 12; + public static final byte EC_BN_D = 13; + public static final byte EC_BN_E = 14; + public static final byte EC_BN_F = 15; + public static final byte POINT_ARRAY_A = 16; + public static final byte POINT_ARRAY_B = 17; + public static final byte HASH_ARRAY = 18; + + public static final short ALLOCATOR_TYPE_ARRAY_LENGTH = (short) (HASH_ARRAY + 1); + + /** + * Creates new allocator control object, resets performance counters + */ + public ObjectAllocator() { + ALLOCATOR_TYPE_ARRAY = new byte[ALLOCATOR_TYPE_ARRAY_LENGTH]; + setAllAllocatorsRAM(); + resetAllocatorCounters(); + } + /** + * All type of allocator for all object as EEPROM + */ + public final void setAllAllocatorsEEPROM() { + Util.arrayFillNonAtomic(ALLOCATOR_TYPE_ARRAY, (short) 0, (short) ALLOCATOR_TYPE_ARRAY.length, JCSystem.MEMORY_TYPE_PERSISTENT); + } + /** + * All type of allocator for all object as RAM + */ + public void setAllAllocatorsRAM() { + Util.arrayFillNonAtomic(ALLOCATOR_TYPE_ARRAY, (short) 0, (short) ALLOCATOR_TYPE_ARRAY.length, JCSystem.MEMORY_TYPE_TRANSIENT_RESET); + } + /** + * All type of allocator for selected object as RAM (faster), rest EEPROM (saving RAM) + * The current settings is heuristically obtained from measurements of performance of Bignat and ECPoint operations + */ + public void setAllocatorsTradeoff() { + // Set initial allocators into EEPROM + setAllAllocatorsEEPROM(); + + // Put only the most perfromance relevant ones into RAM + ALLOCATOR_TYPE_ARRAY[ARRAY_A] = JCSystem.MEMORY_TYPE_TRANSIENT_RESET; + ALLOCATOR_TYPE_ARRAY[ARRAY_B] = JCSystem.MEMORY_TYPE_TRANSIENT_RESET; + ALLOCATOR_TYPE_ARRAY[BN_WORD] = JCSystem.MEMORY_TYPE_TRANSIENT_RESET; + ALLOCATOR_TYPE_ARRAY[BN_A] = JCSystem.MEMORY_TYPE_TRANSIENT_RESET; + ALLOCATOR_TYPE_ARRAY[BN_B] = JCSystem.MEMORY_TYPE_TRANSIENT_RESET; + ALLOCATOR_TYPE_ARRAY[BN_C] = JCSystem.MEMORY_TYPE_TRANSIENT_RESET; + ALLOCATOR_TYPE_ARRAY[BN_D] = JCSystem.MEMORY_TYPE_TRANSIENT_RESET; + ALLOCATOR_TYPE_ARRAY[BN_E] = JCSystem.MEMORY_TYPE_TRANSIENT_RESET; + ALLOCATOR_TYPE_ARRAY[BN_F] = JCSystem.MEMORY_TYPE_TRANSIENT_RESET; + ALLOCATOR_TYPE_ARRAY[BN_G] = JCSystem.MEMORY_TYPE_TRANSIENT_RESET; + ALLOCATOR_TYPE_ARRAY[EC_BN_B] = JCSystem.MEMORY_TYPE_TRANSIENT_RESET; + ALLOCATOR_TYPE_ARRAY[EC_BN_C] = JCSystem.MEMORY_TYPE_TRANSIENT_RESET; + ALLOCATOR_TYPE_ARRAY[POINT_ARRAY_A] = JCSystem.MEMORY_TYPE_TRANSIENT_RESET; + ALLOCATOR_TYPE_ARRAY[POINT_ARRAY_B] = JCSystem.MEMORY_TYPE_TRANSIENT_RESET; + } + + /** + * Allocates new byte[] array with provided length either in RAM or EEPROM based on an allocator type. + * Method updates internal counters of bytes allocated with specific allocator. Use {@code getAllocatedInRAM()} + * or {@code getAllocatedInEEPROM} for counters readout. + * @param length length of array + * @param allocatorType type of allocator + * @return allocated array + */ + public byte[] allocateByteArray(short length, byte allocatorType) { + switch (allocatorType) { + case JCSystem.MEMORY_TYPE_PERSISTENT: + allocatedInEEPROM += length; + return new byte[length]; + case JCSystem.MEMORY_TYPE_TRANSIENT_RESET: + allocatedInRAM += length; + return JCSystem.makeTransientByteArray(length, JCSystem.CLEAR_ON_RESET); + case JCSystem.MEMORY_TYPE_TRANSIENT_DESELECT: + allocatedInRAM += length; + return JCSystem.makeTransientByteArray(length, JCSystem.CLEAR_ON_DESELECT); + } + return null; + } + + /** + * Allocates new short[] array with provided length either in RAM or EEPROM based on an allocator type. + * Method updates internal counters of bytes allocated with specific allocator. Use {@code getAllocatedInRAM()} + * or {@code getAllocatedInEEPROM} for counters readout. + * @param length length of array + * @param allocatorType type of allocator + * @return allocated array + */ + public short[] allocateShortArray(short length, byte allocatorType) { + switch (allocatorType) { + case JCSystem.MEMORY_TYPE_PERSISTENT: + allocatedInEEPROM += (short) (2 * length); + return new short[length]; + case JCSystem.MEMORY_TYPE_TRANSIENT_RESET: + allocatedInRAM += (short) (2 * length); + return JCSystem.makeTransientShortArray(length, JCSystem.CLEAR_ON_RESET); + case JCSystem.MEMORY_TYPE_TRANSIENT_DESELECT: + allocatedInRAM += (short) (2 * length); + return JCSystem.makeTransientShortArray(length, JCSystem.CLEAR_ON_DESELECT); + } + return null; + } + + /** + * Returns pre-set allocator type for provided object identified by unique objectAllocatorID + * @param objectAllocatorID unique id of target object + * @return allocator type + */ + public byte getAllocatorType(short objectAllocatorID) { + if (objectAllocatorID >= 0 && objectAllocatorID <= (short) ALLOCATOR_TYPE_ARRAY.length) { + return ALLOCATOR_TYPE_ARRAY[objectAllocatorID]; + } else { + ISOException.throwIt(ReturnCodes.SW_ALLOCATOR_INVALIDOBJID); + return -1; + } + } + + /** + * Returns number of bytes allocated in RAM via {@code allocateByteArray()} since last reset of counters. + * @return number of bytes allocated in RAM via this control object + */ + public short getAllocatedInRAM() { + return allocatedInRAM; + } + /** + * Returns number of bytes allocated in EEPROM via {@code allocateByteArray()} + * since last reset of counters. + * + * @return number of bytes allocated in EEPROM via this control object + */ + public short getAllocatedInEEPROM() { + return allocatedInEEPROM; + } + /** + * Resets counters of allocated bytes in RAM and EEPROM + */ + public final void resetAllocatorCounters() { + allocatedInRAM = 0; + allocatedInEEPROM = 0; + } + } + + /** + * OperationSupport class + * + * @author Antonin Dufka + */ + public static class OperationSupport { + private static OperationSupport instance; + + public static final short SIMULATOR = 0x0000; // jCardSim.org simulator + public static final short JCOP21 = 0x0001; // NXP J2E145G + public static final short JCOP3_P60 = 0x0002; // NXP JCOP3 J3H145 P60 + public static final short JCOP4_P71 = 0x0003; // NXP JCOP4 J3Rxxx P71 + public static final short GD60 = 0x0004; // G+D Sm@rtcafe 6.0 + public static final short GD70 = 0x0005; // G+D Sm@rtcafe 7.0 + public static final short SECORA = 0x0006; // Infineon Secora ID S + + public short MIN_RSA_BIT_LENGTH = 512; + public boolean DEFERRED_INITIALIZATION = false; + + public boolean RSA_EXP = true; + public boolean RSA_SQ = true; + public boolean RSA_PUB = false; + public boolean RSA_CHECK_ONE = false; + public boolean RSA_CHECK_EXP_ONE = false; + public boolean RSA_KEY_REFRESH = false; + public boolean RSA_PREPEND_ZEROS = false; + public boolean RSA_EXTRA_MOD = false; + public boolean RSA_RESIZE_MOD = true; + public boolean RSA_APPEND_MOD = false; + + public boolean EC_HW_XY = false; + public boolean EC_HW_X = true; + public boolean EC_HW_ADD = false; + public boolean EC_SW_DOUBLE = false; + public boolean EC_PRECISE_BITLENGTH = true; + public boolean EC_SET_COFACTOR = false; + public boolean EC_GEN = true; + public boolean EC_HW_X_ECDSA = true; + + private OperationSupport() { + } + + public static OperationSupport getInstance() { + if (OperationSupport.instance == null) OperationSupport.instance = new OperationSupport(); + return OperationSupport.instance; + } + + public void setCard(short card_identifier) { + switch (card_identifier) { + case SIMULATOR: + RSA_KEY_REFRESH = true; + RSA_PREPEND_ZEROS = true; + RSA_RESIZE_MOD = false; + EC_HW_XY = true; + EC_HW_ADD = true; + EC_SW_DOUBLE = true; + EC_PRECISE_BITLENGTH = false; + break; + case JCOP21: + RSA_PUB = true; + RSA_EXTRA_MOD = true; + RSA_APPEND_MOD = true; + EC_SW_DOUBLE = true; + EC_GEN = false; // required by Wei25519 + EC_HW_X_ECDSA = false; // required by Wei25519 + break; + case GD60: + RSA_PUB = true; + RSA_EXTRA_MOD = true; + RSA_APPEND_MOD = true; + break; + case GD70: + RSA_PUB = true; + RSA_CHECK_ONE = true; + RSA_EXTRA_MOD = true; + RSA_APPEND_MOD = true; + break; + case JCOP3_P60: + DEFERRED_INITIALIZATION = true; + RSA_PUB = true; + EC_HW_XY = true; + EC_HW_ADD = true; + break; + case JCOP4_P71: + DEFERRED_INITIALIZATION = true; + EC_HW_XY = true; + EC_HW_ADD = true; + break; + case SECORA: + MIN_RSA_BIT_LENGTH = 1024; + RSA_SQ = false; + RSA_CHECK_EXP_ONE = true; + RSA_PUB = true; + RSA_EXTRA_MOD = true; + RSA_APPEND_MOD = true; + EC_HW_XY = true; + EC_PRECISE_BITLENGTH = false; + break; + default: + break; + } + } + } + + /** + * @author Petr Svenda + */ + public static class ResourceManager { + public ObjectAllocator memAlloc; + + RandomData rng; + MessageDigest hashEngine; + KeyAgreement ecMultKA; + KeyAgreement ecAddKA; + Signature verifyEcdsa; + Cipher sqCiph, modSqCiph, expCiph; + RSAPublicKey sqPub, modSqPub, expPub; + RSAPrivateKey sqPriv, modSqPriv, expPriv; + BigNat fixedMod; + + byte[] ARRAY_A, ARRAY_B, POINT_ARRAY_A, POINT_ARRAY_B, HASH_ARRAY; + + static byte[] CONST_ONE = {0x01}; + static byte[] CONST_TWO = {0x02}; + + BigNat BN_WORD; + BigNat BN_A, BN_B, BN_C, BN_D, BN_E, BN_F, BN_G; + BigNat EC_BN_A, EC_BN_B, EC_BN_C, EC_BN_D, EC_BN_E, EC_BN_F; + public static BigNat TWO, THREE, ONE_COORD; + + public final short MAX_EXP_BIT_LENGTH; + public final short MAX_EXP_LENGTH; + public final short MAX_SQ_BIT_LENGTH; + public final short MAX_SQ_LENGTH; + public final short MAX_BIGNAT_SIZE; + public final short MAX_POINT_SIZE; + public final short MAX_COORD_SIZE; + + public ResourceManager(short maxEcLength) { + short min = OperationSupport.getInstance().MIN_RSA_BIT_LENGTH; + if (maxEcLength <= (short) 256) { + MAX_EXP_BIT_LENGTH = (short) 512 < min ? min : (short) 512; + MAX_SQ_BIT_LENGTH = (short) 768 < min ? min : (short) 768; + MAX_POINT_SIZE = (short) 64; + } + else if (maxEcLength <= (short) 384) { + MAX_EXP_BIT_LENGTH = (short) 768 < min ? min : (short) 768; + MAX_SQ_BIT_LENGTH = (short) 1024 < min ? min : (short) 1024; + MAX_POINT_SIZE = (short) 96; + } + else if (maxEcLength <= (short) 512) { + MAX_EXP_BIT_LENGTH = (short) 1024 < min ? min : (short) 1024; + MAX_SQ_BIT_LENGTH = (short) 1280 < min ? min : (short) 1280; + MAX_POINT_SIZE = (short) 128; + } + else { + MAX_EXP_BIT_LENGTH = (short) 0; + MAX_SQ_BIT_LENGTH = (short) 0; + MAX_POINT_SIZE = (short) 0; + ISOException.throwIt(ReturnCodes.SW_ECPOINT_INVALIDLENGTH); + } + MAX_SQ_LENGTH = (short) (MAX_SQ_BIT_LENGTH / 8); + MAX_EXP_LENGTH = (short) (MAX_EXP_BIT_LENGTH / 8); + MAX_BIGNAT_SIZE = (short) (MAX_EXP_BIT_LENGTH / 8); + MAX_COORD_SIZE = (short) (MAX_POINT_SIZE / 2); + + memAlloc = new ObjectAllocator(); + memAlloc.setAllAllocatorsRAM(); + // if required, memory for helper objects and arrays can be in persistent memory to save RAM (or some tradeoff) + // ObjectAllocator.setAllAllocatorsEEPROM(); + // ObjectAllocator.setAllocatorsTradeoff(); + + + ARRAY_A = memAlloc.allocateByteArray(MAX_SQ_LENGTH, memAlloc.getAllocatorType(ObjectAllocator.ARRAY_A)); + ARRAY_B = memAlloc.allocateByteArray(MAX_SQ_LENGTH, memAlloc.getAllocatorType(ObjectAllocator.ARRAY_B)); + POINT_ARRAY_A = memAlloc.allocateByteArray((short) (MAX_POINT_SIZE + 1), memAlloc.getAllocatorType(ObjectAllocator.POINT_ARRAY_A)); + POINT_ARRAY_B = memAlloc.allocateByteArray((short) (MAX_POINT_SIZE + 1), memAlloc.getAllocatorType(ObjectAllocator.POINT_ARRAY_B)); + hashEngine = MessageDigest.getInstance(MessageDigest.ALG_SHA_256, false); + HASH_ARRAY = memAlloc.allocateByteArray(hashEngine.getLength(), memAlloc.getAllocatorType(ObjectAllocator.HASH_ARRAY)); + + BN_WORD = new BigNat((short) 2, memAlloc.getAllocatorType(ObjectAllocator.BN_WORD), this); + + BN_A = new BigNat(MAX_BIGNAT_SIZE, memAlloc.getAllocatorType(ObjectAllocator.BN_A), this); + BN_B = new BigNat(MAX_BIGNAT_SIZE, memAlloc.getAllocatorType(ObjectAllocator.BN_B), this); + BN_C = new BigNat(MAX_BIGNAT_SIZE, memAlloc.getAllocatorType(ObjectAllocator.BN_C), this); + BN_D = new BigNat(MAX_BIGNAT_SIZE, memAlloc.getAllocatorType(ObjectAllocator.BN_D), this); + BN_E = new BigNat(MAX_BIGNAT_SIZE, memAlloc.getAllocatorType(ObjectAllocator.BN_E), this); + BN_F = new BigNat(MAX_SQ_LENGTH, memAlloc.getAllocatorType(ObjectAllocator.BN_F), this); + BN_G = new BigNat(MAX_SQ_LENGTH, memAlloc.getAllocatorType(ObjectAllocator.BN_G), this); + + EC_BN_A = new BigNat(MAX_POINT_SIZE, memAlloc.getAllocatorType(ObjectAllocator.EC_BN_A), this); + EC_BN_B = new BigNat(MAX_COORD_SIZE, memAlloc.getAllocatorType(ObjectAllocator.EC_BN_B), this); + EC_BN_C = new BigNat(MAX_COORD_SIZE, memAlloc.getAllocatorType(ObjectAllocator.EC_BN_C), this); + EC_BN_D = new BigNat(MAX_COORD_SIZE, memAlloc.getAllocatorType(ObjectAllocator.EC_BN_D), this); + EC_BN_E = new BigNat(MAX_COORD_SIZE, memAlloc.getAllocatorType(ObjectAllocator.EC_BN_E), this); + EC_BN_F = new BigNat(MAX_COORD_SIZE, memAlloc.getAllocatorType(ObjectAllocator.EC_BN_F), this); + + // Allocate BN constants always in EEPROM (only reading) + TWO = new BigNat((short) 1, JCSystem.MEMORY_TYPE_PERSISTENT, this); + TWO.setValue((byte) 2); + THREE = new BigNat((short) 1, JCSystem.MEMORY_TYPE_PERSISTENT, this); + THREE.setValue((byte) 3); + ONE_COORD = new BigNat(MAX_COORD_SIZE, JCSystem.MEMORY_TYPE_PERSISTENT, this); + ONE_COORD.setValue((byte) 1); + // ECC Helpers + if (OperationSupport.getInstance().EC_HW_XY) { + // ecMultKA = KeyAgreement.getInstance(KeyAgreement.ALG_EC_SVDP_DH_PLAIN_XY, false); + ecMultKA = KeyAgreement.getInstance((byte) 6, false); + } else if (OperationSupport.getInstance().EC_HW_X) { + // ecMultKA = KeyAgreement.getInstance(KeyAgreement.ALG_EC_SVDP_DH_PLAIN, false); + ecMultKA = KeyAgreement.getInstance((byte) 3, false); + } + // verifyEcdsa = Signature.getInstance(Signature.ALG_ECDSA_SHA_256, false); + verifyEcdsa = Signature.getInstance((byte) 33, false); + if (OperationSupport.getInstance().EC_HW_ADD) { + // ecAddKA = KeyAgreement.getInstance(KeyAgreement.ALG_EC_PACE_GM, false); + ecAddKA = KeyAgreement.getInstance((byte) 5, false); + } + + // RSA Sq Helpers + if (OperationSupport.getInstance().RSA_SQ) { + Util.arrayFillNonAtomic(ARRAY_A, (short) 0, MAX_SQ_LENGTH, (byte) 0xff); + sqCiph = Cipher.getInstance(Cipher.ALG_RSA_NOPAD, false); + modSqCiph = Cipher.getInstance(Cipher.ALG_RSA_NOPAD, false); + if (OperationSupport.getInstance().RSA_PUB) { + modSqPub = (RSAPublicKey) KeyBuilder.buildKey(KeyBuilder.TYPE_RSA_PUBLIC, MAX_EXP_BIT_LENGTH, false); + sqPub = (RSAPublicKey) KeyBuilder.buildKey(KeyBuilder.TYPE_RSA_PUBLIC, MAX_SQ_BIT_LENGTH, false); + sqPub.setExponent(CONST_TWO, (short) 0, (short) CONST_TWO.length); + sqPub.setModulus(ARRAY_A, (short) 0, MAX_SQ_LENGTH); + sqCiph.init(sqPub, Cipher.MODE_ENCRYPT); + } else { + modSqPriv = (RSAPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_RSA_PRIVATE, MAX_EXP_BIT_LENGTH, false); + sqPriv = (RSAPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_RSA_PRIVATE, MAX_SQ_BIT_LENGTH, false); + sqPriv.setExponent(CONST_TWO, (short) 0, (short) CONST_TWO.length); + sqPriv.setModulus(ARRAY_A, (short) 0, MAX_SQ_LENGTH); + sqCiph.init(sqPriv, Cipher.MODE_DECRYPT); + } + } + + // RSA Exp Helpers + expPub = (RSAPublicKey) KeyBuilder.buildKey(KeyBuilder.TYPE_RSA_PUBLIC, MAX_EXP_BIT_LENGTH, false); + expPriv = (RSAPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_RSA_PRIVATE, MAX_EXP_BIT_LENGTH, false); + expCiph = Cipher.getInstance(Cipher.ALG_RSA_NOPAD, false); + + rng = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM); + } + + /** + * Preloads modSq engine with a given mod. Can increase performance when the same mod is used repeatedly. The + * provided mod is assumed to be fixed. + */ + public void fixModSqMod(BigNat mod) { + if (!OperationSupport.getInstance().RSA_SQ) { + return; // modSq engine is not used + } + fixedMod = mod; + if (mod == null) { + return; + } + BigNat tmpMod = BN_F; + byte[] tmpBuffer = ARRAY_A; + + tmpMod.setSize(MAX_EXP_LENGTH); + if (OperationSupport.getInstance().RSA_PUB) { + if (OperationSupport.getInstance().RSA_KEY_REFRESH) { + modSqPub = (RSAPublicKey) KeyBuilder.buildKey(KeyBuilder.TYPE_RSA_PUBLIC, MAX_EXP_BIT_LENGTH, false); + } + modSqPub.setExponent(ResourceManager.CONST_TWO, (short) 0, (short) ResourceManager.CONST_TWO.length); + if (OperationSupport.getInstance().RSA_RESIZE_MOD) { + if (OperationSupport.getInstance().RSA_APPEND_MOD) { + mod.appendZeros(MAX_EXP_LENGTH, tmpBuffer, (short) 0); + } else { + mod.prependZeros(MAX_EXP_LENGTH, tmpBuffer, (short) 0); + } + modSqPub.setModulus(tmpBuffer, (short) 0, MAX_EXP_LENGTH); + } else { + short modLength = mod.copyToByteArray(tmpBuffer, (short) 0); + modSqPub.setModulus(tmpBuffer, (short) 0, modLength); + } + modSqCiph.init(modSqPub, Cipher.MODE_DECRYPT); + } else { + if (OperationSupport.getInstance().RSA_KEY_REFRESH) { + modSqPriv = (RSAPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_RSA_PRIVATE, MAX_EXP_BIT_LENGTH, false); + } + modSqPriv.setExponent(ResourceManager.CONST_TWO, (short) 0, (short) ResourceManager.CONST_TWO.length); + if (OperationSupport.getInstance().RSA_RESIZE_MOD) { + if (OperationSupport.getInstance().RSA_APPEND_MOD) { + mod.appendZeros(MAX_EXP_LENGTH, tmpBuffer, (short) 0); + } else { + mod.prependZeros(MAX_EXP_LENGTH, tmpBuffer, (short) 0); + + } + modSqPriv.setModulus(tmpBuffer, (short) 0, MAX_EXP_LENGTH); + } else { + short modLength = mod.copyToByteArray(tmpBuffer, (short) 0); + modSqPriv.setModulus(tmpBuffer, (short) 0, modLength); + } + modSqCiph.init(modSqPriv, Cipher.MODE_DECRYPT); + } + } + + /** + * Erase all values stored in helper objects + */ + void erase() { + BN_WORD.erase(); + + BN_A.erase(); + BN_B.erase(); + BN_C.erase(); + BN_D.erase(); + BN_E.erase(); + BN_F.erase(); + BN_G.erase(); + + EC_BN_A.erase(); + EC_BN_B.erase(); + EC_BN_C.erase(); + EC_BN_D.erase(); + EC_BN_E.erase(); + EC_BN_F.erase(); + + Util.arrayFillNonAtomic(ARRAY_A, (short) 0, (short) ARRAY_A.length, (byte) 0); + Util.arrayFillNonAtomic(ARRAY_B, (short) 0, (short) ARRAY_B.length, (byte) 0); + Util.arrayFillNonAtomic(POINT_ARRAY_A, (short) 0, (short) POINT_ARRAY_A.length, (byte) 0); + } + + } + + /** + * + * @author Vasilios Mavroudis and Petr Svenda + */ + public static class ReturnCodes { + // Custom error response codes + public static final short SW_BIGNAT_RESIZETOLONGER = (short) 0x7000; + public static final short SW_BIGNAT_REALLOCATIONNOTALLOWED = (short) 0x7001; + public static final short SW_BIGNAT_MODULOTOOLARGE = (short) 0x7002; + public static final short SW_BIGNAT_INVALIDCOPYOTHER = (short) 0x7003; + public static final short SW_BIGNAT_INVALIDRESIZE = (short) 0x7004; + public static final short SW_BIGNAT_INVALIDMULT = (short) 0x7005; + public static final short SW_BIGNAT_INVALIDSQ = (short) 0x7006; + public static final short SW_LOCK_ALREADYLOCKED = (short) 0x7010; + public static final short SW_LOCK_NOTLOCKED = (short) 0x7011; + public static final short SW_LOCK_OBJECT_NOT_FOUND = (short) 0x7012; + public static final short SW_LOCK_NOFREESLOT = (short) 0x7013; + public static final short SW_LOCK_OBJECT_MISMATCH = (short) 0x7014; + public static final short SW_ECPOINT_INVALIDLENGTH = (short) 0x7020; + public static final short SW_ECPOINT_UNEXPECTED_KA_LEN = (short) 0x7021; + public static final short SW_ECPOINT_INVALID = (short) 0x7022; + public static final short SW_ALLOCATOR_INVALIDOBJID = (short) 0x7030; + public static final short SW_OPERATION_NOT_SUPPORTED = (short) 0x7040; + } + + public static class Wei25519 { + public final static short k = 8; + + public final static byte[] p = { + (byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xed + }; + + + public final static byte[] a = { + (byte) 0x2a, (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, + (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, + (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, + (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, + (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, + (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, + (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, (byte) 0x98, + (byte) 0x49, (byte) 0x14, (byte) 0xa1, (byte) 0x44 + }; + + public final static byte[] b = { + (byte) 0x7b, (byte) 0x42, (byte) 0x5e, (byte) 0xd0, + (byte) 0x97, (byte) 0xb4, (byte) 0x25, (byte) 0xed, + (byte) 0x09, (byte) 0x7b, (byte) 0x42, (byte) 0x5e, + (byte) 0xd0, (byte) 0x97, (byte) 0xb4, (byte) 0x25, + (byte) 0xed, (byte) 0x09, (byte) 0x7b, (byte) 0x42, + (byte) 0x5e, (byte) 0xd0, (byte) 0x97, (byte) 0xb4, + (byte) 0x26, (byte) 0x0b, (byte) 0x5e, (byte) 0x9c, + (byte) 0x77, (byte) 0x10, (byte) 0xc8, (byte) 0x64 + }; + + public final static byte[] G = { + (byte) 0x04, + + (byte) 0x2a, (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, + (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, + (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, + (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, + (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, + (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, + (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, + (byte) 0xaa, (byte) 0xad, (byte) 0x24, (byte) 0x5a, + + (byte) 0x20, (byte) 0xae, (byte) 0x19, (byte) 0xa1, + (byte) 0xb8, (byte) 0xa0, (byte) 0x86, (byte) 0xb4, + (byte) 0xe0, (byte) 0x1e, (byte) 0xdd, (byte) 0x2c, + (byte) 0x77, (byte) 0x48, (byte) 0xd1, (byte) 0x4c, + (byte) 0x92, (byte) 0x3d, (byte) 0x4d, (byte) 0x7e, + (byte) 0x6d, (byte) 0x7c, (byte) 0x61, (byte) 0xb2, + (byte) 0x29, (byte) 0xe9, (byte) 0xc5, (byte) 0xa2, + (byte) 0x7e, (byte) 0xce, (byte) 0xd3, (byte) 0xd9 + }; + + public final static byte[] r = { + (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x14, (byte) 0xde, (byte) 0xf9, (byte) 0xde, + (byte) 0xa2, (byte) 0xf7, (byte) 0x9c, (byte) 0xd6, + (byte) 0x58, (byte) 0x12, (byte) 0x63, (byte) 0x1a, + (byte) 0x5c, (byte) 0xf5, (byte) 0xd3, (byte) 0xed + }; + } +} diff --git a/src/main/java/im/status/keycard/swalgs.java b/src/main/java/im/status/keycard/swalgs.java new file mode 100644 index 0000000..fd0b23f --- /dev/null +++ b/src/main/java/im/status/keycard/swalgs.java @@ -0,0 +1,774 @@ +package im.status.keycard; + +import javacard.framework.*; +import javacard.security.*; + +/* + * JavaCard class for work with 64bits long numbers and SHA2. + * Created by Petr Svenda http://www.svenda.com/petr + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +public class swalgs { + + static class Sha2 extends MessageDigest { + public static final byte SHA_384 = (byte) 5; + public static final byte SHA_512 = (byte) 6; + public static final short SHA2_BLOCK_LENGTH = 128; // in bytes + public static final short SHA512_DIGEST_LENGTH = 64; // in bytes + public static final short SHA384_DIGEST_LENGTH = 48; // in bytes + public static final short LONG_LENGTH = 8; // in bytes + + public static final short NUM_INT64_VARIABLES = 25; + + private byte m_shaType = SHA_512; + private byte[] xBuf = null; + + public Int64[] K = null; + Int64[] H = null; + short[] W = null; + + public short[] int64Variables = null; + + public static final short OFFSET_a = (short) 0; + public static final short OFFSET_b = (short) 4; + public static final short OFFSET_c = (short) 8; + public static final short OFFSET_d = (short) 12; + public static final short OFFSET_e = (short) 16; + public static final short OFFSET_f = (short) 20; + public static final short OFFSET_g = (short) 24; + public static final short OFFSET_h = (short) 28; + public static final short OFFSET_T1 = (short) 32; + public static final short OFFSET_T2 = (short) 36; + + public static final short OFFSET_result1 = (short) 40; + public static final short OFFSET_result2 = (short) 44; + public static final short OFFSET_result3 = (short) 48; + public static final short OFFSET_result4 = (short) 52; + + public static final short OFFSET_rotl_1 = (short) 56; + public static final short OFFSET_rotl_2 = (short) 60; + public static final short OFFSET_rotl_3 = (short) 64; + + public static final short OFFSET_rotr_1 = (short) 68; + public static final short OFFSET_rotr_2 = (short) 72; + public static final short OFFSET_rotr_3 = (short) 76; + + public static final short OFFSET_safeAdd = (short) 80; + public static final short OFFSET_safeAdd2 = (short) 84; + public static final short OFFSET_Sigma0 = (short) 88; + public static final short OFFSET_Sigma1 = (short) 92; + + public static final short OFFSET_xBufOff = (short) 96; + public static final short OFFSET_wOff = (short) 97; + public static final short OFFSET_byteCount1 = (short) 98; + public static final short OFFSET_byteCount2 = (short) 99; + + public Sha2(byte shaType){ + this.m_shaType = shaType; + + int64Variables = JCSystem.makeTransientShortArray((short) (NUM_INT64_VARIABLES * 4), JCSystem.CLEAR_ON_RESET); + + xBuf = JCSystem.makeTransientByteArray((short) 8, JCSystem.CLEAR_ON_RESET); + + K = new Int64[80]; + K[0] = new Int64((short) 0x428a,(short) 0x2f98,(short) 0xd728,(short) 0xae22); + K[1] = new Int64((short) 0x7137,(short) 0x4491,(short) 0x23ef,(short) 0x65cd); + K[2] = new Int64((short) 0xb5c0,(short) 0xfbcf,(short) 0xec4d,(short) 0x3b2f); + K[3] = new Int64((short) 0xe9b5,(short) 0xdba5,(short) 0x8189,(short) 0xdbbc); + K[4] = new Int64((short) 0x3956,(short) 0xc25b,(short) 0xf348,(short) 0xb538); + K[5] = new Int64((short) 0x59f1,(short) 0x11f1,(short) 0xb605,(short) 0xd019); + K[6] = new Int64((short) 0x923f,(short) 0x82a4,(short) 0xaf19,(short) 0x4f9b); + K[7] = new Int64((short) 0xab1c,(short) 0x5ed5,(short) 0xda6d,(short) 0x8118); + K[8] = new Int64((short) 0xd807,(short) 0xaa98,(short) 0xa303,(short) 0x0242); + K[9] = new Int64((short) 0x1283,(short) 0x5b01,(short) 0x4570,(short) 0x6fbe); + K[10] = new Int64((short) 0x2431,(short) 0x85be,(short) 0x4ee4,(short) 0xb28c); + K[11] = new Int64((short) 0x550c,(short) 0x7dc3,(short) 0xd5ff,(short) 0xb4e2); + K[12] = new Int64((short) 0x72be,(short) 0x5d74,(short) 0xf27b,(short) 0x896f); + K[13] = new Int64((short) 0x80de,(short) 0xb1fe,(short) 0x3b16,(short) 0x96b1); + K[14] = new Int64((short) 0x9bdc,(short) 0x06a7,(short) 0x25c7,(short) 0x1235); + K[15] = new Int64((short) 0xc19b,(short) 0xf174,(short) 0xcf69,(short) 0x2694); + K[16] = new Int64((short) 0xe49b,(short) 0x69c1,(short) 0x9ef1,(short) 0x4ad2); + K[17] = new Int64((short) 0xefbe,(short) 0x4786,(short) 0x384f,(short) 0x25e3); + K[18] = new Int64((short) 0x0fc1,(short) 0x9dc6,(short) 0x8b8c,(short) 0xd5b5); + K[19] = new Int64((short) 0x240c,(short) 0xa1cc,(short) 0x77ac,(short) 0x9c65); + K[20] = new Int64((short) 0x2de9,(short) 0x2c6f,(short) 0x592b,(short) 0x0275); + K[21] = new Int64((short) 0x4a74,(short) 0x84aa,(short) 0x6ea6,(short) 0xe483); + K[22] = new Int64((short) 0x5cb0,(short) 0xa9dc,(short) 0xbd41,(short) 0xfbd4); + K[23] = new Int64((short) 0x76f9,(short) 0x88da,(short) 0x8311,(short) 0x53b5); + K[24] = new Int64((short) 0x983e,(short) 0x5152,(short) 0xee66,(short) 0xdfab); + K[25] = new Int64((short) 0xa831,(short) 0xc66d,(short) 0x2db4,(short) 0x3210); + K[26] = new Int64((short) 0xb003,(short) 0x27c8,(short) 0x98fb,(short) 0x213f); + K[27] = new Int64((short) 0xbf59,(short) 0x7fc7,(short) 0xbeef,(short) 0x0ee4); + K[28] = new Int64((short) 0xc6e0,(short) 0x0bf3,(short) 0x3da8,(short) 0x8fc2); + K[29] = new Int64((short) 0xd5a7,(short) 0x9147,(short) 0x930a,(short) 0xa725); + K[30] = new Int64((short) 0x06ca,(short) 0x6351,(short) 0xe003,(short) 0x826f); + K[31] = new Int64((short) 0x1429,(short) 0x2967,(short) 0x0a0e,(short) 0x6e70); + K[32] = new Int64((short) 0x27b7,(short) 0x0a85,(short) 0x46d2,(short) 0x2ffc); + K[33] = new Int64((short) 0x2e1b,(short) 0x2138,(short) 0x5c26,(short) 0xc926); + K[34] = new Int64((short) 0x4d2c,(short) 0x6dfc,(short) 0x5ac4,(short) 0x2aed); + K[35] = new Int64((short) 0x5338,(short) 0x0d13,(short) 0x9d95,(short) 0xb3df); + K[36] = new Int64((short) 0x650a,(short) 0x7354,(short) 0x8baf,(short) 0x63de); + K[37] = new Int64((short) 0x766a,(short) 0x0abb,(short) 0x3c77,(short) 0xb2a8); + K[38] = new Int64((short) 0x81c2,(short) 0xc92e,(short) 0x47ed,(short) 0xaee6); + K[39] = new Int64((short) 0x9272,(short) 0x2c85,(short) 0x1482,(short) 0x353b); + K[40] = new Int64((short) 0xa2bf,(short) 0xe8a1,(short) 0x4cf1,(short) 0x0364); + K[41] = new Int64((short) 0xa81a,(short) 0x664b,(short) 0xbc42,(short) 0x3001); + K[42] = new Int64((short) 0xc24b,(short) 0x8b70,(short) 0xd0f8,(short) 0x9791); + K[43] = new Int64((short) 0xc76c,(short) 0x51a3,(short) 0x0654,(short) 0xbe30); + K[44] = new Int64((short) 0xd192,(short) 0xe819,(short) 0xd6ef,(short) 0x5218); + K[45] = new Int64((short) 0xd699,(short) 0x0624,(short) 0x5565,(short) 0xa910); + K[46] = new Int64((short) 0xf40e,(short) 0x3585,(short) 0x5771,(short) 0x202a); + K[47] = new Int64((short) 0x106a,(short) 0xa070,(short) 0x32bb,(short) 0xd1b8); + K[48] = new Int64((short) 0x19a4,(short) 0xc116,(short) 0xb8d2,(short) 0xd0c8); + K[49] = new Int64((short) 0x1e37,(short) 0x6c08,(short) 0x5141,(short) 0xab53); + K[50] = new Int64((short) 0x2748,(short) 0x774c,(short) 0xdf8e,(short) 0xeb99); + K[51] = new Int64((short) 0x34b0,(short) 0xbcb5,(short) 0xe19b,(short) 0x48a8); + K[52] = new Int64((short) 0x391c,(short) 0x0cb3,(short) 0xc5c9,(short) 0x5a63); + K[53] = new Int64((short) 0x4ed8,(short) 0xaa4a,(short) 0xe341,(short) 0x8acb); + K[54] = new Int64((short) 0x5b9c,(short) 0xca4f,(short) 0x7763,(short) 0xe373); + K[55] = new Int64((short) 0x682e,(short) 0x6ff3,(short) 0xd6b2,(short) 0xb8a3); + K[56] = new Int64((short) 0x748f,(short) 0x82ee,(short) 0x5def,(short) 0xb2fc); + K[57] = new Int64((short) 0x78a5,(short) 0x636f,(short) 0x4317,(short) 0x2f60); + K[58] = new Int64((short) 0x84c8,(short) 0x7814,(short) 0xa1f0,(short) 0xab72); + K[59] = new Int64((short) 0x8cc7,(short) 0x0208,(short) 0x1a64,(short) 0x39ec); + K[60] = new Int64((short) 0x90be,(short) 0xfffa,(short) 0x2363,(short) 0x1e28); + K[61] = new Int64((short) 0xa450,(short) 0x6ceb,(short) 0xde82,(short) 0xbde9); + K[62] = new Int64((short) 0xbef9,(short) 0xa3f7,(short) 0xb2c6,(short) 0x7915); + K[63] = new Int64((short) 0xc671,(short) 0x78f2,(short) 0xe372,(short) 0x532b); + K[64] = new Int64((short) 0xca27,(short) 0x3ece,(short) 0xea26,(short) 0x619c); + K[65] = new Int64((short) 0xd186,(short) 0xb8c7,(short) 0x21c0,(short) 0xc207); + K[66] = new Int64((short) 0xeada,(short) 0x7dd6,(short) 0xcde0,(short) 0xeb1e); + K[67] = new Int64((short) 0xf57d,(short) 0x4f7f,(short) 0xee6e,(short) 0xd178); + K[68] = new Int64((short) 0x06f0,(short) 0x67aa,(short) 0x7217,(short) 0x6fba); + K[69] = new Int64((short) 0x0a63,(short) 0x7dc5,(short) 0xa2c8,(short) 0x98a6); + K[70] = new Int64((short) 0x113f,(short) 0x9804,(short) 0xbef9,(short) 0x0dae); + K[71] = new Int64((short) 0x1b71,(short) 0x0b35,(short) 0x131c,(short) 0x471b); + K[72] = new Int64((short) 0x28db,(short) 0x77f5,(short) 0x2304,(short) 0x7d84); + K[73] = new Int64((short) 0x32ca,(short) 0xab7b,(short) 0x40c7,(short) 0x2493); + K[74] = new Int64((short) 0x3c9e,(short) 0xbe0a,(short) 0x15c9,(short) 0xbebc); + K[75] = new Int64((short) 0x431d,(short) 0x67c4,(short) 0x9c10,(short) 0x0d4c); + K[76] = new Int64((short) 0x4cc5,(short) 0xd4be,(short) 0xcb3e,(short) 0x42b6); + K[77] = new Int64((short) 0x597f,(short) 0x299c,(short) 0xfc65,(short) 0x7e2a); + K[78] = new Int64((short) 0x5fcb,(short) 0x6fab,(short) 0x3ad6,(short) 0xfaec); + K[79] = new Int64((short) 0x6c44,(short) 0x198c,(short) 0x4a47,(short) 0x5817); + + H = new Int64[8]; + for (byte i=0; i < (short) H.length; i++) H[i] = new Int64(); + clearState(m_shaType); + + W = JCSystem.makeTransientShortArray((short) (80 * 4), JCSystem.CLEAR_ON_RESET); + } + + public void int64Set(short[] array, short offset, short highest, short higher, short lower, short lowest) { + array[offset] = highest; + array[(short)(offset + 1)] = higher; + array[(short)(offset + 2)] = lower; + array[(short)(offset + 3)] = lowest; + } + + public void int64Set(short[] destArray, short destOffset, short[] srcArray, short srcOffset) { + destArray[destOffset] = srcArray[srcOffset]; destArray[(short) (destOffset + 1)] = srcArray[(short) (srcOffset + 1)]; destArray[(short) (destOffset + 2)] = srcArray[(short) (srcOffset + 2)]; destArray[(short) (destOffset + 3)] = srcArray[(short) (srcOffset + 3)]; + } + + private void int64Set(short[] array, short offset, Int64 template) { + array[offset] = template.highest; + array[(short)(offset + 1)] = template.higher; + array[(short)(offset + 2)] = template.lower; + array[(short)(offset + 3)] = template.lowest; + } + + private void int64Set(short[] array, short offset, byte[] srcArray, short srcOffset) { + array[offset] = (short) (srcArray[srcOffset] << 8 | (short) ( srcArray[(short) (srcOffset + 1)] & 0xff)); + array[(short) (offset + 1)] = (short) (srcArray[(short) (srcOffset + 2)] << 8 |(short) ( srcArray[(short) (srcOffset + 3)] & 0xff)); + array[(short) (offset + 2)] = (short) (srcArray[(short) (srcOffset + 4)] << 8 | (short) ( srcArray[(short) (srcOffset + 5)] & 0xff)); + array[(short) (offset + 3)] = (short) (srcArray[(short) (srcOffset + 6)] << 8 | (short) ( srcArray[(short) (srcOffset + 7)] & 0xff)); + } + + void clearState(short variant) { + if (variant == SHA_384) { + H[0].set((short) 0xcbbb, (short) 0x9d5d, (short) 0xc105, (short) 0x9ed8); + H[1].set((short) 0x629a, (short) 0x292a, (short) 0x367c, (short) 0xd507); + H[2].set((short) 0x9159, (short) 0x015a, (short) 0x3070, (short) 0xdd17); + H[3].set((short) 0x152f, (short) 0xecd8, (short) 0xf70e, (short) 0x5939); + H[4].set((short) 0x6733, (short) 0x2667, (short) 0xffc0, (short) 0x0b31); + H[5].set((short) 0x8eb4, (short) 0x4a87, (short) 0x6858, (short) 0x1511); + H[6].set((short) 0xdb0c, (short) 0x2e0d, (short) 0x64f9, (short) 0x8fa7); + H[7].set((short) 0x47b5, (short) 0x481d, (short) 0xbefa, (short) 0x4fa4); + } + if (variant == SHA_512) { + H[0].set((short) 0x6a09, (short) 0xe667, (short) 0xf3bc, (short) 0xc908); + H[1].set((short) 0xbb67, (short) 0xae85, (short) 0x84ca, (short) 0xa73b); + H[2].set((short) 0x3c6e, (short) 0xf372, (short) 0xfe94, (short) 0xf82b); + H[3].set((short) 0xa54f, (short) 0xf53a, (short) 0x5f1d, (short) 0x36f1); + H[4].set((short) 0x510e, (short) 0x527f, (short) 0xade6, (short) 0x82d1); + H[5].set((short) 0x9b05, (short) 0x688c, (short) 0x2b3e, (short) 0x6c1f); + H[6].set((short) 0x1f83, (short) 0xd9ab, (short) 0xfb41, (short) 0xbd6b); + H[7].set((short) 0x5be0, (short) 0xcd19, (short) 0x137e, (short) 0x2179); + } + } + + + public void update(byte in) { + xBuf[int64Variables[OFFSET_xBufOff]++] = in; + if (int64Variables[OFFSET_xBufOff] == (short) xBuf.length) { + int64Set(W, int64Variables[OFFSET_wOff], xBuf, (short) 0); + int64Variables[OFFSET_wOff] += 4; + if (int64Variables[OFFSET_wOff] == (short) (16 * 4)) processBlock(); + + int64Variables[OFFSET_xBufOff] = 0; + } + int64Variables[OFFSET_byteCount1]++; + } + + public void update(byte[] strToHash, short startOffset, short strLen) { + while ((int64Variables[OFFSET_xBufOff] != 0) && (strLen > 0)) { + xBuf[int64Variables[OFFSET_xBufOff]++] = strToHash[startOffset]; + if (int64Variables[OFFSET_xBufOff] == (short) xBuf.length) { + int64Set(W, int64Variables[OFFSET_wOff], xBuf, (short) 0); + int64Variables[OFFSET_wOff] += 4; + if (int64Variables[OFFSET_wOff] == (short) (16 * 4)) processBlock(); + + int64Variables[OFFSET_xBufOff] = 0; + } + int64Variables[OFFSET_byteCount1]++; + + startOffset++; + strLen--; + } + + while (strLen > xBuf.length) + { + int64Set(W, int64Variables[OFFSET_wOff], strToHash, startOffset); + int64Variables[OFFSET_wOff] += 4; + if (int64Variables[OFFSET_wOff] == (short) (16 * 4)) processBlock(); + + startOffset += xBuf.length; + strLen -= xBuf.length; + int64Variables[OFFSET_byteCount1] += xBuf.length; + } + + while (strLen > 0) + { + xBuf[int64Variables[OFFSET_xBufOff]++] = strToHash[startOffset]; + if (int64Variables[OFFSET_xBufOff] == (short) xBuf.length) { + int64Set(W, int64Variables[OFFSET_wOff], xBuf, (short) 0); + int64Variables[OFFSET_wOff] += 4; + if (int64Variables[OFFSET_wOff] == (short) (16 * 4)) processBlock(); + + int64Variables[OFFSET_xBufOff] = 0; + } + int64Variables[OFFSET_byteCount1]++; + + startOffset++; + strLen--; + } + } + + public short doFinal(byte[] strToHash, short startOffset, short strLen, byte[] hash, short hashOffset) { + update(strToHash, startOffset, strLen); + + int64Set(int64Variables, OFFSET_a, (short) 0, (short) 0, (short) 0, int64Variables[OFFSET_byteCount1]); + rotl(OFFSET_a, (byte) 3, OFFSET_a); + + update((byte)128); + while (int64Variables[OFFSET_xBufOff] != 0) update((byte)0); + + if (int64Variables[OFFSET_wOff] > (short) (14 * 4)) processBlock(); + int64Set(W, (short) (14 * 4), (short) 0, (short) 0, (short) 0, (short) 0); + int64Set(W, (short) (15 * 4), int64Variables, OFFSET_a); + + processBlock(); + + switch (m_shaType) { + case SHA_384: { + for (short i = 0; i < 6; i++) { + H[i].get(hash, (short) (hashOffset + (short) (i * LONG_LENGTH))); + } + break; + } + case SHA_512: { + for (short i = 0; i < 8; i++) { + H[i].get(hash, (short) (hashOffset + (short) (i * LONG_LENGTH))); + } + break; + } + default:return (short) -1; + } + + reset(); + + return (short) 0; + } + + public void reset() { + clearState(m_shaType); + int64Variables[OFFSET_byteCount1] = 0; + int64Variables[OFFSET_byteCount2] = 0; + + int64Variables[OFFSET_xBufOff] = 0; + for (short i = 0; i < xBuf.length; i++) xBuf[i] = 0; + + int64Variables[OFFSET_wOff] = 0; + for (short i = 0; i != (short) W.length; i++) W[i] = 0; + } + + public byte getLength() { + switch (m_shaType) { + case SHA_512: return SHA512_DIGEST_LENGTH; + case SHA_384: return SHA384_DIGEST_LENGTH; + } + + return (short) 0; + } + + public byte getDigestSize() { + return getLength(); + } + + public byte getAlgorithm() { + return m_shaType; + } + + void processBlock() { + for (short t = 16; t < 80; t++) + { + safeAdd(safeAdd(safeAdd(Sigma1(W, (short)((t-2) * 4), OFFSET_result1),W, (short)((short)(t-7) * 4), OFFSET_result2),Sigma0(W, (short)((short) (t-15) * 4), OFFSET_result3), OFFSET_result4),W, (short)((short)(t-16) * 4), W, (short) (t * 4)); + } + int64Set(int64Variables, OFFSET_a, H[0]); + int64Set(int64Variables, OFFSET_b, H[1]); + int64Set(int64Variables, OFFSET_c, H[2]); + int64Set(int64Variables, OFFSET_d, H[3]); + int64Set(int64Variables, OFFSET_e, H[4]); + int64Set(int64Variables, OFFSET_f, H[5]); + int64Set(int64Variables, OFFSET_g, H[6]); + int64Set(int64Variables, OFFSET_h, H[7]); + + short t = 0; // this value is incremented in W[t++] + for(short i = 0; i < 10; i ++) { // TODO: enable all rounds (should be 10) + safeAdd(safeAdd(safeAdd(safeAdd(OFFSET_h, Sum1(OFFSET_e, OFFSET_result1), OFFSET_h), ch(OFFSET_e, OFFSET_f, OFFSET_g, OFFSET_result3), OFFSET_h), K[t], OFFSET_h), W, (short) (t * 4), OFFSET_h); + t++; + safeAdd(OFFSET_d, OFFSET_h, OFFSET_d); + safeAdd(Sum0(OFFSET_a, OFFSET_result1), maj(OFFSET_a, OFFSET_b, OFFSET_c, OFFSET_result2), OFFSET_T2); + safeAdd(OFFSET_h, OFFSET_T2, OFFSET_h); + + safeAdd(safeAdd(safeAdd(safeAdd(OFFSET_g, Sum1(OFFSET_d, OFFSET_result1), OFFSET_g), ch(OFFSET_d, OFFSET_e, OFFSET_f, OFFSET_result3), OFFSET_g), K[t], OFFSET_g), W, (short) (t * 4), OFFSET_g); + t++; + safeAdd(OFFSET_c, OFFSET_g, OFFSET_c); + safeAdd(Sum0(OFFSET_h, OFFSET_result1), maj(OFFSET_h, OFFSET_a, OFFSET_b, OFFSET_result2), OFFSET_T2); + safeAdd(OFFSET_g, OFFSET_T2, OFFSET_g); + + safeAdd(safeAdd(safeAdd(safeAdd(OFFSET_f, Sum1(OFFSET_c, OFFSET_result1), OFFSET_f), ch(OFFSET_c, OFFSET_d, OFFSET_e, OFFSET_result3), OFFSET_f), K[t], OFFSET_f), W, (short) (t * 4), OFFSET_f); + t++; + safeAdd(OFFSET_b, OFFSET_f, OFFSET_b); + safeAdd(Sum0(OFFSET_g, OFFSET_result1), maj(OFFSET_g, OFFSET_h, OFFSET_a, OFFSET_result2), OFFSET_T2); + safeAdd(OFFSET_f, OFFSET_T2, OFFSET_f); + + safeAdd(safeAdd(safeAdd(safeAdd(OFFSET_e, Sum1(OFFSET_b, OFFSET_result1), OFFSET_e), ch(OFFSET_b, OFFSET_c, OFFSET_d, OFFSET_result3), OFFSET_e), K[t], OFFSET_e), W, (short) (t * 4), OFFSET_e); + t++; + safeAdd(OFFSET_a, OFFSET_e, OFFSET_a); + safeAdd(Sum0(OFFSET_f, OFFSET_result1), maj(OFFSET_f, OFFSET_g, OFFSET_h, OFFSET_result2), OFFSET_T2); + safeAdd(OFFSET_e, OFFSET_T2, OFFSET_e); + + safeAdd(safeAdd(safeAdd(safeAdd(OFFSET_d, Sum1(OFFSET_a, OFFSET_result1), OFFSET_d), ch(OFFSET_a, OFFSET_b, OFFSET_c, OFFSET_result3), OFFSET_d), K[t], OFFSET_d), W, (short) (t * 4), OFFSET_d); + t++; + safeAdd(OFFSET_h, OFFSET_d, OFFSET_h); + safeAdd(Sum0(OFFSET_e, OFFSET_result1), maj(OFFSET_e, OFFSET_f, OFFSET_g, OFFSET_result2), OFFSET_T2); + safeAdd(OFFSET_d, OFFSET_T2, OFFSET_d); + + safeAdd(safeAdd(safeAdd(safeAdd(OFFSET_c, Sum1(OFFSET_h, OFFSET_result1), OFFSET_c), ch(OFFSET_h, OFFSET_a, OFFSET_b, OFFSET_result3), OFFSET_c), K[t], OFFSET_c), W, (short) (t * 4), OFFSET_c); + t++; + safeAdd(OFFSET_g, OFFSET_c, OFFSET_g); + safeAdd(Sum0(OFFSET_d, OFFSET_result1), maj(OFFSET_d, OFFSET_e, OFFSET_f, OFFSET_result2), OFFSET_T2); + safeAdd(OFFSET_c, OFFSET_T2, OFFSET_c); + + safeAdd(safeAdd(safeAdd(safeAdd(OFFSET_b, Sum1(OFFSET_g, OFFSET_result1), OFFSET_b), ch(OFFSET_g, OFFSET_h, OFFSET_a, OFFSET_result3), OFFSET_b), K[t], OFFSET_b), W, (short) (t * 4), OFFSET_b); + t++; + safeAdd(OFFSET_f, OFFSET_b, OFFSET_f); + safeAdd(Sum0(OFFSET_c, OFFSET_result1), maj(OFFSET_c, OFFSET_d, OFFSET_e, OFFSET_result2), OFFSET_T2); + safeAdd(OFFSET_b, OFFSET_T2, OFFSET_b); + + safeAdd(safeAdd(safeAdd(safeAdd(OFFSET_a, Sum1(OFFSET_f, OFFSET_result1), OFFSET_a), ch(OFFSET_f, OFFSET_g, OFFSET_h, OFFSET_result3), OFFSET_a), K[t], OFFSET_a), W, (short) (t * 4), OFFSET_a); + t++; + safeAdd(OFFSET_e, OFFSET_a, OFFSET_e); + safeAdd(Sum0(OFFSET_b, OFFSET_result1), maj(OFFSET_b, OFFSET_c, OFFSET_d, OFFSET_result2), OFFSET_T2); + safeAdd(OFFSET_a, OFFSET_T2, OFFSET_a); + + } + + safeAdd(OFFSET_a,H[0],OFFSET_a); + H[0].set(int64Variables, OFFSET_a); + safeAdd(OFFSET_b,H[1],OFFSET_b); + H[1].set(int64Variables, OFFSET_b); + safeAdd(OFFSET_c,H[2],OFFSET_c); + H[2].set(int64Variables, OFFSET_c); + safeAdd(OFFSET_d,H[3],OFFSET_d); + H[3].set(int64Variables, OFFSET_d); + safeAdd(OFFSET_e,H[4],OFFSET_e); + H[4].set(int64Variables, OFFSET_e); + safeAdd(OFFSET_f,H[5],OFFSET_f); + H[5].set(int64Variables, OFFSET_f); + safeAdd(OFFSET_g,H[6],OFFSET_g); + H[6].set(int64Variables, OFFSET_g); + safeAdd(OFFSET_h,H[7],OFFSET_h); + H[7].set(int64Variables, OFFSET_h); + + int64Variables[OFFSET_wOff] = 0; + for (short i = 0; i < (short) W.length; i++) W[i] = 0; + } + + public short rotr(short offset_x, byte n, short offset_result){ + int64Variables[offset_result] = int64Variables[offset_x]; + int64Variables[(short) (offset_result + 1)] = int64Variables[(short) (offset_x + 1)]; + int64Variables[(short) (offset_result + 2)] = int64Variables[(short) (offset_x + 2)]; + int64Variables[(short) (offset_result + 3)] = int64Variables[(short) (offset_x + 3)]; + while (n >= 16) { + int64Variables[(short) (offset_result + 3)] = int64Variables[(short) (offset_result + 2)]; + int64Variables[(short) (offset_result + 2)] = int64Variables[(short) (offset_result + 1)]; + int64Variables[(short) (offset_result + 1)] = int64Variables[offset_result]; + int64Variables[offset_result] = (short) 0; + n = (byte) (n - 16); + } + + if (n > 0) { + short mask = 0; + switch (n) { + case 1: mask = (short)0x7fff; break; + case 2: mask = (short)0x3fff; break; + case 3: mask = (short)0x1fff; break; + case 4: mask = (short)0x0fff; break; + case 5: mask = (short)0x07ff; break; + case 6: mask = (short)0x03ff; break; + case 7: mask = (short)0x01ff; break; + + case 8: mask = (short)0x00ff; break; + case 9: mask = (short)0x007f; break; + case 10: mask = (short)0x003f; break; + case 11: mask = (short)0x001f; break; + case 12: mask = (short)0x000f; break; + case 13: mask = (short)0x0007; break; + case 14: mask = (short)0x0003; break; + case 15: mask = (short)0x0001; break; + } + int64Variables[(short) (offset_result + 3)] = (short) ((short) (int64Variables[(short) (offset_result + 2)] << (16 - n)) | (short) (int64Variables[(short) (offset_result + 3)] >> n & mask)); + int64Variables[(short) (offset_result + 2)] = (short) ((short) (int64Variables[(short) (offset_result + 1)] << (16 - n)) | (short) (int64Variables[(short) (offset_result + 2)] >> n & mask)); + int64Variables[(short) (offset_result + 1)] = (short) ((short) (int64Variables[offset_result] << (16 - n)) | (short) (int64Variables[(short) (offset_result + 1)] >> n & mask)); + int64Variables[offset_result] = (short) ((int64Variables[offset_result] >> n) & mask); + } + return offset_result; + } + + public short rotl(short offset_x, byte n, short offset_result){ + int64Variables[offset_result] = int64Variables[offset_x]; + int64Variables[(short) (offset_result + 1)] = int64Variables[(short) (offset_x + 1)]; + int64Variables[(short) (offset_result + 2)] = int64Variables[(short) (offset_x + 2)]; + int64Variables[(short) (offset_result + 3)] = int64Variables[(short) (offset_x + 3)]; + + while (n >= 16) { + int64Variables[offset_result] = int64Variables[(short) (offset_result + 1)]; + int64Variables[(short) (offset_result + 1)] = int64Variables[(short) (offset_result + 2)]; + int64Variables[(short) (offset_result + 2)] = int64Variables[(short) (offset_result + 3)]; + int64Variables[(short) (offset_result + 3)] = 0; + n = (byte) (n - 16); + } + + while (n >= 8) { + int64Variables[offset_result] = (short) ((short) (int64Variables[offset_result] << 8) | (short) (int64Variables[(short) (offset_result + 1)] >> 8 & 0xff)); + int64Variables[(short) (offset_result + 1)] = (short) ((short) (int64Variables[(short) (offset_result + 1)] << 8) | (short) (int64Variables[(short) (offset_result + 2)] >> 8 & 0xff)); + int64Variables[(short) (offset_result + 2)] = (short) ((short) (int64Variables[(short) (offset_result + 2)] << 8) | (short) (int64Variables[(short) (offset_result + 3)] >> 8 & 0xff)); + int64Variables[(short) (offset_result + 3)] = (short) (int64Variables[(short) (offset_result + 3)] << 8); + n = (byte) (n - 8); + } + + if (n > 0) { + short mask = 0; + switch (n) { + case 1: mask = (short)0x0001; break; + case 2: mask = (short)0x0003; break; + case 3: mask = (short)0x0007; break; + case 4: mask = (short)0x000f; break; + case 5: mask = (short)0x001f; break; + case 6: mask = (short)0x003f; break; + case 7: mask = (short)0x007f; break; + } + int64Variables[offset_result] = (short) ((short) (int64Variables[offset_result] << n) | (short) (int64Variables[(short) (offset_result + 1)] >> (16 - n) & mask)); + int64Variables[(short) (offset_result + 1)] = (short) ((short) (int64Variables[(short) (offset_result + 1)] << n) | (short) (int64Variables[(short) (offset_result + 2)] >> (16 - n) & mask)); + int64Variables[(short) (offset_result + 2)] = (short) ((short) (int64Variables[(short) (offset_result + 2)] << n) | (short) (int64Variables[(short) (offset_result + 3)] >> (16 - n) & mask)); + int64Variables[(short) (offset_result + 3)] = (short) (int64Variables[(short) (offset_result + 3)] << n); + } + return offset_result; + } + + short ch(short offset_x, short offset_y, short offset_z, short offset_result){ + int64Variables[offset_result] = (short) ((short) (int64Variables[offset_x] & int64Variables[offset_y]) ^ (short) (~(int64Variables[offset_x]) & int64Variables[offset_z])); + int64Variables[(short) (offset_result + 1)] = (short) ((short) (int64Variables[(short) (offset_x + 1)] & int64Variables[(short) (offset_y + 1)])^ (short) (~(int64Variables[(short) (offset_x + 1)]) & int64Variables[(short) (offset_z + 1)])); + int64Variables[(short) (offset_result + 2)] = (short) ((short) (int64Variables[(short) (offset_x + 2)] & int64Variables[(short) (offset_y + 2)])^ (short) (~(int64Variables[(short) (offset_x + 2)]) & int64Variables[(short) (offset_z + 2)])); + int64Variables[(short) (offset_result + 3)] = (short) ((short) (int64Variables[(short) (offset_x + 3)] & int64Variables[(short) (offset_y + 3)])^ (short) (~(int64Variables[(short) (offset_x + 3)]) & int64Variables[(short) (offset_z + 3)])); + + return offset_result; + } + + short maj(short offset_x, short offset_y, short offset_z, short offset_result){ + int64Variables[offset_result] = (short) ((short) (int64Variables[offset_x] & int64Variables[offset_y]) ^ (short)(int64Variables[offset_x] & int64Variables[offset_z])^ (short) (int64Variables[offset_y] & int64Variables[offset_z])); + int64Variables[(short) (offset_result + 1)] = (short) ((short) (int64Variables[(short) (offset_x + 1)] & int64Variables[(short) (offset_y + 1)]) ^ (short)(int64Variables[(short) (offset_x + 1)] & int64Variables[(short) (offset_z + 1)])^ (short) (int64Variables[(short) (offset_y + 1)] & int64Variables[(short) (offset_z + 1)])); + int64Variables[(short) (offset_result + 2)] = (short) ((short) (int64Variables[(short) (offset_x + 2)] & int64Variables[(short) (offset_y + 2)]) ^ (short)(int64Variables[(short) (offset_x + 2)] & int64Variables[(short) (offset_z + 2)])^ (short) (int64Variables[(short) (offset_y + 2)] & int64Variables[(short) (offset_z + 2)])); + int64Variables[(short) (offset_result + 3)] = (short) ((short) (int64Variables[(short) (offset_x + 3)] & int64Variables[(short) (offset_y + 3)]) ^ (short)(int64Variables[(short) (offset_x + 3)] & int64Variables[(short) (offset_z + 3)])^ (short) (int64Variables[(short) (offset_y + 3)] & int64Variables[(short) (offset_z + 3)])); + + return offset_result; + } + + + short Sum0(short offset_x, short offset_result) { + rotl(offset_x,(byte)36,OFFSET_rotl_1); + rotr(offset_x,(byte)28,OFFSET_rotr_1); + + rotl(offset_x,(byte)30,OFFSET_rotl_2); + rotr(offset_x,(byte)34,OFFSET_rotr_2); + + rotl(offset_x,(byte)25,OFFSET_rotl_3); + rotr(offset_x,(byte)39,OFFSET_rotr_3); + + int64Variables[offset_result] = (short) (((short) (int64Variables[OFFSET_rotr_1] | int64Variables[OFFSET_rotl_1])) ^ ((short) (int64Variables[OFFSET_rotl_2] | int64Variables[OFFSET_rotr_2])) ^ ((short) (int64Variables[OFFSET_rotl_3] | int64Variables[OFFSET_rotr_3]))); + int64Variables[(short) (offset_result + 1)] = (short) (((short) (int64Variables[(short) (OFFSET_rotl_1 + 1)] | int64Variables[(short) (OFFSET_rotr_1 + 1)])) ^ ((short) (int64Variables[(short) (OFFSET_rotl_2 + 1)] | int64Variables[(short) (OFFSET_rotr_2 + 1)])) ^ ((short) (int64Variables[(short) (OFFSET_rotl_3 + 1)] | int64Variables[(short) (OFFSET_rotr_3 + 1)]))); + int64Variables[(short) (offset_result + 2)] = (short) (((short) (int64Variables[(short) (OFFSET_rotl_1 + 2)] | int64Variables[(short) (OFFSET_rotr_1 + 2)])) ^ ((short) (int64Variables[(short) (OFFSET_rotl_2 + 2)] | int64Variables[(short) (OFFSET_rotr_2 + 2)])) ^ ((short) (int64Variables[(short) (OFFSET_rotl_3 + 2)] | int64Variables[(short) (OFFSET_rotr_3 + 2)]))); + int64Variables[(short) (offset_result + 3)] = (short) (((short) (int64Variables[(short) (OFFSET_rotl_1 + 3)] | int64Variables[(short) (OFFSET_rotr_1 + 3)])) ^ ((short) (int64Variables[(short) (OFFSET_rotl_2 + 3)] | int64Variables[(short) (OFFSET_rotr_2 + 3)])) ^ ((short) (int64Variables[(short) (OFFSET_rotl_3 + 3)] | int64Variables[(short) (OFFSET_rotr_3 + 3)]))); + + return offset_result; + } + + short Sum1(short offset_x, short offset_result) { + rotl(offset_x,(byte)50,OFFSET_rotl_1); + rotr(offset_x,(byte)14,OFFSET_rotr_1); + + rotl(offset_x,(byte)46,OFFSET_rotl_2); + rotr(offset_x,(byte)18,OFFSET_rotr_2); + + rotl(offset_x,(byte)23,OFFSET_rotl_3); + rotr(offset_x,(byte)41,OFFSET_rotr_3); + + int64Variables[offset_result] = (short) (((short) (int64Variables[OFFSET_rotl_1] | int64Variables[OFFSET_rotr_1])) ^ ((short) (int64Variables[OFFSET_rotl_2] | int64Variables[OFFSET_rotr_2])) ^ ((short) (int64Variables[OFFSET_rotl_3] | int64Variables[OFFSET_rotr_3]))); + int64Variables[(short) (offset_result + 1)] = (short) (((short) (int64Variables[(short) (OFFSET_rotl_1 + 1)] | int64Variables[(short) (OFFSET_rotr_1 + 1)])) ^ ((short) (int64Variables[(short) (OFFSET_rotl_2 + 1)] | int64Variables[(short) (OFFSET_rotr_2 + 1)])) ^ ((short) (int64Variables[(short) (OFFSET_rotl_3 + 1)] | int64Variables[(short) (OFFSET_rotr_3 + 1)]))); + int64Variables[(short) (offset_result + 2)] = (short) (((short) (int64Variables[(short) (OFFSET_rotl_1 + 2)] | int64Variables[(short) (OFFSET_rotr_1 + 2)])) ^ ((short) (int64Variables[(short) (OFFSET_rotl_2 + 2)] | int64Variables[(short) (OFFSET_rotr_2 + 2)])) ^ ((short) (int64Variables[(short) (OFFSET_rotl_3 + 2)] | int64Variables[(short) (OFFSET_rotr_3 + 2)]))); + int64Variables[(short) (offset_result + 3)] = (short) (((short) (int64Variables[(short) (OFFSET_rotl_1 + 3)] | int64Variables[(short) (OFFSET_rotr_1 + 3)])) ^ ((short) (int64Variables[(short) (OFFSET_rotl_2 + 3)] | int64Variables[(short) (OFFSET_rotr_2 + 3)])) ^ ((short) (int64Variables[(short) (OFFSET_rotl_3 + 3)] | int64Variables[(short) (OFFSET_rotr_3 + 3)]))); + + return offset_result; + } + + short Sigma0(Int64 x, short offset_result) { + x.get(int64Variables, OFFSET_Sigma0); + return Sigma0(OFFSET_Sigma0, offset_result); + } + short Sigma0(short[] xArray, short xOffset, short offset_result) { + for (short i = 0; i < (short) 4; i++) int64Variables[(short) (OFFSET_Sigma0 + i)] = xArray[(short) (xOffset + i)]; + return Sigma0(OFFSET_Sigma0, offset_result); + } + short Sigma0(short offset_x, short offset_result) { + rotl(offset_x,(byte)63,OFFSET_rotl_1); + rotr(offset_x,(byte)1,OFFSET_rotr_1); + + rotl(offset_x,(byte)56,OFFSET_rotl_2); + rotr(offset_x,(byte)8,OFFSET_rotr_2); + + rotr(offset_x,(byte)7,OFFSET_rotr_3); + + int64Variables[offset_result] = (short) (((short) (int64Variables[OFFSET_rotl_1] | int64Variables[OFFSET_rotr_1])) ^ ((short) (int64Variables[OFFSET_rotl_2] | int64Variables[OFFSET_rotr_2])) ^ int64Variables[OFFSET_rotr_3]); + int64Variables[(short) (offset_result + 1)] = (short) (((short) (int64Variables[(short) (OFFSET_rotl_1 + 1)] | int64Variables[(short) (OFFSET_rotr_1 + 1)])) ^ ((short) (int64Variables[(short) (OFFSET_rotl_2 + 1)] | int64Variables[(short) (OFFSET_rotr_2 + 1)])) ^ int64Variables[(short) (OFFSET_rotr_3 + 1)]); + int64Variables[(short) (offset_result + 2)] = (short) (((short) (int64Variables[(short) (OFFSET_rotl_1 + 2)] | int64Variables[(short) (OFFSET_rotr_1 + 2)])) ^ ((short) (int64Variables[(short) (OFFSET_rotl_2 + 2)] | int64Variables[(short) (OFFSET_rotr_2 + 2)])) ^ int64Variables[(short) (OFFSET_rotr_3 + 2)]); + int64Variables[(short) (offset_result + 3)] = (short) (((short) (int64Variables[(short) (OFFSET_rotl_1 + 3)] | int64Variables[(short) (OFFSET_rotr_1 + 3)])) ^ ((short) (int64Variables[(short) (OFFSET_rotl_2 + 3)] | int64Variables[(short) (OFFSET_rotr_2 + 3)])) ^ int64Variables[(short) (OFFSET_rotr_3 + 3)]); + + return offset_result; + } + + short Sigma1(Int64 x, short offset_result) { + x.get(int64Variables, OFFSET_Sigma1); + return Sigma1(OFFSET_Sigma1, offset_result); + } + + short Sigma1(short[] xArray, short xOffset, short offset_result) { + for (short i = 0; i < (short) 4; i++) int64Variables[(short) (OFFSET_Sigma1 + i)] = xArray[(short) (xOffset + i)]; + return Sigma1(OFFSET_Sigma1, offset_result); + } + + short Sigma1(short offset_x, short offset_result) { + rotl(offset_x,(byte)45,OFFSET_rotl_1); + rotr(offset_x,(byte)19,OFFSET_rotr_1); + + rotl(offset_x,(byte)3,OFFSET_rotl_2); + rotr(offset_x,(byte)61,OFFSET_rotr_2); + + rotr(offset_x,(byte)6,OFFSET_rotr_3); + + int64Variables[offset_result] = (short) (((short) (int64Variables[OFFSET_rotl_1] | int64Variables[OFFSET_rotr_1])) ^ ((short) (int64Variables[OFFSET_rotl_2] | int64Variables[OFFSET_rotr_2])) ^ int64Variables[OFFSET_rotr_3]); + int64Variables[(short) (offset_result + 1)] = (short) (((short) (int64Variables[(short) (OFFSET_rotl_1 + 1)] | int64Variables[(short) (OFFSET_rotr_1 + 1)])) ^ ((short) (int64Variables[(short) (OFFSET_rotl_2 + 1)] | int64Variables[(short) (OFFSET_rotr_2 + 1)])) ^ int64Variables[(short) (OFFSET_rotr_3 + 1)]); + int64Variables[(short) (offset_result + 2)] = (short) (((short) (int64Variables[(short) (OFFSET_rotl_1 + 2)] | int64Variables[(short) (OFFSET_rotr_1 + 2)])) ^ ((short) (int64Variables[(short) (OFFSET_rotl_2 + 2)] | int64Variables[(short) (OFFSET_rotr_2 + 2)])) ^ int64Variables[(short) (OFFSET_rotr_3 + 2)]); + int64Variables[(short) (offset_result + 3)] = (short) (((short) (int64Variables[(short) (OFFSET_rotl_1 + 3)] | int64Variables[(short) (OFFSET_rotr_1 + 3)])) ^ ((short) (int64Variables[(short) (OFFSET_rotl_2 + 3)] | int64Variables[(short) (OFFSET_rotr_2 + 3)])) ^ int64Variables[(short) (OFFSET_rotr_3 + 3)]); + + return offset_result; + } + + short safeAdd(short offset_x, Int64 y, short offset_result) { + y.get(int64Variables, OFFSET_safeAdd); + return safeAdd(offset_x, OFFSET_safeAdd, offset_result); + } + + Int64 safeAdd(short offset_x, Int64 y, Int64 result) { + y.get(int64Variables, OFFSET_safeAdd); + safeAdd(offset_x, OFFSET_safeAdd, OFFSET_safeAdd2); + result.set(int64Variables, OFFSET_safeAdd2); + return result; + } + + short safeAdd(short offset_x, short[] yArray, short yOffset, short[] resultArray, short resultOffset) { + int64Variables[OFFSET_safeAdd] = yArray[yOffset]; + int64Variables[(short) (OFFSET_safeAdd + 1)] = yArray[(short) (yOffset + 1)]; + int64Variables[(short) (OFFSET_safeAdd + 2)] = yArray[(short) (yOffset + 2)]; + int64Variables[(short) (OFFSET_safeAdd + 3)] = yArray[(short) (yOffset + 3)]; + + safeAdd(offset_x, OFFSET_safeAdd, OFFSET_safeAdd2); + + resultArray[resultOffset] = int64Variables[OFFSET_safeAdd2]; + resultArray[(short) (resultOffset + 1)] = int64Variables[(short) (OFFSET_safeAdd2 + 1)]; + resultArray[(short) (resultOffset + 2)] = int64Variables[(short) (OFFSET_safeAdd2 + 2)]; + resultArray[(short) (resultOffset + 3)] = int64Variables[(short) (OFFSET_safeAdd2 + 3)]; + return resultOffset; + } + + short safeAdd(short offset_x, short[] yArray, short yOffset, short offset_result) { + int64Variables[(short) (OFFSET_safeAdd + 0)] = yArray[(short) (yOffset + 0)]; + int64Variables[(short) (OFFSET_safeAdd + 1)] = yArray[(short) (yOffset + 1)]; + int64Variables[(short) (OFFSET_safeAdd + 2)] = yArray[(short) (yOffset + 2)]; + int64Variables[(short) (OFFSET_safeAdd + 3)] = yArray[(short) (yOffset + 3)]; + return safeAdd(offset_x, OFFSET_safeAdd, offset_result); + } + + + short safeAdd(short offset_x, short offset_y, short offset_result) { + + boolean bOverflow = false; + short a1 = (short) ((int64Variables[(short) (offset_x + 3)] & 0xFF) + (int64Variables[(short) (offset_y + 3)] & 0xFF)); + short a2 = (short) ((int64Variables[(short) (offset_x + 3)] >> 8)+(int64Variables[(short) (offset_y + 3)] >> 8)+(a1 >>> 8)); + short lowest = (short) (((a2 & 0xFF)<< 8)|(a1 & 0xFF)); + + a1 = (short) ((int64Variables[(short) (offset_x + 2)] & 0xFF) + (int64Variables[(short) (offset_y + 2)] & 0xFF) + (a2 >>> 8)); + a2 = (short) ((int64Variables[(short) (offset_x + 2)] >> 8)+(int64Variables[(short) (offset_y + 2)] >> 8)+(a1 >>> 8)); + short lower = (short) (((a2 & 0xFF)<< 8)|(a1 & 0xFF)); + if (bOverflow) { + lower++; + bOverflow = false; + if (lower == 0) bOverflow = true; + } + if (int64Variables[(short) (offset_x + 3)] < 0) { + lower++; + if (lower == 0) bOverflow = true; + } + if (int64Variables[(short) (offset_y + 3)] < 0) { + lower++; + if (lower == 0) bOverflow = true; + } + + + a1 = (short) ((int64Variables[(short) (offset_x + 1)] & 0xFF) + (int64Variables[(short) (offset_y + 1)] & 0xFF) + (a2 >>> 8)); + a2 = (short) ((int64Variables[(short) (offset_x + 1)] >> 8) + (int64Variables[(short) (offset_y + 1)] >> 8)+(a1 >>> 8)); + short higher = (short) (((a2 & 0xFF)<< 8)|(a1 & 0xFF)); + if (bOverflow) { + higher++; + bOverflow = false; + if (higher == 0) bOverflow = true; + } + if (int64Variables[(short) (offset_x + 2)] < 0) { + higher++; + if (higher == 0) bOverflow = true; + } + if (int64Variables[(short) (offset_y + 2)] < 0) { + higher++; + if (higher == 0) bOverflow = true; + } + + a1 = (short) ((int64Variables[offset_x] & 0xFF) + (int64Variables[offset_y] & 0xFF) + (a2 >>> 8)); + a2 = (short) ((int64Variables[offset_x] >> 8)+(int64Variables[offset_y] >> 8) + (a1 >>> 8)); + short highest = (short) (((a2 & 0xFF)<< 8)|(a1 & 0xFF)); + + if (bOverflow) { + highest++; + } + if (int64Variables[(short) (offset_x + 1)] < 0) { + highest++; + } + if (int64Variables[(short) (offset_y + 1)] < 0) { + highest++; + } + + int64Variables[offset_result] = highest; + int64Variables[(short) (offset_result + 1)] = higher; + int64Variables[(short) (offset_result + 2)] = lower; + int64Variables[(short) (offset_result + 3)] = lowest; + + return offset_result; + } + } + + static class Int64 { + public short highest; + public short higher; + public short lower; + public short lowest; + + public Int64() { + } + public Int64(short highest, short higher, short lower, short lowest) { + set(highest, higher, lower, lowest); + } + public Int64(Int64 template) { + set(template); + } + public void set(short highest, short higher, short lower, short lowest) { + this.highest = highest; + this.higher = higher; + this.lower = lower; + this.lowest = lowest; + } + public void set(Int64 template) { + this.highest = template.highest; + this.higher = template.higher; + this.lower = template.lower; + this.lowest = template.lowest; + } + public void set(byte[] value, short offset) { + this.highest = (short) (value[offset] << 8 | (short) ( value[(short) (offset + 1)] & 0xff)); + this.higher = (short) (value[(short) (offset + 2)] << 8 |(short) ( value[(short) (offset + 3)] & 0xff)); + this.lower = (short) (value[(short) (offset + 4)] << 8 | (short) ( value[(short) (offset + 5)] & 0xff)); + this.lowest = (short) (value[(short) (offset + 6)] << 8 | (short) ( value[(short) (offset + 7)] & 0xff)); + } + + public void set(short[] value, short offset) { + this.highest = value[offset]; + this.higher = value[(short) (offset + 1)]; + this.lower = value[(short) (offset + 2)]; + this.lowest = value[(short) (offset + 3)]; + } + + public void get(byte[] value, short offset) { + value[offset] = (byte) (highest >> 8); + value[(short) (offset + 1)] = (byte) (highest & 0xff); + value[(short) (offset + 2)] = (byte) (higher >> 8); + value[(short) (offset + 3)] = (byte) (higher & 0xff); + value[(short) (offset + 4)] = (byte) (lower >> 8); + value[(short) (offset + 5)] = (byte) (lower & 0xff); + value[(short) (offset + 6)] = (byte) (lowest >> 8); + value[(short) (offset + 7)] = (byte) (lowest & 0xff); + } + public void get(short[] value, short offset) { + value[offset] = highest; + value[(short) (offset + 1)] = higher; + value[(short) (offset + 2)] = lower; + value[(short) (offset + 3)] = lowest; + } + } +} From f49bc3390293899d890084eb5b1fe950fe9f169b Mon Sep 17 00:00:00 2001 From: kaichaosun Date: Tue, 3 Sep 2024 18:40:13 +0800 Subject: [PATCH 2/6] load seed for ed25519 --- src/main/java/im/status/keycard/Crypto.java | 15 +++++++++++++++ .../java/im/status/keycard/KeycardApplet.java | 17 ++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/main/java/im/status/keycard/Crypto.java b/src/main/java/im/status/keycard/Crypto.java index 23fe94a..27ad8ec 100644 --- a/src/main/java/im/status/keycard/Crypto.java +++ b/src/main/java/im/status/keycard/Crypto.java @@ -25,6 +25,7 @@ public class Crypto { final static private short HMAC_BLOCK_SIZE = (short) 128; final static private byte[] KEY_BITCOIN_SEED = {'B', 'i', 't', 'c', 'o', 'i', 'n', ' ', 's', 'e', 'e', 'd'}; + final static public byte[] KEY_ED25519_SEED = {'e', 'd', '2', '5', '5', '1', '9', ' ', 's', 'e', 'e', 'd'}; // The below 5 objects can be accessed anywhere from the entire applet RandomData random; @@ -105,6 +106,20 @@ void bip32MasterFromSeed(byte[] seed, short seedOff, short seedSize, byte[] mast hmacSHA512(KEY_BITCOIN_SEED, (short) 0, (short) KEY_BITCOIN_SEED.length, seed, seedOff, seedSize, masterKey, keyOff); } + /** + * Applies the algorithm for master key derivation defined by SLIP-10 to the binary seed provided as input. + * + * @param curveKey the key used for hmac512 + * @param seed the binary seed + * @param seedOff the offset of the binary seed + * @param seedSize the size of the binary seed + * @param masterKey the output buffer + * @param keyOff the offset in the output buffer + */ + void slip10MasterFromSeed(byte[] curveKey, byte[] seed, short seedOff, short seedSize, byte[] masterKey, short keyOff) { + hmacSHA512(curveKey, (short) 0, (short) KEY_BITCOIN_SEED.length, seed, seedOff, seedSize, masterKey, keyOff); + } + /** * Fixes the S value of the signature as described in BIP-62 to avoid malleable signatures. It also fixes the all * internal TLV length fields. Returns the number of bytes by which the overall signature length changed (0 or -1). diff --git a/src/main/java/im/status/keycard/KeycardApplet.java b/src/main/java/im/status/keycard/KeycardApplet.java index 798e403..6b15a09 100644 --- a/src/main/java/im/status/keycard/KeycardApplet.java +++ b/src/main/java/im/status/keycard/KeycardApplet.java @@ -47,6 +47,8 @@ public class KeycardApplet extends Applet { static final short CHAIN_CODE_SIZE = 32; static final short KEY_UID_LENGTH = 32; static final short BIP39_SEED_SIZE = CHAIN_CODE_SIZE * 2; + static final short PRIVATE_KEY_SIZE = 32; + static final short PUBLIC_KEY_SIZE = 32; static final byte GET_STATUS_P1_APPLICATION = 0x00; static final byte GET_STATUS_P1_KEY_PATH = 0x01; @@ -129,6 +131,9 @@ public class KeycardApplet extends Applet { private byte[] chainCode; private boolean isExtended; + private byte[] ed25519MasterPrivate; + private byte[] ed25519MasterPublic; + private byte[] tmpPath; private short tmpPathLen; @@ -185,6 +190,9 @@ public KeycardApplet(byte[] bArray, short bOffset, byte bLength) { altChainCode = new byte[CHAIN_CODE_SIZE]; chainCode = masterChainCode; + ed25519MasterPrivate = new byte[PRIVATE_KEY_SIZE]; + ed25519MasterPublic = new byte[PUBLIC_KEY_SIZE]; + keyPath = new byte[KEY_PATH_MAX_DEPTH * 4]; pinlessPath = new byte[KEY_PATH_MAX_DEPTH * 4]; tmpPath = JCSystem.makeTransientByteArray((short)(KEY_PATH_MAX_DEPTH * 4), JCSystem.CLEAR_ON_RESET); @@ -788,8 +796,13 @@ private void loadSeed(byte[] apduBuffer) { ISOException.throwIt(ISO7816.SW_WRONG_DATA); } + byte[] apduBufferEd = new byte[BIP39_SEED_SIZE]; + Util.arrayCopy(apduBuffer, ISO7816.OFFSET_CDATA, apduBufferEd, (short) 0, BIP39_SEED_SIZE); + crypto.bip32MasterFromSeed(apduBuffer, (short) ISO7816.OFFSET_CDATA, BIP39_SEED_SIZE, apduBuffer, (short) ISO7816.OFFSET_CDATA); + crypto.slip10MasterFromSeed(Crypto.KEY_ED25519_SEED, apduBufferEd, (short) 0, BIP39_SEED_SIZE, apduBufferEd, (short) 0); + JCSystem.beginTransaction(); isExtended = true; @@ -797,9 +810,11 @@ private void loadSeed(byte[] apduBuffer) { Util.arrayCopy(apduBuffer, (short) (ISO7816.OFFSET_CDATA + CHAIN_CODE_SIZE), masterChainCode, (short) 0, CHAIN_CODE_SIZE); short pubLen = secp256k1.derivePublicKey(masterPrivate, apduBuffer, (short) 0); - masterPublic.setW(apduBuffer, (short) 0, pubLen); + Util.arrayCopy(apduBufferEd, (short) 0, ed25519MasterPrivate, (short) 0, PRIVATE_KEY_SIZE); + Util.arrayCopy(apduBufferEd, (short) PRIVATE_KEY_SIZE, ed25519MasterPublic, (short) 0, PUBLIC_KEY_SIZE); + resetKeyStatus(); JCSystem.commitTransaction(); } From 8b22645ae6306d855db4f8347319febcf8d44e54 Mon Sep 17 00:00:00 2001 From: kaichaosun Date: Tue, 3 Sep 2024 23:58:47 +0800 Subject: [PATCH 3/6] ed25519 derive --- src/main/java/im/status/keycard/Crypto.java | 2 +- .../java/im/status/keycard/KeycardApplet.java | 141 +++++++++++++++++- 2 files changed, 138 insertions(+), 5 deletions(-) diff --git a/src/main/java/im/status/keycard/Crypto.java b/src/main/java/im/status/keycard/Crypto.java index 27ad8ec..0dac5b4 100644 --- a/src/main/java/im/status/keycard/Crypto.java +++ b/src/main/java/im/status/keycard/Crypto.java @@ -117,7 +117,7 @@ void bip32MasterFromSeed(byte[] seed, short seedOff, short seedSize, byte[] mast * @param keyOff the offset in the output buffer */ void slip10MasterFromSeed(byte[] curveKey, byte[] seed, short seedOff, short seedSize, byte[] masterKey, short keyOff) { - hmacSHA512(curveKey, (short) 0, (short) KEY_BITCOIN_SEED.length, seed, seedOff, seedSize, masterKey, keyOff); + hmacSHA512(curveKey, (short) 0, (short) curveKey.length, seed, seedOff, seedSize, masterKey, keyOff); } /** diff --git a/src/main/java/im/status/keycard/KeycardApplet.java b/src/main/java/im/status/keycard/KeycardApplet.java index 6b15a09..f5ee90c 100644 --- a/src/main/java/im/status/keycard/KeycardApplet.java +++ b/src/main/java/im/status/keycard/KeycardApplet.java @@ -48,7 +48,6 @@ public class KeycardApplet extends Applet { static final short KEY_UID_LENGTH = 32; static final short BIP39_SEED_SIZE = CHAIN_CODE_SIZE * 2; static final short PRIVATE_KEY_SIZE = 32; - static final short PUBLIC_KEY_SIZE = 32; static final byte GET_STATUS_P1_APPLICATION = 0x00; static final byte GET_STATUS_P1_KEY_PATH = 0x01; @@ -132,7 +131,9 @@ public class KeycardApplet extends Applet { private boolean isExtended; private byte[] ed25519MasterPrivate; - private byte[] ed25519MasterPublic; + private byte[] ed25519MasterChainCode; + private byte[] ed25519TmpPrivate; + private byte[] ed25519TmpChainCode; private byte[] tmpPath; private short tmpPathLen; @@ -191,7 +192,9 @@ public KeycardApplet(byte[] bArray, short bOffset, byte bLength) { chainCode = masterChainCode; ed25519MasterPrivate = new byte[PRIVATE_KEY_SIZE]; - ed25519MasterPublic = new byte[PUBLIC_KEY_SIZE]; + ed25519MasterChainCode = new byte[CHAIN_CODE_SIZE]; + ed25519TmpPrivate = new byte[PRIVATE_KEY_SIZE]; + ed25519TmpChainCode = new byte[CHAIN_CODE_SIZE]; keyPath = new byte[KEY_PATH_MAX_DEPTH * 4]; pinlessPath = new byte[KEY_PATH_MAX_DEPTH * 4]; @@ -813,7 +816,7 @@ private void loadSeed(byte[] apduBuffer) { masterPublic.setW(apduBuffer, (short) 0, pubLen); Util.arrayCopy(apduBufferEd, (short) 0, ed25519MasterPrivate, (short) 0, PRIVATE_KEY_SIZE); - Util.arrayCopy(apduBufferEd, (short) PRIVATE_KEY_SIZE, ed25519MasterPublic, (short) 0, PUBLIC_KEY_SIZE); + Util.arrayCopy(apduBufferEd, (short) PRIVATE_KEY_SIZE, ed25519MasterChainCode, (short) 0, CHAIN_CODE_SIZE); resetKeyStatus(); JCSystem.commitTransaction(); @@ -952,6 +955,52 @@ private void doDerive(byte[] apduBuffer, short off) { } } + /** + * Internal derivation function for SLIP-10, called by DERIVE KEY and EXPORT KEY + * @param apduBuffer the APDU buffer + * @param off the offset in the APDU buffer relative to the data field + */ + private void doSlip10Derive(byte[] apduBuffer, short off) { + if (tmpPathLen == 0) { + Util.arrayCopy(ed25519MasterPrivate, (short) 0, derivationOutput, (short) 0, PRIVATE_KEY_SIZE); + return; + } + + short scratchOff = (short) (ISO7816.OFFSET_CDATA + off); + short dataOff = (short) (scratchOff + Crypto.KEY_DERIVATION_SCRATCH_SIZE); + + short pubKeyOff = (short) (dataOff + masterPrivate.getS(apduBuffer, dataOff)); + pubKeyOff = Util.arrayCopyNonAtomic(chainCode, (short) 0, apduBuffer, pubKeyOff, CHAIN_CODE_SIZE); + + if (!crypto.bip32IsHardened(tmpPath, (short) 0)) { + masterPublic.getW(apduBuffer, pubKeyOff); + } else { + apduBuffer[pubKeyOff] = 0; + } + + byte[] dataTmp = new byte[Crypto.KEY_DERIVATION_SCRATCH_SIZE]; + dataTmp[0] = 0; + Util.arrayCopy(ed25519MasterPrivate, (short) 0, dataTmp, (short) 1, PRIVATE_KEY_SIZE); + Util.arrayCopy(tmpPath, (short) 0, dataTmp, (short) (PRIVATE_KEY_SIZE + 1), (short) 4); + + crypto.slip10MasterFromSeed(ed25519MasterChainCode, dataTmp, (short) 0, (short) dataTmp.length, derivationOutput, (short) 0); + + Util.arrayCopy(derivationOutput, (short) 0, ed25519TmpPrivate, (short) 0, PRIVATE_KEY_SIZE); + Util.arrayCopy(derivationOutput, (short) PRIVATE_KEY_SIZE, ed25519TmpChainCode, (short) 0, CHAIN_CODE_SIZE); + + for (short i = 0; i < tmpPathLen; i += 4) { + if (i > 0) { + Util.arrayCopy(ed25519TmpPrivate, (short) 0, dataTmp, (short) 1, PRIVATE_KEY_SIZE); + Util.arrayCopy(tmpPath, (short) i, dataTmp, (short) (PRIVATE_KEY_SIZE + 1), (short) 4); + + crypto.slip10MasterFromSeed(ed25519TmpChainCode, dataTmp, (short) 0, (short) dataTmp.length, derivationOutput, (short) 0); + + Util.arrayCopy(derivationOutput, (short) 0, ed25519TmpPrivate, (short) 0, PRIVATE_KEY_SIZE); + Util.arrayCopy(derivationOutput, (short) PRIVATE_KEY_SIZE, ed25519TmpChainCode, (short) 0, CHAIN_CODE_SIZE); + } + } + } + /** * Generates a mnemonic phrase according to the BIP39 specifications. Requires an open secure channel. Since embedding * the strings in the applet would be unreasonable, the data returned is actually a sequence of 16-bit big-endian @@ -1199,6 +1248,90 @@ private void sign(APDU apdu) { } } + /** + * Processes the SIGN command with ed25519 signature. Requires a secure channel to open and either the PIN to be verified or the PIN-less key + * path to be the current key path. This command supports signing a precomputed 32-bytes hash. The signature is + * generated using the current keys, so if no keys are loaded the command does not work. + * + * @param apdu the JCRE-owned APDU object. + */ + private void ed25519Sign(APDU apdu) { + byte[] apduBuffer = apdu.getBuffer(); + boolean usePinless = false; + boolean makeCurrent = false; + byte derivationSource = (byte) (apduBuffer[OFFSET_P1] & DERIVE_P1_SOURCE_MASK); + + switch((byte) (apduBuffer[OFFSET_P1] & ~DERIVE_P1_SOURCE_MASK)) { + case SIGN_P1_CURRENT_KEY: + derivationSource = DERIVE_P1_SOURCE_CURRENT; + break; + case SIGN_P1_DERIVE: + break; + case SIGN_P1_DERIVE_AND_MAKE_CURRENT: + makeCurrent = true; + break; + case SIGN_P1_PINLESS: + usePinless = true; + derivationSource = DERIVE_P1_SOURCE_PINLESS; + break; + default: + ISOException.throwIt(ISO7816.SW_WRONG_P1P2); + return; + } + + short len; + + if (usePinless && !secureChannel.isOpen()) { + len = (short) (apduBuffer[ISO7816.OFFSET_LC] & (short) 0xff); + } else { + len = secureChannel.preprocessAPDU(apduBuffer); + } + + if (usePinless && pinlessPathLen == 0) { + ISOException.throwIt(SW_REFERENCED_DATA_NOT_FOUND); + } + + if (len < MessageDigest.LENGTH_SHA_256) { + ISOException.throwIt(ISO7816.SW_WRONG_DATA); + } + + short pathLen = (short) (len - MessageDigest.LENGTH_SHA_256); + updateDerivationPath(apduBuffer, MessageDigest.LENGTH_SHA_256, pathLen, derivationSource); + + if (!((pin.isValidated() || usePinless || isPinless()) && masterPrivate.isInitialized())) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + + doSlip10Derive(apduBuffer, MessageDigest.LENGTH_SHA_256); + + apduBuffer[SecureChannel.SC_OUT_OFFSET] = TLV_SIGNATURE_TEMPLATE; + apduBuffer[(short)(SecureChannel.SC_OUT_OFFSET + 3)] = TLV_PUB_KEY; + short outLen = apduBuffer[(short)(SecureChannel.SC_OUT_OFFSET + 4)] = Crypto.KEY_PUB_SIZE; + + secp256k1.derivePublicKey(derivationOutput, (short) 0, apduBuffer, (short) (SecureChannel.SC_OUT_OFFSET + 5)); + + outLen += 5; + short sigOff = (short) (SecureChannel.SC_OUT_OFFSET + outLen); + + signature.init(secp256k1.tmpECPrivateKey, Signature.MODE_SIGN); + + outLen += signature.signPreComputedHash(apduBuffer, ISO7816.OFFSET_CDATA, MessageDigest.LENGTH_SHA_256, apduBuffer, sigOff); + outLen += crypto.fixS(apduBuffer, sigOff); + + apduBuffer[(short)(SecureChannel.SC_OUT_OFFSET + 1)] = (byte) 0x81; + apduBuffer[(short)(SecureChannel.SC_OUT_OFFSET + 2)] = (byte) (outLen - 3); + + if (makeCurrent) { + commitTmpPath(); + } + + if (secureChannel.isOpen()) { + secureChannel.respond(apdu, outLen, ISO7816.SW_NO_ERROR); + } else { + apdu.setOutgoingAndSend(SecureChannel.SC_OUT_OFFSET, outLen); + } + } + /** * Processes the SET PINLESS PATH command. Requires an open secure channel and the PIN to be verified. It does not * require keys to be loaded or the current key path to be set at a specific value. The data is formatted in the same From 26ac37011e829e26c1903bac16c3bc57e119dc41 Mon Sep 17 00:00:00 2001 From: kaichaosun Date: Wed, 4 Sep 2024 18:31:48 +0800 Subject: [PATCH 4/6] ed25519 sign init --- src/main/java/im/status/keycard/Ed25519.java | 200 ++++++++++++++++++ .../java/im/status/keycard/KeycardApplet.java | 64 ++++-- 2 files changed, 244 insertions(+), 20 deletions(-) create mode 100755 src/main/java/im/status/keycard/Ed25519.java diff --git a/src/main/java/im/status/keycard/Ed25519.java b/src/main/java/im/status/keycard/Ed25519.java new file mode 100755 index 0000000..f4d0a50 --- /dev/null +++ b/src/main/java/im/status/keycard/Ed25519.java @@ -0,0 +1,200 @@ +package im.status.keycard; + +import javacard.framework.*; +import javacard.security.*; +import im.status.keycard.jcmathlib.*; +import im.status.keycard.swalgs.*; + +public class Ed25519 { + public final static boolean DEBUG = true; + public final static short CARD = OperationSupport.JCOP4_P71; // TODO set your card + // public final static short CARD = OperationSupport.JCOP4_P71; // NXP J3Rxxx + // public final static short CARD = OperationSupport.JCOP3_P60; // NXP J3H145 + // public final static short CARD = OperationSupport.JCOP21; // NXP J2E145 + // public final static short CARD = OperationSupport.SECORA; // Infineon Secora ID S + + + private ResourceManager rm; + private ECCurve curve; + private BigNat privateKey, privateNonce, signature; + private BigNat transformC, transformA3, transformX, transformY, eight; + private ECPoint point; + + private final byte[] prefix = new byte[32]; + private final byte[] publicKey = new byte[32]; + private final byte[] publicNonce = new byte[32]; + + private MessageDigest hasher; + + private final byte[] ramArray = JCSystem.makeTransientByteArray((short) Wei25519.G.length, JCSystem.CLEAR_ON_DESELECT); + private final RandomData random = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM); + + public Ed25519() { + OperationSupport.getInstance().setCard(CARD); + + try { + hasher = MessageDigest.getInstance(MessageDigest.ALG_SHA_512, false); + } catch (CryptoException e) { + hasher = new Sha2(Sha2.SHA_512); + } + + rm = new ResourceManager((short) 256); + + privateKey = new BigNat((short) 32, JCSystem.MEMORY_TYPE_PERSISTENT, rm); + + privateNonce = new BigNat((short) 64, JCSystem.MEMORY_TYPE_TRANSIENT_DESELECT, rm); + signature = new BigNat((short) 64, JCSystem.MEMORY_TYPE_TRANSIENT_DESELECT, rm); + + transformC = new BigNat((short) Consts.TRANSFORM_C.length, JCSystem.MEMORY_TYPE_PERSISTENT, rm); + transformC.fromByteArray(Consts.TRANSFORM_C, (short) 0, (short) Consts.TRANSFORM_C.length); + transformA3 = new BigNat((short) Consts.TRANSFORM_A3.length, JCSystem.MEMORY_TYPE_PERSISTENT, rm); + transformA3.fromByteArray(Consts.TRANSFORM_A3, (short) 0, (short) Consts.TRANSFORM_A3.length); + transformX = new BigNat((short) 32, JCSystem.MEMORY_TYPE_TRANSIENT_RESET, rm); + transformY = new BigNat((short) 32, JCSystem.MEMORY_TYPE_TRANSIENT_RESET, rm); + + eight = new BigNat((short) 1, JCSystem.MEMORY_TYPE_PERSISTENT, rm); + eight.setValue((byte) 8); + + curve = new ECCurve(Wei25519.p, Wei25519.a, Wei25519.b, Wei25519.G, Wei25519.r, Wei25519.k, rm); + point = new ECPoint(curve); + } + + public boolean select() { + curve.updateAfterReset(); + return true; + } + + public void setKeypair(byte[] masterKeyParam, byte[] apduBuffer, short offset) { + hasher.reset(); + hasher.doFinal(masterKeyParam, (short) 0, (short) 32, ramArray, (short) 0); + ramArray[0] &= (byte) 0xf8; // Clear lowest three bits + ramArray[31] &= (byte) 0x7f; // Clear highest bit + ramArray[31] |= (byte) 0x40; // Set second-highest bit + changeEndianity(ramArray, (short) 0, (short) 32); + + Util.arrayCopyNonAtomic(ramArray, (short) 32, prefix, (short) 0, (short) 32); + + privateKey.fromByteArray(ramArray, (short) 0, (short) 32); + privateKey.shiftRight((short) 3); // Required by smartcards (scalar must be lesser than r) + point.setW(curve.G, (short) 0, curve.POINT_SIZE); + point.multiplication(privateKey); + privateKey.fromByteArray(ramArray, (short) 0, (short) 32); // Reload private key + privateKey.mod(curve.rBN); + + point.multiplication(eight); // Compensate bit shift + + encodeEd25519(point, publicKey, (short) 0); + + Util.arrayCopyNonAtomic(publicKey, (short) 0, apduBuffer, (short) offset, (short) 32); + } + + private void setPublicKey(APDU apdu) { + byte[] apduBuffer = apdu.getBuffer(); + Util.arrayCopyNonAtomic(apduBuffer, ISO7816.OFFSET_CDATA, publicKey, (short) 0, (short) publicKey.length); + apdu.setOutgoing(); + } + + public void signInit() { + // Generate nonce R + randomNonce(); + point.setW(curve.G, (short) 0, curve.POINT_SIZE); + point.multiplication(privateNonce); + hasher.reset(); + + encodeEd25519(point, ramArray, (short) 0); + Util.arrayCopyNonAtomic(ramArray, (short) 0, publicNonce, (short) 0, curve.COORD_SIZE); + hasher.update(ramArray, (short) 0, curve.COORD_SIZE); // R + hasher.update(publicKey, (short) 0, curve.COORD_SIZE); // A + } + + private void signNonce(APDU apdu) { + byte[] apduBuffer = apdu.getBuffer(); + hasher.reset(); + Util.arrayCopyNonAtomic(apduBuffer, ISO7816.OFFSET_CDATA, publicNonce, (short) 0, curve.COORD_SIZE); + hasher.update(apduBuffer, ISO7816.OFFSET_CDATA, curve.COORD_SIZE); // R + hasher.update(publicKey, (short) 0, curve.COORD_SIZE); // A + apdu.setOutgoing(); + } + + private void signFinalize(APDU apdu) { + byte[] apduBuffer = apdu.getBuffer(); + short len = (short) ((short) apduBuffer[ISO7816.OFFSET_P1] & (short) 0xff); + hasher.doFinal(apduBuffer, ISO7816.OFFSET_CDATA, len, apduBuffer, (short) 0); // m + changeEndianity(apduBuffer, (short) 0, (short) 64); + signature.fromByteArray(apduBuffer, (short) 0, (short) 64); + signature.mod(curve.rBN); + signature.resize((short) 32); + + // Compute signature s = r + ex + signature.modMult(privateKey, curve.rBN); + signature.modAdd(privateNonce, curve.rBN); + + // Return signature (R, s) + Util.arrayCopyNonAtomic(publicNonce, (short) 0, apduBuffer, (short) 0, curve.COORD_SIZE); + signature.prependZeros(curve.COORD_SIZE, apduBuffer, curve.COORD_SIZE); + changeEndianity(apduBuffer, curve.COORD_SIZE, curve.COORD_SIZE); + apdu.setOutgoingAndSend((short) 0, (short) (curve.COORD_SIZE + curve.COORD_SIZE)); + } + + private void signUpdate(APDU apdu) { + byte[] apduBuffer = apdu.getBuffer(); + short len = (short) ((short) apduBuffer[ISO7816.OFFSET_P1] & (short) 0xff); + hasher.update(apduBuffer, ISO7816.OFFSET_CDATA, len); + apdu.setOutgoing(); + } + + private void encodeEd25519(ECPoint point, byte[] buffer, short offset) { + point.getW(ramArray, (short) 0); + + // Compute X + transformX.fromByteArray(ramArray, (short) 1, (short) 32); + transformY.fromByteArray(ramArray, (short) 33, (short) 32); + transformX.modSub(transformA3, curve.pBN); + transformX.modMult(transformC, curve.pBN); + transformY.modInv(curve.pBN); + transformX.modMult(transformY, curve.pBN); + + boolean xBit = transformX.isOdd(); + + // Compute Y + transformX.fromByteArray(ramArray, (short) 1, (short) 32); + transformX.modSub(transformA3, curve.pBN); + transformY.clone(transformX); + transformX.decrement(); + transformY.increment(); + transformY.mod(curve.pBN); + transformY.modInv(curve.pBN); + transformX.modMult(transformY, curve.pBN); + transformX.prependZeros(curve.COORD_SIZE, buffer, offset); + + buffer[offset] |= xBit ? (byte) 0x80 : (byte) 0x00; + + changeEndianity(buffer, offset, (short) 32); + } + + private void changeEndianity(byte[] array, short offset, short len) { + for (short i = 0; i < (short) (len / 2); ++i) { + byte tmp = array[(short) (offset + len - i - 1)]; + array[(short) (offset + len - i - 1)] = array[(short) (offset + i)]; + array[(short) (offset + i)] = tmp; + } + } + + // CAN BE USED ONLY IF NO OFFLOADING IS USED; OTHERWISE INSECURE! + private void deterministicNonce(byte[] msg, short offset, short len) { + hasher.reset(); + hasher.update(prefix, (short) 0, (short) 32); + hasher.doFinal(msg, offset, len, ramArray, (short) 0); + changeEndianity(ramArray, (short) 0, (short) 64); + privateNonce.fromByteArray(ramArray, (short) 0, (short) 64); + privateNonce.mod(curve.rBN); + privateNonce.resize((short) 32); + } + + public void randomNonce() { + random.generateData(ramArray, (short) 0, (short) 32); + privateNonce.fromByteArray(ramArray, (short) 0, (short) 32); + privateNonce.mod(curve.rBN); + privateNonce.resize((short) 32); + } +} diff --git a/src/main/java/im/status/keycard/KeycardApplet.java b/src/main/java/im/status/keycard/KeycardApplet.java index f5ee90c..5fc5b7d 100644 --- a/src/main/java/im/status/keycard/KeycardApplet.java +++ b/src/main/java/im/status/keycard/KeycardApplet.java @@ -27,6 +27,9 @@ public class KeycardApplet extends Applet { static final byte INS_EXPORT_KEY = (byte) 0xC2; static final byte INS_GET_DATA = (byte) 0xCA; static final byte INS_STORE_DATA = (byte) 0xE2; + static final byte INS_ED25519_SIGN_INIT = (byte) 0x30; + static final byte INS_ED25519_SIGN_UPDATE = (byte) 0x31; + static final byte INS_ED25519_SIGN_FINAL = (byte) 0x32; static final short SW_REFERENCED_DATA_NOT_FOUND = (short) 0x6A88; @@ -48,6 +51,7 @@ public class KeycardApplet extends Applet { static final short KEY_UID_LENGTH = 32; static final short BIP39_SEED_SIZE = CHAIN_CODE_SIZE * 2; static final short PRIVATE_KEY_SIZE = 32; + static final short PUBLIC_KEY_SIZE = 32; static final byte GET_STATUS_P1_APPLICATION = 0x00; static final byte GET_STATUS_P1_KEY_PATH = 0x01; @@ -150,6 +154,7 @@ public class KeycardApplet extends Applet { private Crypto crypto; private SECP256k1 secp256k1; + private Ed25519 ed25519; private byte[] derivationOutput; @@ -181,6 +186,7 @@ public static void install(byte[] bArray, short bOffset, byte bLength) { public KeycardApplet(byte[] bArray, short bOffset, byte bLength) { crypto = new Crypto(); secp256k1 = new SECP256k1(); + ed25519 = new Ed25519(); uid = new byte[UID_LENGTH]; crypto.random.generateData(uid, (short) 0, UID_LENGTH); @@ -286,6 +292,14 @@ public void process(APDU apdu) throws ISOException { case INS_SIGN: sign(apdu); break; + case INS_ED25519_SIGN_INIT: + ed25519SignInit(apdu); + break; + case INS_ED25519_SIGN_UPDATE: + ed25519SignUpdate(apdu); + case INS_ED25519_SIGN_FINAL: + ed25519SignFinal(apdu); + break; case INS_SET_PINLESS_PATH: setPinlessPath(apdu); break; @@ -1255,7 +1269,7 @@ private void sign(APDU apdu) { * * @param apdu the JCRE-owned APDU object. */ - private void ed25519Sign(APDU apdu) { + private void ed25519SignInit(APDU apdu) { byte[] apduBuffer = apdu.getBuffer(); boolean usePinless = false; boolean makeCurrent = false; @@ -1304,32 +1318,42 @@ private void ed25519Sign(APDU apdu) { doSlip10Derive(apduBuffer, MessageDigest.LENGTH_SHA_256); - apduBuffer[SecureChannel.SC_OUT_OFFSET] = TLV_SIGNATURE_TEMPLATE; - apduBuffer[(short)(SecureChannel.SC_OUT_OFFSET + 3)] = TLV_PUB_KEY; - short outLen = apduBuffer[(short)(SecureChannel.SC_OUT_OFFSET + 4)] = Crypto.KEY_PUB_SIZE; + short off = SecureChannel.SC_OUT_OFFSET; + short outLen = PUBLIC_KEY_SIZE; - secp256k1.derivePublicKey(derivationOutput, (short) 0, apduBuffer, (short) (SecureChannel.SC_OUT_OFFSET + 5)); + byte[] privateKey = new byte[PRIVATE_KEY_SIZE]; + Util.arrayCopy(derivationOutput, (short) 0, privateKey, (short) 0, PRIVATE_KEY_SIZE); + + ed25519.setKeypair(privateKey, apduBuffer, off); + ed25519.signInit(); - outLen += 5; - short sigOff = (short) (SecureChannel.SC_OUT_OFFSET + outLen); + if (makeCurrent) { + commitTmpPath(); + } - signature.init(secp256k1.tmpECPrivateKey, Signature.MODE_SIGN); + secureChannel.respond(apdu, outLen, ISO7816.SW_NO_ERROR); + } - outLen += signature.signPreComputedHash(apduBuffer, ISO7816.OFFSET_CDATA, MessageDigest.LENGTH_SHA_256, apduBuffer, sigOff); - outLen += crypto.fixS(apduBuffer, sigOff); + /** + * Processes the SIGN UPDATE command with ed25519 signature. Requires a secure channel to open and either the PIN to be verified or the PIN-less key + * path to be the current key path. This command supports signing a precomputed 32-bytes hash. The signature is + * generated using the current keys, so if no keys are loaded the command does not work. + * + * @param apdu the JCRE-owned APDU object. + */ + private void ed25519SignUpdate(APDU apdu) { - apduBuffer[(short)(SecureChannel.SC_OUT_OFFSET + 1)] = (byte) 0x81; - apduBuffer[(short)(SecureChannel.SC_OUT_OFFSET + 2)] = (byte) (outLen - 3); + } - if (makeCurrent) { - commitTmpPath(); - } + /** + * Processes the SIGN FINALIZE command with ed25519 signature. Requires a secure channel to open and either the PIN to be verified or the PIN-less key + * path to be the current key path. This command supports signing a precomputed 32-bytes hash. The signature is + * generated using the current keys, so if no keys are loaded the command does not work. + * + * @param apdu the JCRE-owned APDU object. + */ + private void ed25519SignFinal(APDU apdu) { - if (secureChannel.isOpen()) { - secureChannel.respond(apdu, outLen, ISO7816.SW_NO_ERROR); - } else { - apdu.setOutgoingAndSend(SecureChannel.SC_OUT_OFFSET, outLen); - } } /** From 161a609e5287fcfab2fbc97e211444b25c13ddc1 Mon Sep 17 00:00:00 2001 From: kaichaosun Date: Wed, 11 Sep 2024 15:45:12 +0800 Subject: [PATCH 5/6] ed25119 sign finalize --- src/main/java/im/status/keycard/Ed25519.java | 19 ++++++++----------- .../java/im/status/keycard/KeycardApplet.java | 5 ++++- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/main/java/im/status/keycard/Ed25519.java b/src/main/java/im/status/keycard/Ed25519.java index f4d0a50..fa7c044 100755 --- a/src/main/java/im/status/keycard/Ed25519.java +++ b/src/main/java/im/status/keycard/Ed25519.java @@ -15,7 +15,7 @@ public class Ed25519 { private ResourceManager rm; - private ECCurve curve; + public ECCurve curve; private BigNat privateKey, privateNonce, signature; private BigNat transformC, transformA3, transformX, transformY, eight; private ECPoint point; @@ -116,12 +116,10 @@ private void signNonce(APDU apdu) { apdu.setOutgoing(); } - private void signFinalize(APDU apdu) { - byte[] apduBuffer = apdu.getBuffer(); - short len = (short) ((short) apduBuffer[ISO7816.OFFSET_P1] & (short) 0xff); - hasher.doFinal(apduBuffer, ISO7816.OFFSET_CDATA, len, apduBuffer, (short) 0); // m - changeEndianity(apduBuffer, (short) 0, (short) 64); - signature.fromByteArray(apduBuffer, (short) 0, (short) 64); + public void signFinalize(byte[] apduBuffer, short off) { + hasher.doFinal(apduBuffer, ISO7816.OFFSET_CDATA, MessageDigest.LENGTH_SHA_256, apduBuffer, (short) off); // m + changeEndianity(apduBuffer, (short) off, (short) 64); + signature.fromByteArray(apduBuffer, (short) off, (short) 64); signature.mod(curve.rBN); signature.resize((short) 32); @@ -130,10 +128,9 @@ private void signFinalize(APDU apdu) { signature.modAdd(privateNonce, curve.rBN); // Return signature (R, s) - Util.arrayCopyNonAtomic(publicNonce, (short) 0, apduBuffer, (short) 0, curve.COORD_SIZE); - signature.prependZeros(curve.COORD_SIZE, apduBuffer, curve.COORD_SIZE); - changeEndianity(apduBuffer, curve.COORD_SIZE, curve.COORD_SIZE); - apdu.setOutgoingAndSend((short) 0, (short) (curve.COORD_SIZE + curve.COORD_SIZE)); + Util.arrayCopyNonAtomic(publicNonce, (short) 0, apduBuffer, (short) off, curve.COORD_SIZE); + signature.prependZeros(curve.COORD_SIZE, apduBuffer, (short) (curve.COORD_SIZE + off)); + changeEndianity(apduBuffer, (short) (curve.COORD_SIZE + off), curve.COORD_SIZE); } private void signUpdate(APDU apdu) { diff --git a/src/main/java/im/status/keycard/KeycardApplet.java b/src/main/java/im/status/keycard/KeycardApplet.java index 5fc5b7d..12d9304 100644 --- a/src/main/java/im/status/keycard/KeycardApplet.java +++ b/src/main/java/im/status/keycard/KeycardApplet.java @@ -1319,7 +1319,6 @@ private void ed25519SignInit(APDU apdu) { doSlip10Derive(apduBuffer, MessageDigest.LENGTH_SHA_256); short off = SecureChannel.SC_OUT_OFFSET; - short outLen = PUBLIC_KEY_SIZE; byte[] privateKey = new byte[PRIVATE_KEY_SIZE]; Util.arrayCopy(derivationOutput, (short) 0, privateKey, (short) 0, PRIVATE_KEY_SIZE); @@ -1327,6 +1326,10 @@ private void ed25519SignInit(APDU apdu) { ed25519.setKeypair(privateKey, apduBuffer, off); ed25519.signInit(); + ed25519.signFinalize(apduBuffer, (short) (off + 32)); + + short outLen = (short) (PUBLIC_KEY_SIZE + ed25519.curve.COORD_SIZE + ed25519.curve.COORD_SIZE); + if (makeCurrent) { commitTmpPath(); } From 11d5981102d9ab8dbd66d9d680738abb45db395b Mon Sep 17 00:00:00 2001 From: kaichaosun Date: Wed, 11 Sep 2024 16:40:25 +0800 Subject: [PATCH 6/6] clear files --- src/main/java/im/status/keycard/Ed25519.java | 56 +-- .../java/im/status/keycard/JCEd25519.java | 330 ------------------ .../java/im/status/keycard/KeycardApplet.java | 25 +- 3 files changed, 43 insertions(+), 368 deletions(-) delete mode 100755 src/main/java/im/status/keycard/JCEd25519.java diff --git a/src/main/java/im/status/keycard/Ed25519.java b/src/main/java/im/status/keycard/Ed25519.java index fa7c044..33d6d99 100755 --- a/src/main/java/im/status/keycard/Ed25519.java +++ b/src/main/java/im/status/keycard/Ed25519.java @@ -88,11 +88,11 @@ public void setKeypair(byte[] masterKeyParam, byte[] apduBuffer, short offset) { Util.arrayCopyNonAtomic(publicKey, (short) 0, apduBuffer, (short) offset, (short) 32); } - private void setPublicKey(APDU apdu) { - byte[] apduBuffer = apdu.getBuffer(); - Util.arrayCopyNonAtomic(apduBuffer, ISO7816.OFFSET_CDATA, publicKey, (short) 0, (short) publicKey.length); - apdu.setOutgoing(); - } + // private void setPublicKey(APDU apdu) { + // byte[] apduBuffer = apdu.getBuffer(); + // Util.arrayCopyNonAtomic(apduBuffer, ISO7816.OFFSET_CDATA, publicKey, (short) 0, (short) publicKey.length); + // apdu.setOutgoing(); + // } public void signInit() { // Generate nonce R @@ -107,14 +107,14 @@ public void signInit() { hasher.update(publicKey, (short) 0, curve.COORD_SIZE); // A } - private void signNonce(APDU apdu) { - byte[] apduBuffer = apdu.getBuffer(); - hasher.reset(); - Util.arrayCopyNonAtomic(apduBuffer, ISO7816.OFFSET_CDATA, publicNonce, (short) 0, curve.COORD_SIZE); - hasher.update(apduBuffer, ISO7816.OFFSET_CDATA, curve.COORD_SIZE); // R - hasher.update(publicKey, (short) 0, curve.COORD_SIZE); // A - apdu.setOutgoing(); - } + // private void signNonce(APDU apdu) { + // byte[] apduBuffer = apdu.getBuffer(); + // hasher.reset(); + // Util.arrayCopyNonAtomic(apduBuffer, ISO7816.OFFSET_CDATA, publicNonce, (short) 0, curve.COORD_SIZE); + // hasher.update(apduBuffer, ISO7816.OFFSET_CDATA, curve.COORD_SIZE); // R + // hasher.update(publicKey, (short) 0, curve.COORD_SIZE); // A + // apdu.setOutgoing(); + // } public void signFinalize(byte[] apduBuffer, short off) { hasher.doFinal(apduBuffer, ISO7816.OFFSET_CDATA, MessageDigest.LENGTH_SHA_256, apduBuffer, (short) off); // m @@ -133,12 +133,12 @@ public void signFinalize(byte[] apduBuffer, short off) { changeEndianity(apduBuffer, (short) (curve.COORD_SIZE + off), curve.COORD_SIZE); } - private void signUpdate(APDU apdu) { - byte[] apduBuffer = apdu.getBuffer(); - short len = (short) ((short) apduBuffer[ISO7816.OFFSET_P1] & (short) 0xff); - hasher.update(apduBuffer, ISO7816.OFFSET_CDATA, len); - apdu.setOutgoing(); - } + // private void signUpdate(APDU apdu) { + // byte[] apduBuffer = apdu.getBuffer(); + // short len = (short) ((short) apduBuffer[ISO7816.OFFSET_P1] & (short) 0xff); + // hasher.update(apduBuffer, ISO7816.OFFSET_CDATA, len); + // apdu.setOutgoing(); + // } private void encodeEd25519(ECPoint point, byte[] buffer, short offset) { point.getW(ramArray, (short) 0); @@ -178,15 +178,15 @@ private void changeEndianity(byte[] array, short offset, short len) { } // CAN BE USED ONLY IF NO OFFLOADING IS USED; OTHERWISE INSECURE! - private void deterministicNonce(byte[] msg, short offset, short len) { - hasher.reset(); - hasher.update(prefix, (short) 0, (short) 32); - hasher.doFinal(msg, offset, len, ramArray, (short) 0); - changeEndianity(ramArray, (short) 0, (short) 64); - privateNonce.fromByteArray(ramArray, (short) 0, (short) 64); - privateNonce.mod(curve.rBN); - privateNonce.resize((short) 32); - } + // private void deterministicNonce(byte[] msg, short offset, short len) { + // hasher.reset(); + // hasher.update(prefix, (short) 0, (short) 32); + // hasher.doFinal(msg, offset, len, ramArray, (short) 0); + // changeEndianity(ramArray, (short) 0, (short) 64); + // privateNonce.fromByteArray(ramArray, (short) 0, (short) 64); + // privateNonce.mod(curve.rBN); + // privateNonce.resize((short) 32); + // } public void randomNonce() { random.generateData(ramArray, (short) 0, (short) 32); diff --git a/src/main/java/im/status/keycard/JCEd25519.java b/src/main/java/im/status/keycard/JCEd25519.java deleted file mode 100755 index e8445df..0000000 --- a/src/main/java/im/status/keycard/JCEd25519.java +++ /dev/null @@ -1,330 +0,0 @@ -package im.status.keycard; - -import javacard.framework.*; -import javacard.security.*; -import im.status.keycard.jcmathlib.*; -import im.status.keycard.swalgs.*; - -public class JCEd25519 extends Applet { - public final static boolean DEBUG = true; - public final static short CARD = OperationSupport.SIMULATOR; // TODO set your card - // public final static short CARD = OperationSupport.JCOP4_P71; // NXP J3Rxxx - // public final static short CARD = OperationSupport.JCOP3_P60; // NXP J3H145 - // public final static short CARD = OperationSupport.JCOP21; // NXP J2E145 - // public final static short CARD = OperationSupport.SECORA; // Infineon Secora ID S - - - private ResourceManager rm; - private ECCurve curve; - private BigNat privateKey, privateNonce, signature; - private BigNat transformC, transformA3, transformX, transformY, eight; - private ECPoint point; - - private final byte[] masterKey = new byte[32]; - private final byte[] prefix = new byte[32]; - private final byte[] publicKey = new byte[32]; - private final byte[] publicNonce = new byte[32]; - - private MessageDigest hasher; - - private final byte[] ramArray = JCSystem.makeTransientByteArray((short) Wei25519.G.length, JCSystem.CLEAR_ON_DESELECT); - private final RandomData random = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM); - - private boolean initialized = false; - - public static void install(byte[] bArray, short bOffset, byte bLength) { - new JCEd25519(bArray, bOffset, bLength); - } - - public JCEd25519(byte[] buffer, short offset, byte length) { - OperationSupport.getInstance().setCard(CARD); - register(); - } - - public void process(APDU apdu) { - if (selectingApplet()) - return; - - if (!initialized) { - initialize(apdu); - } - - if (apdu.getBuffer()[ISO7816.OFFSET_CLA] != Consts.CLA_ED25519) - ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED); - - try { - switch (apdu.getBuffer()[ISO7816.OFFSET_INS]) { - case Consts.INS_KEYGEN: - generateKeypair(apdu); - break; - - case Consts.INS_SET_PUB: - setPublicKey(apdu); - break; - case Consts.INS_SIGN_INIT: - signInit(apdu); - break; - case Consts.INS_SIGN_NONCE: - signNonce(apdu); - break; - case Consts.INS_SIGN_FINALIZE: - signFinalize(apdu); - break; - case Consts.INS_SIGN_UPDATE: - signUpdate(apdu); - break; - - case Consts.INS_GET_PRIV: - if(!DEBUG) { - ISOException.throwIt(Consts.E_DEBUG_DISABLED); - } - privateKey.copyToByteArray(apdu.getBuffer(), (short) 0); - apdu.setOutgoingAndSend((short) 0, (short) 32); - break; - case Consts.INS_GET_PRIV_NONCE: - if(!DEBUG) { - ISOException.throwIt(Consts.E_DEBUG_DISABLED); - } - privateNonce.copyToByteArray(apdu.getBuffer(), (short) 0); - apdu.setOutgoingAndSend((short) 0, (short) 32); - break; - default: - ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); - } - } catch (ISOException e) { - throw e; // Our exception from code, just re-emit - } catch (ArrayIndexOutOfBoundsException e) { - ISOException.throwIt(Consts.SW_ArrayIndexOutOfBoundsException); - } catch (ArithmeticException e) { - ISOException.throwIt(Consts.SW_ArithmeticException); - } catch (ArrayStoreException e) { - ISOException.throwIt(Consts.SW_ArrayStoreException); - } catch (NullPointerException e) { - ISOException.throwIt(Consts.SW_NullPointerException); - } catch (NegativeArraySizeException e) { - ISOException.throwIt(Consts.SW_NegativeArraySizeException); - } catch (CryptoException e) { - ISOException.throwIt((short) (Consts.SW_CryptoException_prefix | e.getReason())); - } catch (SystemException e) { - ISOException.throwIt((short) (Consts.SW_SystemException_prefix | e.getReason())); - } catch (PINException e) { - ISOException.throwIt((short) (Consts.SW_PINException_prefix | e.getReason())); - } catch (TransactionException e) { - ISOException.throwIt((short) (Consts.SW_TransactionException_prefix | e.getReason())); - } catch (CardRuntimeException e) { - ISOException.throwIt((short) (Consts.SW_CardRuntimeException_prefix | e.getReason())); - } catch (Exception e) { - ISOException.throwIt(Consts.SW_Exception); - } - } - - public boolean select() { - if (initialized) { - curve.updateAfterReset(); - } - return true; - } - - private void initialize(APDU apdu) { - if (initialized) - ISOException.throwIt(Consts.E_ALREADY_INITIALIZED); - - try { - hasher = MessageDigest.getInstance(MessageDigest.ALG_SHA_512, false); - } catch (CryptoException e) { - hasher = new Sha2(Sha2.SHA_512); - } - - rm = new ResourceManager((short) 256); - - privateKey = new BigNat((short) 32, JCSystem.MEMORY_TYPE_PERSISTENT, rm); - - privateNonce = new BigNat((short) 64, JCSystem.MEMORY_TYPE_TRANSIENT_DESELECT, rm); - signature = new BigNat((short) 64, JCSystem.MEMORY_TYPE_TRANSIENT_DESELECT, rm); - - transformC = new BigNat((short) Consts.TRANSFORM_C.length, JCSystem.MEMORY_TYPE_PERSISTENT, rm); - transformC.fromByteArray(Consts.TRANSFORM_C, (short) 0, (short) Consts.TRANSFORM_C.length); - transformA3 = new BigNat((short) Consts.TRANSFORM_A3.length, JCSystem.MEMORY_TYPE_PERSISTENT, rm); - transformA3.fromByteArray(Consts.TRANSFORM_A3, (short) 0, (short) Consts.TRANSFORM_A3.length); - transformX = new BigNat((short) 32, JCSystem.MEMORY_TYPE_TRANSIENT_RESET, rm); - transformY = new BigNat((short) 32, JCSystem.MEMORY_TYPE_TRANSIENT_RESET, rm); - - eight = new BigNat((short) 1, JCSystem.MEMORY_TYPE_PERSISTENT, rm); - eight.setValue((byte) 8); - - curve = new ECCurve(Wei25519.p, Wei25519.a, Wei25519.b, Wei25519.G, Wei25519.r, Wei25519.k, rm); - point = new ECPoint(curve); - - initialized = true; - } - - private void generateKeypair(APDU apdu) { - if (!initialized) - ISOException.throwIt(Consts.E_UNINITIALIZED); - - byte[] apduBuffer = apdu.getBuffer(); - boolean offload = apduBuffer[ISO7816.OFFSET_P1] != (byte) 0x00; - - random.generateData(masterKey, (short) 0, (short) 32); - hasher.reset(); - hasher.doFinal(masterKey, (short) 0, (short) 32, ramArray, (short) 0); - ramArray[0] &= (byte) 0xf8; // Clear lowest three bits - ramArray[31] &= (byte) 0x7f; // Clear highest bit - ramArray[31] |= (byte) 0x40; // Set second-highest bit - changeEndianity(ramArray, (short) 0, (short) 32); - - Util.arrayCopyNonAtomic(ramArray, (short) 32, prefix, (short) 0, (short) 32); - - privateKey.fromByteArray(ramArray, (short) 0, (short) 32); - privateKey.shiftRight((short) 3); // Required by smartcards (scalar must be lesser than r) - point.setW(curve.G, (short) 0, curve.POINT_SIZE); - point.multiplication(privateKey); - privateKey.fromByteArray(ramArray, (short) 0, (short) 32); // Reload private key - privateKey.mod(curve.rBN); - - if(!offload) { - point.multiplication(eight); // Compensate bit shift - - encodeEd25519(point, publicKey, (short) 0); - - Util.arrayCopyNonAtomic(publicKey, (short) 0, apduBuffer, (short) 0, (short) 32); - apdu.setOutgoingAndSend((short) 0, (short) 32); - } else { - point.getW(apduBuffer, (short) 0); - apdu.setOutgoingAndSend((short) 0, curve.POINT_SIZE); - } - } - - private void setPublicKey(APDU apdu) { - if (!initialized) - ISOException.throwIt(Consts.E_UNINITIALIZED); - - byte[] apduBuffer = apdu.getBuffer(); - Util.arrayCopyNonAtomic(apduBuffer, ISO7816.OFFSET_CDATA, publicKey, (short) 0, (short) publicKey.length); - apdu.setOutgoing(); - } - - private void signInit(APDU apdu) { - if (!initialized) - ISOException.throwIt(Consts.E_UNINITIALIZED); - - byte[] apduBuffer = apdu.getBuffer(); - boolean offload = apduBuffer[ISO7816.OFFSET_P1] != (byte) 0x00; - - // Generate nonce R - randomNonce(); - point.setW(curve.G, (short) 0, curve.POINT_SIZE); - point.multiplication(privateNonce); - hasher.reset(); - if (offload) { - point.getW(apduBuffer, (short) 0); - apdu.setOutgoingAndSend((short) 0, curve.POINT_SIZE); - } else { - encodeEd25519(point, ramArray, (short) 0); - Util.arrayCopyNonAtomic(ramArray, (short) 0, publicNonce, (short) 0, curve.COORD_SIZE); - hasher.update(ramArray, (short) 0, curve.COORD_SIZE); // R - hasher.update(publicKey, (short) 0, curve.COORD_SIZE); // A - apdu.setOutgoing(); - } - } - - private void signNonce(APDU apdu) { - if (!initialized) - ISOException.throwIt(Consts.E_UNINITIALIZED); - - byte[] apduBuffer = apdu.getBuffer(); - hasher.reset(); - Util.arrayCopyNonAtomic(apduBuffer, ISO7816.OFFSET_CDATA, publicNonce, (short) 0, curve.COORD_SIZE); - hasher.update(apduBuffer, ISO7816.OFFSET_CDATA, curve.COORD_SIZE); // R - hasher.update(publicKey, (short) 0, curve.COORD_SIZE); // A - apdu.setOutgoing(); - } - - private void signFinalize(APDU apdu) { - if (!initialized) - ISOException.throwIt(Consts.E_UNINITIALIZED); - - byte[] apduBuffer = apdu.getBuffer(); - short len = (short) ((short) apduBuffer[ISO7816.OFFSET_P1] & (short) 0xff); - hasher.doFinal(apduBuffer, ISO7816.OFFSET_CDATA, len, apduBuffer, (short) 0); // m - changeEndianity(apduBuffer, (short) 0, (short) 64); - signature.fromByteArray(apduBuffer, (short) 0, (short) 64); - signature.mod(curve.rBN); - signature.resize((short) 32); - - // Compute signature s = r + ex - signature.modMult(privateKey, curve.rBN); - signature.modAdd(privateNonce, curve.rBN); - - // Return signature (R, s) - Util.arrayCopyNonAtomic(publicNonce, (short) 0, apduBuffer, (short) 0, curve.COORD_SIZE); - signature.prependZeros(curve.COORD_SIZE, apduBuffer, curve.COORD_SIZE); - changeEndianity(apduBuffer, curve.COORD_SIZE, curve.COORD_SIZE); - apdu.setOutgoingAndSend((short) 0, (short) (curve.COORD_SIZE + curve.COORD_SIZE)); - } - - private void signUpdate(APDU apdu) { - if (!initialized) - ISOException.throwIt(Consts.E_UNINITIALIZED); - - byte[] apduBuffer = apdu.getBuffer(); - short len = (short) ((short) apduBuffer[ISO7816.OFFSET_P1] & (short) 0xff); - hasher.update(apduBuffer, ISO7816.OFFSET_CDATA, len); - apdu.setOutgoing(); - } - - private void encodeEd25519(ECPoint point, byte[] buffer, short offset) { - point.getW(ramArray, (short) 0); - - // Compute X - transformX.fromByteArray(ramArray, (short) 1, (short) 32); - transformY.fromByteArray(ramArray, (short) 33, (short) 32); - transformX.modSub(transformA3, curve.pBN); - transformX.modMult(transformC, curve.pBN); - transformY.modInv(curve.pBN); - transformX.modMult(transformY, curve.pBN); - - boolean xBit = transformX.isOdd(); - - // Compute Y - transformX.fromByteArray(ramArray, (short) 1, (short) 32); - transformX.modSub(transformA3, curve.pBN); - transformY.clone(transformX); - transformX.decrement(); - transformY.increment(); - transformY.mod(curve.pBN); - transformY.modInv(curve.pBN); - transformX.modMult(transformY, curve.pBN); - transformX.prependZeros(curve.COORD_SIZE, buffer, offset); - - buffer[offset] |= xBit ? (byte) 0x80 : (byte) 0x00; - - changeEndianity(buffer, offset, (short) 32); - } - - private void changeEndianity(byte[] array, short offset, short len) { - for (short i = 0; i < (short) (len / 2); ++i) { - byte tmp = array[(short) (offset + len - i - 1)]; - array[(short) (offset + len - i - 1)] = array[(short) (offset + i)]; - array[(short) (offset + i)] = tmp; - } - } - - // CAN BE USED ONLY IF NO OFFLOADING IS USED; OTHERWISE INSECURE! - private void deterministicNonce(byte[] msg, short offset, short len) { - hasher.reset(); - hasher.update(prefix, (short) 0, (short) 32); - hasher.doFinal(msg, offset, len, ramArray, (short) 0); - changeEndianity(ramArray, (short) 0, (short) 64); - privateNonce.fromByteArray(ramArray, (short) 0, (short) 64); - privateNonce.mod(curve.rBN); - privateNonce.resize((short) 32); - } - - private void randomNonce() { - random.generateData(ramArray, (short) 0, (short) 32); - privateNonce.fromByteArray(ramArray, (short) 0, (short) 32); - privateNonce.mod(curve.rBN); - privateNonce.resize((short) 32); - } -} diff --git a/src/main/java/im/status/keycard/KeycardApplet.java b/src/main/java/im/status/keycard/KeycardApplet.java index 12d9304..809530d 100644 --- a/src/main/java/im/status/keycard/KeycardApplet.java +++ b/src/main/java/im/status/keycard/KeycardApplet.java @@ -295,11 +295,12 @@ public void process(APDU apdu) throws ISOException { case INS_ED25519_SIGN_INIT: ed25519SignInit(apdu); break; - case INS_ED25519_SIGN_UPDATE: - ed25519SignUpdate(apdu); - case INS_ED25519_SIGN_FINAL: - ed25519SignFinal(apdu); - break; + // case INS_ED25519_SIGN_UPDATE: + // ed25519SignUpdate(apdu); + // break; + // case INS_ED25519_SIGN_FINAL: + // ed25519SignFinal(apdu); + // break; case INS_SET_PINLESS_PATH: setPinlessPath(apdu); break; @@ -1334,7 +1335,11 @@ private void ed25519SignInit(APDU apdu) { commitTmpPath(); } - secureChannel.respond(apdu, outLen, ISO7816.SW_NO_ERROR); + if (secureChannel.isOpen()) { + secureChannel.respond(apdu, outLen, ISO7816.SW_NO_ERROR); + } else { + apdu.setOutgoingAndSend(SecureChannel.SC_OUT_OFFSET, outLen); + } } /** @@ -1344,9 +1349,9 @@ private void ed25519SignInit(APDU apdu) { * * @param apdu the JCRE-owned APDU object. */ - private void ed25519SignUpdate(APDU apdu) { + // private void ed25519SignUpdate(APDU apdu) { - } + // } /** * Processes the SIGN FINALIZE command with ed25519 signature. Requires a secure channel to open and either the PIN to be verified or the PIN-less key @@ -1355,9 +1360,9 @@ private void ed25519SignUpdate(APDU apdu) { * * @param apdu the JCRE-owned APDU object. */ - private void ed25519SignFinal(APDU apdu) { + // private void ed25519SignFinal(APDU apdu) { - } + // } /** * Processes the SET PINLESS PATH command. Requires an open secure channel and the PIN to be verified. It does not