/* ******************************************************************************* * Java Card Bitcoin Hardware Wallet * (c) 2015 Ledger * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. ******************************************************************************* */ package com.ledger.wallet; import javacard.framework.JCSystem; import javacard.framework.Util; import javacard.security.CryptoException; import javacard.security.DESKey; import javacard.security.AESKey; import javacard.security.ECKey; import javacard.security.ECPrivateKey; import javacard.security.ECPublicKey; import javacard.security.HMACKey; import javacard.security.KeyAgreement; import javacard.security.KeyBuilder; import javacard.security.KeyPair; import javacard.security.MessageDigest; import javacard.security.RandomData; import javacard.security.Signature; import javacardx.crypto.Cipher; /** * Hardware Wallet crypto tools * @author BTChip * */ public class Crypto { // Java Card constants might be off for some platforms - recheck with your implementation //private static final short HMAC_SHA512_SIZE = KeyBuilder.LENGTH_HMAC_SHA_512_BLOCK_128; private static final short HMAC_SHA512_SIZE = (short)(32 * 8); public static void init() { scratch = JCSystem.makeTransientByteArray((short)1, JCSystem.CLEAR_ON_DESELECT); random = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM); try { // ok, let's save RAM transientPrivate = (ECPrivateKey)KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE_TRANSIENT_DESELECT, KeyBuilder.LENGTH_EC_FP_256, false); transientPrivateTransient = true; } catch(CryptoException e) { try { // ok, let's save a bit less RAM transientPrivate = (ECPrivateKey)KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE_TRANSIENT_RESET, KeyBuilder.LENGTH_EC_FP_256, false); transientPrivateTransient = true; } catch(CryptoException e1) { // ok, let's test the flash wear leveling \o/ transientPrivate = (ECPrivateKey)KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, KeyBuilder.LENGTH_EC_FP_256, false); Secp256k1.setCommonCurveParameters(transientPrivate); } } digestFull = MessageDigest.getInstance(MessageDigest.ALG_SHA_256, false); digestAuthorization = MessageDigest.getInstance(MessageDigest.ALG_SHA_256, false); digestScratch = MessageDigest.getInstance(MessageDigest.ALG_SHA_256, false); blobEncryptDecrypt = Cipher.getInstance(Cipher.ALG_DES_CBC_NOPAD, false); signature = Signature.getInstance(Signature.ALG_ECDSA_SHA_256, false); try { digestRipemd = MessageDigest.getInstance(MessageDigest.ALG_RIPEMD160, false); } catch(CryptoException e) { // A typical Java Card implementation will not support RIPEMD160 - we deal with it } try { digestSha512 = MessageDigest.getInstance(MessageDigest.ALG_SHA_512, false); } catch(CryptoException e) { sha512 = new SHA512(); } try { signatureHmac = Signature.getInstance(Signature.ALG_HMAC_SHA_512, false); try { // ok, let's save RAM keyHmac = (HMACKey)KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC_TRANSIENT_DESELECT, HMAC_SHA512_SIZE, false); keyHmac2 = (HMACKey)KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC_TRANSIENT_DESELECT, HMAC_SHA512_SIZE, false); } catch(CryptoException e) { try { // ok, let's save a bit less RAM keyHmac = (HMACKey)KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC_TRANSIENT_RESET, HMAC_SHA512_SIZE, false); keyHmac2 = (HMACKey)KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC_TRANSIENT_RESET, HMAC_SHA512_SIZE, false); } catch(CryptoException e1) { // ok, let's test the flash wear leveling \o/ keyHmac = (HMACKey)KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC, HMAC_SHA512_SIZE, false); keyHmac2 = (HMACKey)KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC, HMAC_SHA512_SIZE, false); } } } catch(CryptoException e) { signatureHmac = null; } // Optional initializations if no proprietary API is available try { keyAgreement = KeyAgreement.getInstance(KeyAgreement.ALG_EC_SVDP_DH_PLAIN, false); } catch(CryptoException e) { // Not having the KeyAgreement API is manageable if there is a proprietary API to recover public keys // and if the airgapped personalization can be skipped // Otherwise there should be a remote secure oracle performing public derivations and sending back results } try { publicKey = (ECPublicKey)KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PUBLIC, KeyBuilder.LENGTH_EC_FP_256, false); Secp256k1.setCommonCurveParameters(publicKey); } catch(CryptoException e) { } try { keyPair = new KeyPair( (ECPublicKey)KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PUBLIC, KeyBuilder.LENGTH_EC_FP_256, false), (ECPrivateKey)KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, KeyBuilder.LENGTH_EC_FP_256, false)); Secp256k1.setCommonCurveParameters((ECKey)keyPair.getPrivate()); Secp256k1.setCommonCurveParameters((ECKey)keyPair.getPublic()); } catch(CryptoException e) { } try { blobEncryptDecryptAES = Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_CBC_NOPAD, false); } catch(CryptoException e) { } } public static void initTransientPrivate(byte[] keyBuffer, short keyOffset) { if (transientPrivateTransient) { Secp256k1.setCommonCurveParameters(transientPrivate); } transientPrivate.setS(keyBuffer, keyOffset, (short)32); } public static void signTransientPrivate(byte[] keyBuffer, short keyOffset, byte[] dataBuffer, short dataOffset, byte[] targetBuffer, short targetOffset) { initTransientPrivate(keyBuffer, keyOffset); Util.arrayFillNonAtomic(keyBuffer, keyOffset, (short)32, (byte)0x00); // recheck with the target platform, initializing once instead might be possible and save a few flash write // (this part is unspecified in the Java Card API) signature.init(transientPrivate, Signature.MODE_SIGN); signature.sign(dataBuffer, dataOffset, (short)32, targetBuffer, targetOffset); if (transientPrivateTransient) { transientPrivate.clearKey(); } } // following method is only used if no proprietary API is available public static boolean verifyPublic(byte[] keyBuffer, short keyOffset, byte[] dataBuffer, short dataOffset, byte[] signatureBuffer, short signatureOffset) { publicKey.setW(keyBuffer, keyOffset, (short)65); signature.init(publicKey, Signature.MODE_VERIFY); try { return signature.verify(dataBuffer, dataOffset, (short)32, signatureBuffer, signatureOffset, (short)(signatureBuffer[(short)(signatureOffset + 1)] + 2)); } catch(Exception e) { return false; } } public static void initCipher(DESKey key, boolean encrypt) { blobEncryptDecrypt.init(key, (encrypt ? Cipher.MODE_ENCRYPT : Cipher.MODE_DECRYPT), IV_ZERO, (short)0, (short)IV_ZERO.length); } public static void initCipherAES(AESKey key, boolean encrypt) { blobEncryptDecryptAES.init(key, (encrypt ? Cipher.MODE_ENCRYPT : Cipher.MODE_DECRYPT), IV_ZERO_AES, (short)0, (short)IV_ZERO_AES.length); } public static byte getRandomByteModulo(short modulo) { short rng_max = (short)(256 % modulo); short rng_limit = (short)(256 - rng_max); short candidate = (short)0; do { random.generateData(scratch, (short)0, (short)1); candidate = (short)(scratch[0] & 0xff); } while(candidate > rng_limit); return (byte)(candidate % modulo); } private static final byte[] IV_ZERO = { 0, 0, 0, 0, 0, 0, 0, 0 }; private static final byte[] IV_ZERO_AES = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; private static byte[] scratch; protected static ECPrivateKey transientPrivate; protected static boolean transientPrivateTransient; protected static Signature signature; protected static Signature signatureHmac; protected static HMACKey keyHmac; protected static HMACKey keyHmac2; // duplicated because platforms don't like changing the key size on the fly protected static MessageDigest digestFull; protected static MessageDigest digestAuthorization; protected static MessageDigest digestScratch; protected static MessageDigest digestRipemd; protected static MessageDigest digestSha512; protected static SHA512 sha512; protected static RandomData random; protected static Cipher blobEncryptDecrypt; protected static Cipher blobEncryptDecryptAES; protected static KeyAgreement keyAgreement; protected static KeyPair keyPair; // following variables are only used if no proprietary API is available protected static ECPublicKey publicKey; }