package com.subgraph.orchid.crypto; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import com.subgraph.orchid.data.HexDigest; import com.subgraph.orchid.misc.Utils; public class TorNTorKeyAgreement implements TorKeyAgreement { public final static int CURVE25519_PUBKEY_LEN = 32; final static int CURVE25519_OUTPUT_LEN = 32; final static int DIGEST256_LEN = 32; final static int DIGEST_LEN = 20; final static int KEY_LEN = 16; final static int NTOR_ONIONSKIN_LEN = 2 * CURVE25519_PUBKEY_LEN + DIGEST_LEN; final static String PROTOID = "ntor-curve25519-sha256-1"; final static String SERVER_STR = "Server"; final static int SECRET_INPUT_LEN = CURVE25519_PUBKEY_LEN * 3 + CURVE25519_OUTPUT_LEN * 2 + DIGEST_LEN + PROTOID.length(); final static int AUTH_INPUT_LEN = DIGEST256_LEN + DIGEST_LEN + (CURVE25519_PUBKEY_LEN * 3) + PROTOID.length() + SERVER_STR.length(); final static Charset cs = Charset.forName("ISO-8859-1"); private final TorRandom random = new TorRandom(); private final HexDigest peerIdentity; private final byte[] peerNTorOnionKey; /* pubkey_B */ private final byte[] secretKey_x; private final byte[] publicKey_X; public TorNTorKeyAgreement(HexDigest peerIdentity, byte[] peerNTorOnionKey) { this.peerIdentity = peerIdentity; this.peerNTorOnionKey = peerNTorOnionKey; this.secretKey_x = generateSecretKey(); this.publicKey_X = getPublicKeyForPrivate(secretKey_x); } public byte[] createOnionSkin() { final ByteBuffer buffer = makeBuffer(NTOR_ONIONSKIN_LEN); buffer.put(peerIdentity.getRawBytes()); buffer.put(peerNTorOnionKey); buffer.put(publicKey_X); return buffer.array(); } private ByteBuffer makeBuffer(int sz) { final byte[] array = new byte[sz]; return ByteBuffer.wrap(array); } byte[] generateSecretKey() { final byte[]key = random.getBytes(32); key[0] &= 248; key[31] &= 127; key[31] |= 64; return key; } byte[] getPublicKeyForPrivate(byte[] secretKey) { final byte[] pub = new byte[32]; Curve25519.crypto_scalarmult_base(pub, secretKey); return pub; } private boolean isBad; public boolean deriveKeysFromHandshakeResponse(byte[] handshakeResponse, byte[] keyMaterialOut, byte[] verifyHashOut) { isBad = false; final ByteBuffer hr = ByteBuffer.wrap(handshakeResponse); byte[] serverPub = new byte[CURVE25519_PUBKEY_LEN]; byte[] authCandidate = new byte[DIGEST256_LEN]; hr.get(serverPub); hr.get(authCandidate); final byte[] secretInput = buildSecretInput(serverPub); final byte[] verify = tweak("verify", secretInput); final byte[] authInput = buildAuthInput(verify, serverPub); final byte[] auth = tweak("mac", authInput); isBad |= !Utils.constantTimeArrayEquals(auth, authCandidate); final byte[] seed = tweak("key_extract", secretInput); final TorRFC5869KeyDerivation kdf = new TorRFC5869KeyDerivation(seed); kdf.deriveKeys(keyMaterialOut, verifyHashOut); return !isBad; } public byte[] getNtorCreateMagic() { return "ntorNTORntorNTOR".getBytes(cs); } private byte[] buildSecretInput(byte[] serverPublic_Y) { final ByteBuffer bb = makeBuffer(SECRET_INPUT_LEN); bb.put(scalarMult(serverPublic_Y)); bb.put(scalarMult(peerNTorOnionKey)); bb.put(peerIdentity.getRawBytes()); bb.put(peerNTorOnionKey); bb.put(publicKey_X); bb.put(serverPublic_Y); bb.put(PROTOID.getBytes()); return bb.array(); } private byte[] buildAuthInput(byte[] verify, byte[] serverPublic_Y) { final ByteBuffer bb = makeBuffer(AUTH_INPUT_LEN); bb.put(verify); bb.put(peerIdentity.getRawBytes()); bb.put(peerNTorOnionKey); bb.put(serverPublic_Y); bb.put(publicKey_X); bb.put(PROTOID.getBytes(cs)); bb.put(SERVER_STR.getBytes(cs)); return bb.array(); } private byte[] scalarMult(byte[] peerValue) { final byte[] out = new byte[CURVE25519_OUTPUT_LEN]; Curve25519.crypto_scalarmult(out, secretKey_x, peerValue); isBad |= isAllZero(out); return out; } boolean isAllZero(byte[] bs) { boolean result = true; for(byte b: bs) { result &= (b == 0); } return result; } byte[] tweak(String suffix, byte[] input) { return hmac256(input, getStringConstant(suffix)); } byte[] hmac256(byte[] input, byte[] key) { final SecretKeySpec keyspec = new SecretKeySpec(key, "HmacSHA256"); try { final Mac mac = Mac.getInstance("HmacSHA256"); mac.init(keyspec); return mac.doFinal(input); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException("Failed to create HmacSHA256 instance: "+ e); } catch (InvalidKeyException e) { throw new IllegalStateException("Failed to create HmacSHA256 instance: "+ e); } } byte[] getStringConstant(String suffix) { if(suffix == null || suffix.isEmpty()) { return PROTOID.getBytes(cs); } else { return (PROTOID + ":" + suffix).getBytes(cs); } } }