From f1522fac81a3cd2ac2fe84e67a39202ef2ce2fa7 Mon Sep 17 00:00:00 2001 From: Michele Balistreri Date: Mon, 22 Jun 2020 14:21:28 +0300 Subject: [PATCH] first attempt --- .../java/im/status/keycard/CashApplet.java | 2 +- src/main/java/im/status/keycard/Crypto.java | 39 +++++-- .../java/im/status/keycard/KeycardApplet.java | 34 ++++-- .../java/im/status/keycard/SECP256k1.java | 108 +++++++++++++++++- .../java/im/status/keycard/KeycardTest.java | 73 ++++++++++++ 5 files changed, 237 insertions(+), 19 deletions(-) diff --git a/src/main/java/im/status/keycard/CashApplet.java b/src/main/java/im/status/keycard/CashApplet.java index dab799d..1067d3a 100644 --- a/src/main/java/im/status/keycard/CashApplet.java +++ b/src/main/java/im/status/keycard/CashApplet.java @@ -40,7 +40,7 @@ public class CashApplet extends Applet { */ public CashApplet(byte[] bArray, short bOffset, byte bLength) { crypto = new Crypto(); - secp256k1 = new SECP256k1(); + secp256k1 = new SECP256k1(crypto); keypair = new KeyPair(KeyPair.ALG_EC_FP, SECP256k1.SECP256K1_KEY_SIZE); publicKey = (ECPublicKey) keypair.getPublic(); diff --git a/src/main/java/im/status/keycard/Crypto.java b/src/main/java/im/status/keycard/Crypto.java index d374241..382333f 100644 --- a/src/main/java/im/status/keycard/Crypto.java +++ b/src/main/java/im/status/keycard/Crypto.java @@ -36,7 +36,7 @@ public class Crypto { private Signature hmacSHA512; private HMACKey hmacKey; - private byte[] hmacBlock; + protected byte[] hmacBlock; Crypto() { random = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM); @@ -150,7 +150,7 @@ public class Crypto { * @param out the output buffer * @param outOff the offset in the output buffer */ - private void hmacSHA512(byte[] key, short keyOff, short keyLen, byte[] in, short inOff, short inLen, byte[] out, short outOff) { + void hmacSHA512(byte[] key, short keyOff, short keyLen, byte[] in, short inOff, short inLen, byte[] out, short outOff) { if (hmacSHA512 != null) { hmacKey.setKey(key, keyOff, keyLen); hmacSHA512.init(hmacKey, Signature.MODE_SIGN); @@ -186,7 +186,7 @@ public class Crypto { * @param out the output buffer * @param outOff the offset in the output buffer */ - private void addm256(byte[] a, short aOff, byte[] b, short bOff, byte[] n, short nOff, byte[] out, short outOff) { + void addm256(byte[] a, short aOff, byte[] b, short bOff, byte[] n, short nOff, byte[] out, short outOff) { if ((add256(a, aOff, b, bOff, out, outOff) != 0) || (ucmp256(out, outOff, n, nOff) > 0)) { sub256(out, outOff, n, nOff, out, outOff); } @@ -201,7 +201,7 @@ public class Crypto { * @param bOff the offset of the b operand * @return the comparison result */ - private short ucmp256(byte[] a, short aOff, byte[] b, short bOff) { + short ucmp256(byte[] a, short aOff, byte[] b, short bOff) { short ai, bi; for (short i = 0 ; i < 32; i++) { ai = (short)(a[(short)(aOff + i)] & 0x00ff); @@ -222,7 +222,7 @@ public class Crypto { * @param aOff the offset of the a operand * @return true if a is 0, false otherwise */ - private boolean isZero256(byte[] a, short aOff) { + boolean isZero256(byte[] a, short aOff) { boolean isZero = true; for (short i = 0; i < (byte) 32; i++) { @@ -246,11 +246,11 @@ public class Crypto { * @param outOff the offset in the output buffer * @return the carry of the addition */ - private short add256(byte[] a, short aOff, byte[] b, short bOff, byte[] out, short outOff) { + short add256(byte[] a, short aOff, byte[] b, short bOff, byte[] out, short outOff) { short outI = 0; for (short i = 31 ; i >= 0 ; i--) { outI = (short) ((short)(a[(short)(aOff + i)] & 0xFF) + (short)(b[(short)(bOff + i)] & 0xFF) + outI); - out[(short)(outOff + i)] = (byte)outI ; + out[(short)(outOff + i)] = (byte)outI; outI = (short)(outI >> 8); } return outI; @@ -267,7 +267,7 @@ public class Crypto { * @param outOff the offset in the output buffer * @return the carry of the subtraction */ - private short sub256(byte[] a, short aOff, byte[] b, short bOff, byte[] out, short outOff) { + short sub256(byte[] a, short aOff, byte[] b, short bOff, byte[] out, short outOff) { short outI = 0; for (short i = 31 ; i >= 0 ; i--) { @@ -278,4 +278,27 @@ public class Crypto { return outI; } + + /** + * Subtraction of two 512-bit numbers. + * + * @param a the a operand + * @param aOff the offset of the a operand + * @param b the b operand + * @param bOff the offset of the b operand + * @param out the output buffer + * @param outOff the offset in the output buffer + * @return the carry of the subtraction + */ + short sub512(byte[] a, short aOff, byte[] b, short bOff, byte[] out, short outOff) { + short outI = 0; + + for (short i = 63 ; i >= 0 ; i--) { + outI = (short) ((short)(a[(short)(aOff + i)] & 0xFF) - (short)(b[(short)(bOff + i)] & 0xFF) - outI); + out[(short)(outOff + i)] = (byte)outI ; + outI = (short)(((outI >> 8) != 0) ? 1 : 0); + } + + return outI; + } } diff --git a/src/main/java/im/status/keycard/KeycardApplet.java b/src/main/java/im/status/keycard/KeycardApplet.java index cc71842..58483c7 100644 --- a/src/main/java/im/status/keycard/KeycardApplet.java +++ b/src/main/java/im/status/keycard/KeycardApplet.java @@ -2,9 +2,9 @@ package im.status.keycard; import javacard.framework.*; import javacard.security.*; -import javacardx.crypto.Cipher; import static javacard.framework.ISO7816.OFFSET_P1; +import static javacard.framework.ISO7816.OFFSET_P2; /** * The applet's main class. All incoming commands a processed by this class. @@ -68,6 +68,9 @@ public class KeycardApplet extends Applet { static final byte SIGN_P1_DERIVE_AND_MAKE_CURRENT = 0x02; static final byte SIGN_P1_PINLESS = 0x03; + static final byte SIGN_P2_ECDSA = 0x00; + static final byte SIGN_P2_SCHNORR = 0x01; + static final byte EXPORT_KEY_P1_CURRENT = 0x00; static final byte EXPORT_KEY_P1_DERIVE = 0x01; static final byte EXPORT_KEY_P1_DERIVE_AND_MAKE_CURRENT = 0x02; @@ -167,7 +170,7 @@ public class KeycardApplet extends Applet { */ public KeycardApplet(byte[] bArray, short bOffset, byte bLength) { crypto = new Crypto(); - secp256k1 = new SECP256k1(); + secp256k1 = new SECP256k1(crypto); uid = new byte[UID_LENGTH]; crypto.random.generateData(uid, (short) 0, UID_LENGTH); @@ -1104,6 +1107,20 @@ public class KeycardApplet extends Applet { return; } + boolean schnorr; + + switch(apduBuffer[OFFSET_P2]) { + case SIGN_P2_ECDSA: + schnorr = false; + break; + case SIGN_P2_SCHNORR: + schnorr = true; + break; + default: + ISOException.throwIt(ISO7816.SW_WRONG_P1P2); + return; + } + short len; if (usePinless && !secureChannel.isOpen()) { @@ -1148,10 +1165,13 @@ public class KeycardApplet extends Applet { outLen += 5; short sigOff = (short) (SecureChannel.SC_OUT_OFFSET + outLen); - signature.init(signingKey, Signature.MODE_SIGN); - - outLen += signature.signPreComputedHash(apduBuffer, ISO7816.OFFSET_CDATA, MessageDigest.LENGTH_SHA_256, apduBuffer, sigOff); - outLen += crypto.fixS(apduBuffer, sigOff); + if (schnorr) { + outLen += secp256k1.signSchnorr(signingKey, apduBuffer, (short) (SecureChannel.SC_OUT_OFFSET + 5), apduBuffer, ISO7816.OFFSET_CDATA, MessageDigest.LENGTH_SHA_256, apduBuffer, sigOff); + } else { + signature.init(signingKey, 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); @@ -1212,7 +1232,7 @@ public class KeycardApplet extends Applet { boolean publicOnly; - switch (apduBuffer[ISO7816.OFFSET_P2]) { + switch (apduBuffer[OFFSET_P2]) { case EXPORT_KEY_P2_PRIVATE_AND_PUBLIC: publicOnly = false; break; diff --git a/src/main/java/im/status/keycard/SECP256k1.java b/src/main/java/im/status/keycard/SECP256k1.java index 5f53de7..9571f1a 100644 --- a/src/main/java/im/status/keycard/SECP256k1.java +++ b/src/main/java/im/status/keycard/SECP256k1.java @@ -1,9 +1,14 @@ package im.status.keycard; +import javacard.framework.JCSystem; +import javacard.framework.Util; import javacard.security.ECKey; import javacard.security.ECPrivateKey; import javacard.security.KeyAgreement; import javacard.security.KeyBuilder; +import javacard.security.KeyPair; +import javacard.security.RSAPublicKey; +import javacardx.crypto.Cipher; /** * Utility methods to work with the SECP256k1 curve. This class is not meant to be instantiated, but its init method @@ -49,20 +54,52 @@ public class SECP256k1 { static final byte SECP256K1_K = (byte)0x01; static final short SECP256K1_KEY_SIZE = 256; + static final short SECP256K1_BYTE_SIZE = (short) (SECP256K1_KEY_SIZE / 8); + + static final short SCHNORR_K_OUT_OFF = (short) 0; + static final short SCHNORR_E_OUT_OFF = (short) (32 + SCHNORR_K_OUT_OFF); + static final short SCHNORR_E_32_OFF = (short) (32 + SCHNORR_E_OUT_OFF); + static final short SCHNORR_D_OUT_OFF = (short) (64 + SCHNORR_E_OUT_OFF); + static final short SCHNORR_D_32_OFF = (short) (32 + SCHNORR_D_OUT_OFF); + static final short SCHNORR_TMP1_OUT_OFF = (short) (64 + SCHNORR_D_OUT_OFF); + static final short SCHNORR_TMP1_32_OUT_OFF = (short) (32 + SCHNORR_TMP1_OUT_OFF); + static final short SCHNORR_TMP2_OUT_OFF = (short) (64 + SCHNORR_TMP1_OUT_OFF); + + static final short TMP_LEN = 288; private static final byte ALG_EC_SVDP_DH_PLAIN_XY = 6; // constant from JavaCard 3.0.5 - private KeyAgreement ecPointMultiplier; + private Crypto crypto; ECPrivateKey tmpECPrivateKey; + private KeyPair multPair; + private RSAPublicKey pow2; + private Cipher multCipher; + + static final byte[] CONST_TWO = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02}; + + private byte[] tmp; + /** * Allocates objects needed by this class. Must be invoked during the applet installation exactly 1 time. */ - SECP256k1() { + SECP256k1(Crypto crypto) { + this.crypto = crypto; + + this.tmp = JCSystem.makeTransientByteArray(TMP_LEN, JCSystem.CLEAR_ON_RESET); + this.ecPointMultiplier = KeyAgreement.getInstance(ALG_EC_SVDP_DH_PLAIN_XY, false); this.tmpECPrivateKey = (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, SECP256K1_KEY_SIZE, false); setCurveParameters(tmpECPrivateKey); + + multPair = new KeyPair(KeyPair.ALG_RSA_CRT, KeyBuilder.LENGTH_RSA_512); + multPair.genKeyPair(); + pow2 = (RSAPublicKey) multPair.getPublic(); + pow2.setExponent(CONST_TWO, (short) 0, (short) CONST_TWO.length); + + multCipher = Cipher.getInstance(Cipher.ALG_RSA_NOPAD, false); + multCipher.init(pow2, Cipher.MODE_ENCRYPT); } /** @@ -103,7 +140,7 @@ public class SECP256k1 { * @return the length of the public key */ short derivePublicKey(byte[] privateKey, short privOff, byte[] pubOut, short pubOff) { - tmpECPrivateKey.setS(privateKey, privOff, (short)(SECP256K1_KEY_SIZE/8)); + tmpECPrivateKey.setS(privateKey, privOff, SECP256K1_BYTE_SIZE); return derivePublicKey(tmpECPrivateKey, pubOut, pubOff); } @@ -123,4 +160,69 @@ public class SECP256k1 { ecPointMultiplier.init(privateKey); return ecPointMultiplier.generateSecret(point, pointOff, pointLen, out, outOff); } + + short signSchnorr(ECPrivateKey privKey, byte[] pubKey, short pubOff, byte[] data, short dataOff, short dataLen, byte[] output, short outOff) { + /* + The algorithm Sign(sk, m) is defined as: + Let d' = int(sk) + Fail if d' = 0 or d' ≥ n + Let P = d'⋅G + Let d = d' if has_square_y(P), otherwise let d = n - d' . + Let rand = hashBIPSchnorrDerive(bytes(d) || m). + Let k' = int(rand) mod n. + Fail if k' = 0. + Let R = k'⋅G. + Let k = k' if has_square_y(R), otherwise let k = n - k' . + Let e = int(hashBIPSchnorr(bytes(R) || bytes(P) || m)) mod n. + Return the signature bytes(R) || bytes((k + ed) mod n). + */ + //TODO: evaluate if mod must be really applied to the output of the RNG and SHA256, since the hash is statistically very unlikley to be higher than R + + crypto.random.generateData(tmp, SCHNORR_K_OUT_OFF, SECP256K1_BYTE_SIZE); + Util.arrayFillNonAtomic(tmp, SCHNORR_E_OUT_OFF, (short)(TMP_LEN - SCHNORR_E_OUT_OFF), (byte) 0x0); + + derivePublicKey(tmp, SCHNORR_K_OUT_OFF, output, outOff); + crypto.sha256.update(output, outOff, Crypto.KEY_PUB_SIZE); + crypto.sha256.update(pubKey, pubOff, Crypto.KEY_PUB_SIZE); + crypto.sha256.doFinal(data, dataOff, dataLen, tmp, SCHNORR_E_32_OFF); + privKey.getS(tmp, SCHNORR_D_32_OFF); + + tmp[(short)(SCHNORR_TMP1_32_OUT_OFF - 1)] = (byte) crypto.add256(tmp, SCHNORR_E_32_OFF, tmp, SCHNORR_D_32_OFF, tmp, SCHNORR_TMP1_32_OUT_OFF); + multCipher.doFinal(tmp, SCHNORR_TMP1_OUT_OFF, (short) 64, tmp, SCHNORR_TMP1_OUT_OFF); + multCipher.doFinal(tmp, SCHNORR_D_OUT_OFF, (short) 64, tmp, SCHNORR_TMP2_OUT_OFF); + crypto.sub512(tmp, SCHNORR_TMP1_OUT_OFF, tmp, SCHNORR_TMP2_OUT_OFF, tmp, SCHNORR_TMP1_OUT_OFF); + multCipher.doFinal(tmp, SCHNORR_E_OUT_OFF, (short) 64, tmp, SCHNORR_TMP2_OUT_OFF); + crypto.sub512(tmp, SCHNORR_TMP1_OUT_OFF, tmp, SCHNORR_TMP2_OUT_OFF, tmp, SCHNORR_TMP1_OUT_OFF); + + short res, res2; + + for (short i = (short) 63; i >= 0; i--) { + res = (short) ((short) (tmp[(short)(SCHNORR_TMP1_OUT_OFF + i)] & 0xff) >> 1); + res2 = (short) ((short) (tmp[(short)(SCHNORR_TMP1_OUT_OFF + i - 1)] & 0xff) << 7); + tmp[(short)(SCHNORR_TMP1_OUT_OFF + i)] = (byte) (short) (res | res2); + } + + tmp[SCHNORR_TMP1_OUT_OFF] &= (byte) 0x7f; + + add256to512(tmp, SCHNORR_TMP1_OUT_OFF, tmp, SCHNORR_K_OUT_OFF, output, (short) (outOff + Crypto.KEY_PUB_SIZE)); + + return (short) (Crypto.KEY_PUB_SIZE + 64); + } + + short add256to512(byte[] a, short aOff, byte[] b, short bOff, byte[] out, short outOff) { + short outI = 0; + + for (short i = 63 ; i >= 32 ; i--) { + outI = (short) ((short)(a[(short)(aOff + i)] & 0xFF) + (short)(b[(short)(bOff + 32 + i)] & 0xFF) + outI); + out[(short)(outOff + i)] = (byte)outI; + outI = (short)(outI >> 8); + } + + for (short i = 31 ; i >= 0 ; i--) { + outI = (short) ((short)(a[(short)(aOff + i)] & 0xFF) + outI); + out[(short)(outOff + i)] = (byte)outI; + outI = (short)(outI >> 8); + } + return outI; + } } diff --git a/src/test/java/im/status/keycard/KeycardTest.java b/src/test/java/im/status/keycard/KeycardTest.java index fe589dc..88766c3 100644 --- a/src/test/java/im/status/keycard/KeycardTest.java +++ b/src/test/java/im/status/keycard/KeycardTest.java @@ -17,6 +17,8 @@ import org.bitcoinj.crypto.HDKeyDerivation; import org.bouncycastle.jce.ECNamedCurveTable; import org.bouncycastle.jce.spec.ECParameterSpec; import org.bouncycastle.jce.spec.ECPublicKeySpec; +import org.bouncycastle.math.ec.ECCurve; +import org.bouncycastle.math.ec.ECPoint; import org.bouncycastle.util.encoders.Hex; import org.junit.jupiter.api.*; import org.web3j.crypto.*; @@ -1006,6 +1008,16 @@ public class KeycardTest { response = cmdSet.sign(hash); verifySignResp(data, response); + // Schnorr + APDUCommand sign = secureChannel.protectedCommand(0x80, 0xC0, 0x00, 0x01, hash); + long time = System.currentTimeMillis(); + response = secureChannel.transmit(sdkChannel, sign); + System.out.print("Schnorr time: "); + System.out.println(System.currentTimeMillis() - time); + response.checkOK(); + + verifySchnorr(hash, response.getData()); + // Sign and derive String currentPath = new KeyPath(cmdSet.getStatus(KeycardCommandSet.GET_STATUS_P1_KEY_PATH).checkOK().getData()).toString(); String updatedPath = new KeyPath(currentPath + "/2").toString(); @@ -1048,6 +1060,67 @@ public class KeycardTest { assertEquals(0x6A88, response.getSw()); } + private void schnorrTest() throws Exception { + byte[] m = new byte[32]; + SecureRandom.getInstanceStrong().nextBytes(m); + MessageDigest dg = MessageDigest.getInstance("SHA256"); + ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("secp256k1"); + + byte[] sk = new byte[32]; + SecureRandom.getInstanceStrong().nextBytes(sk); + BigInteger d = new BigInteger(1, sk); + ECPoint P = ecSpec.getG().multiply(d); + + byte[] _k = new byte[32]; + SecureRandom.getInstanceStrong().nextBytes(_k); + BigInteger k = new BigInteger(1, _k); + ECPoint R = ecSpec.getG().multiply(k); + + dg.update(R.getEncoded(false)); + dg.update(P.getEncoded(false)); + dg.update(m); + BigInteger e = new BigInteger(1, dg.digest()); + + BigInteger s = e.multiply(d).add(k); + + ECPoint R2 = ecSpec.getG().multiply(s).subtract(P.multiply(e)); + + System.out.println("Rt = " + Hex.toHexString(R.getEncoded(false))); + System.out.println("R2t = " + Hex.toHexString(R2.getEncoded(false))); + + assertTrue(R.equals(R2)); + } + + private void verifySchnorr(byte[] m, byte[] sig) throws Exception { + schnorrTest(); + + byte[] p = extractPublicKeyFromSignature(sig); + + int off = sig[4] + 5; + byte[] rawSig = Arrays.copyOfRange(sig, off, sig.length); + + byte[] r = Arrays.copyOf(rawSig, 65); + + System.out.println("p = " + Hex.toHexString(p)); + System.out.println("r = " + Hex.toHexString(r)); + System.out.println("s = " + Hex.toHexString(Arrays.copyOfRange(rawSig, 65, rawSig.length))); + + MessageDigest dg = MessageDigest.getInstance("SHA256"); + dg.update(r); + dg.update(p); + dg.update(m); + BigInteger e = new BigInteger(1, dg.digest()); + + ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("secp256k1"); + ECPoint P = ecSpec.getCurve().decodePoint(p); + ECPoint G = ecSpec.getG(); + BigInteger s = new BigInteger(1, Arrays.copyOfRange(rawSig, 65, rawSig.length)); + s = s.mod(ecSpec.getCurve().getOrder()); + ECPoint R = G.multiply(s).subtract(P.multiply(e)); + System.out.println("R = " + Hex.toHexString(R.getEncoded(false))); + assertTrue(R.equals(ecSpec.getCurve().decodePoint(r))); + } + private void verifySignResp(byte[] data, APDUResponse response) throws Exception { Signature signature = Signature.getInstance("SHA256withECDSA", "BC"); assertEquals(0x9000, response.getSw());