package com.subgraph.orchid.crypto;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PublicKey;
import java.util.Arrays;
import javax.crypto.KeyAgreement;
import javax.crypto.interfaces.DHPublicKey;
import javax.crypto.spec.DHParameterSpec;
import javax.crypto.spec.DHPublicKeySpec;
import com.subgraph.orchid.TorException;
/**
* The <code>TorKeyAgreement</code> class implements the diffie-hellman key agreement
* protocol using the parameters specified in the main Tor specification (tor-spec.txt).
*
* An instance of this class can only be used to perform a single key agreement operation.
*
* After instantiating the class, a user calls {@link #getPublicValue()} or {@link #getPublicKeyBytes()}
* to retrieve the public value to transmit to the peer in the key agreement operation. After receiving
* a public value from the peer, this value should be converted into a <code>BigInteger</code> and
* {@link #isValidPublicValue(BigInteger)} should be called to verify that the peer has sent a safe
* and legal public value. If {@link #isValidPublicValue(BigInteger)} returns true, the peer public
* value is valid and {@link #getSharedSecret(BigInteger)} can be called to complete the key agreement
* protocol and return the shared secret value.
*
*/
public class TorTapKeyAgreement implements TorKeyAgreement {
public final static int DH_LEN = 128;
public final static int DH_SEC_LEN = 40;
/*
* tor-spec 0.3
*
* For Diffie-Hellman, we use a generator (g) of 2. For the modulus (p), we
* use the 1024-bit safe prime from rfc2409 section 6.2 whose hex
* representation is:
*/
private static final BigInteger P1024 = new BigInteger(
"00FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08"
+ "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B"
+ "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9"
+ "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6"
+ "49286651ECE65381FFFFFFFFFFFFFFFF", 16);
private static final BigInteger G = new BigInteger("2");
/*
* tor-spec 0.3
*
* As an optimization, implementations SHOULD choose DH private keys (x) of
* 320 bits.
*/
private static final int PRIVATE_KEY_SIZE = 320;
private static final DHParameterSpec DH_PARAMETER_SPEC = new DHParameterSpec(P1024, G, PRIVATE_KEY_SIZE);
private final KeyAgreement dh;
private final KeyPair keyPair;
private final TorPublicKey onionKey;
/**
* Create a new <code>TorKeyAgreement</code> instance which can be used to perform a single
* key agreement operation. A new set of ephemeral Diffie-Hellman parameters are generated
* when this class is instantiated.
*/
public TorTapKeyAgreement(TorPublicKey onionKey) {
this.keyPair = generateKeyPair();
this.dh = createDH();
this.onionKey = onionKey;
}
public TorTapKeyAgreement() {
this(null);
}
/**
* Return the generated public value for this key agreement operation as a <code>BigInteger</code>.
*
* @return The diffie-hellman public value as a <code>BigInteger</code>.
*/
public BigInteger getPublicValue() {
DHPublicKey pubKey = (DHPublicKey) keyPair.getPublic();
return pubKey.getY();
}
/**
* Return the generated public value for this key agreement operation as an array with the value
* encoded in big-endian byte order.
*
* @return A byte array containing the encoded public value for this key agreement operation.
*/
public byte[] getPublicKeyBytes() {
final byte[] output = new byte[128];
final byte[] yBytes = getPublicValue().toByteArray();
if(yBytes[0] == 0 && yBytes.length == (DH_LEN + 1)) {
System.arraycopy(yBytes, 1, output, 0, DH_LEN);
} else if (yBytes.length <= DH_LEN) {
final int offset = DH_LEN - yBytes.length;
System.arraycopy(yBytes, 0, output, offset, yBytes.length);
} else {
throw new IllegalStateException("Public value is longer than DH_LEN but not because of sign bit");
}
return output;
}
/**
* Return <code>true</code> if the specified value is a legal public
* value rather than a dangerous degenerate or confined subgroup value.
*
* tor-spec 5.2
* Before computing g^xy, both client and server MUST verify that
* the received g^x or g^y value is not degenerate; that is, it must
* be strictly greater than 1 and strictly less than p-1 where p is
* the DH modulus. Implementations MUST NOT complete a handshake
* with degenerate keys.
*/
public static boolean isValidPublicValue(BigInteger publicValue) {
if(publicValue.signum() < 1 || publicValue.equals(BigInteger.ONE))
return false;
if(publicValue.compareTo(P1024.subtract(BigInteger.ONE)) >= 0)
return false;
return true;
}
/**
* Complete the key agreement protocol with the peer public value
* <code>otherPublic</code> and return the calculated shared secret.
*
* @param otherPublic The peer public value.
* @return The shared secret value produced by the protocol.
*/
public byte[] getSharedSecret(BigInteger otherPublic) {
try {
KeyFactory factory = KeyFactory.getInstance("DH");
DHPublicKeySpec pub = new DHPublicKeySpec(otherPublic, P1024, G);
PublicKey key = factory.generatePublic(pub);
dh.doPhase(key, true);
return dh.generateSecret();
} catch (GeneralSecurityException e) {
throw new TorException(e);
}
}
private final KeyAgreement createDH() {
try {
KeyAgreement dh = KeyAgreement.getInstance("DH");
dh.init(keyPair.getPrivate());
return dh;
} catch (GeneralSecurityException e) {
throw new TorException(e);
}
}
private final KeyPair generateKeyPair() {
try {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DH");
keyGen.initialize(DH_PARAMETER_SPEC);
return keyGen.generateKeyPair();
} catch (GeneralSecurityException e) {
throw new TorException(e);
}
}
public byte[] createOnionSkin() {
final byte[] yBytes = getPublicKeyBytes();
final HybridEncryption hybrid = new HybridEncryption();
return hybrid.encrypt(yBytes, onionKey);
}
public boolean deriveKeysFromHandshakeResponse(byte[] handshakeResponse,
byte[] keyMaterialOut, byte[] verifyHashOut) {
ByteBuffer bb = ByteBuffer.wrap(handshakeResponse);
byte[] dhPublic = new byte[DH_LEN];
byte[] keyHash = new byte[TorMessageDigest.TOR_DIGEST_SIZE];
bb.get(dhPublic);
bb.get(keyHash);
BigInteger peerPublic = new BigInteger(1, dhPublic);
return deriveKeysFromDHPublicAndHash(peerPublic, keyHash, keyMaterialOut, verifyHashOut);
}
public boolean deriveKeysFromDHPublicAndHash(BigInteger peerPublic, byte[] keyHash, byte[] keyMaterialOut, byte[] verifyHashOut) {
if(!isValidPublicValue(peerPublic)) {
throw new TorException("Illegal DH public value");
}
final byte[] sharedSecret = getSharedSecret(peerPublic);
final TorKeyDerivation kdf = new TorKeyDerivation(sharedSecret);
kdf.deriveKeys(keyMaterialOut, verifyHashOut);
return Arrays.equals(verifyHashOut, keyHash);
}
}