package org.bouncycastle.tls.crypto.impl.jcajce;
import java.io.IOException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import javax.crypto.KeyAgreement;
import javax.crypto.interfaces.DHPrivateKey;
import javax.crypto.interfaces.DHPublicKey;
import javax.crypto.spec.DHParameterSpec;
import javax.crypto.spec.DHPublicKeySpec;
import org.bouncycastle.tls.AlertDescription;
import org.bouncycastle.tls.TlsFatalAlert;
import org.bouncycastle.tls.crypto.TlsAgreement;
import org.bouncycastle.tls.crypto.TlsDHConfig;
import org.bouncycastle.tls.crypto.TlsDHDomain;
import org.bouncycastle.util.BigIntegers;
/**
* JCE support class for Diffie-Hellman key pair generation and key agreement over a specified Diffie-Hellman configuration.
*/
public class JceTlsDHDomain
implements TlsDHDomain
{
protected JcaTlsCrypto crypto;
protected TlsDHConfig dhConfig;
protected DHParameterSpec dhDomain;
public JceTlsDHDomain(JcaTlsCrypto crypto, TlsDHConfig dhConfig)
{
this.crypto = crypto;
this.dhConfig = dhConfig;
this.dhDomain = getParameters(dhConfig);
}
public byte[] calculateDHAgreement(DHPublicKey publicKey, DHPrivateKey privateKey)
throws GeneralSecurityException
{
KeyAgreement agreement = crypto.getHelper().createKeyAgreement("DH");
agreement.init(privateKey);
agreement.doPhase(publicKey, true);
/*
* RFC 5246 8.1.2. Leading bytes of Z that contain all zero bits are stripped before it is
* used as the pre_master_secret. We use the convention established by the JSSE to signal this
* by asking for "TlsPremasterSecret".
*/
return agreement.generateSecret("TlsPremasterSecret").getEncoded();
}
public TlsAgreement createDH()
{
return new JceTlsDH(this);
}
public static BigInteger decodeParameter(byte[] encoding) throws IOException
{
return new BigInteger(1, encoding);
}
public DHPublicKey decodePublicKey(byte[] encoding) throws IOException
{
try
{
BigInteger y = decodeParameter(encoding);
// TODO Check RFCs for any validation that could/should be done here
KeyFactory keyFactory = crypto.getHelper().createKeyFactory("DH");
return (DHPublicKey)keyFactory.generatePublic(new DHPublicKeySpec(y, dhDomain.getP(), dhDomain.getG()));
}
catch (Exception e)
{
throw new TlsFatalAlert(AlertDescription.illegal_parameter, e);
}
}
public byte[] encodeParameter(BigInteger x) throws IOException
{
return BigIntegers.asUnsignedByteArray(x);
}
public byte[] encodePublicKey(DHPublicKey publicKey) throws IOException
{
return encodeParameter(publicKey.getY());
}
public KeyPair generateKeyPair()
{
try
{
KeyPairGenerator keyPairGenerator = crypto.getHelper().createKeyPairGenerator("DH");
keyPairGenerator.initialize(getParameters(dhConfig), crypto.getSecureRandom());
return keyPairGenerator.generateKeyPair();
}
catch (GeneralSecurityException e)
{
throw new IllegalStateException("unable to create key pair: " + e.getMessage(), e);
}
}
public JcaTlsCrypto getCrypto()
{
return crypto;
}
public DHParameterSpec getParameters(TlsDHConfig dhConfig)
{
// TODO There's a draft RFC for negotiated (named) groups
BigInteger[] pg = dhConfig.getExplicitPG();
if (pg != null)
{
return new DHParameterSpec(pg[0], pg[1]);
}
throw new IllegalStateException("No DH configuration provided");
}
}