first attempt

This commit is contained in:
Michele Balistreri 2020-06-22 14:21:28 +03:00
parent 36de0fcc9c
commit f1522fac81
No known key found for this signature in database
GPG Key ID: E9567DA33A4F791A
5 changed files with 237 additions and 19 deletions

View File

@ -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();

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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());