Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions src/main/java/im/status/keycard/Consts.java
Original file line number Diff line number Diff line change
@@ -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
};
}
15 changes: 15 additions & 0 deletions src/main/java/im/status/keycard/Crypto.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) curveKey.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).
Expand Down
197 changes: 197 additions & 0 deletions src/main/java/im/status/keycard/Ed25519.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
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;
public 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();
// }

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

// 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) 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) {
// 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);
}
}
Loading