package org.bouncycastle.crypto.tls;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Vector;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.util.PublicKeyFactory;
/**
* ECDH key exchange (see RFC 4492)
*/
public class TlsECDHKeyExchange
extends AbstractTlsKeyExchange
{
protected TlsSigner tlsSigner;
protected int[] namedCurves;
protected short[] clientECPointFormats, serverECPointFormats;
protected AsymmetricKeyParameter serverPublicKey;
protected ECPublicKeyParameters ecAgreeServerPublicKey;
protected TlsAgreementCredentials agreementCredentials;
protected ECPrivateKeyParameters ecAgreeClientPrivateKey;
protected ECPrivateKeyParameters ecAgreeServerPrivateKey;
protected ECPublicKeyParameters ecAgreeClientPublicKey;
public TlsECDHKeyExchange(int keyExchange, Vector supportedSignatureAlgorithms, int[] namedCurves,
short[] clientECPointFormats, short[] serverECPointFormats)
{
super(keyExchange, supportedSignatureAlgorithms);
switch (keyExchange)
{
case KeyExchangeAlgorithm.ECDHE_RSA:
this.tlsSigner = new TlsRSASigner();
break;
case KeyExchangeAlgorithm.ECDHE_ECDSA:
this.tlsSigner = new TlsECDSASigner();
break;
case KeyExchangeAlgorithm.ECDH_RSA:
case KeyExchangeAlgorithm.ECDH_ECDSA:
this.tlsSigner = null;
break;
default:
throw new IllegalArgumentException("unsupported key exchange algorithm");
}
this.keyExchange = keyExchange;
this.namedCurves = namedCurves;
this.clientECPointFormats = clientECPointFormats;
this.serverECPointFormats = serverECPointFormats;
}
public void init(TlsContext context)
{
super.init(context);
if (this.tlsSigner != null)
{
this.tlsSigner.init(context);
}
}
public void skipServerCredentials()
throws IOException
{
throw new TlsFatalAlert(AlertDescription.unexpected_message);
}
public void processServerCertificate(Certificate serverCertificate)
throws IOException
{
if (serverCertificate.isEmpty())
{
throw new TlsFatalAlert(AlertDescription.bad_certificate);
}
org.bouncycastle.asn1.x509.Certificate x509Cert = serverCertificate.getCertificateAt(0);
SubjectPublicKeyInfo keyInfo = x509Cert.getSubjectPublicKeyInfo();
try
{
this.serverPublicKey = PublicKeyFactory.createKey(keyInfo);
}
catch (RuntimeException e)
{
throw new TlsFatalAlert(AlertDescription.unsupported_certificate);
}
if (tlsSigner == null)
{
try
{
this.ecAgreeServerPublicKey = TlsECCUtils
.validateECPublicKey((ECPublicKeyParameters)this.serverPublicKey);
}
catch (ClassCastException e)
{
throw new TlsFatalAlert(AlertDescription.certificate_unknown);
}
TlsUtils.validateKeyUsage(x509Cert, KeyUsage.keyAgreement);
}
else
{
if (!tlsSigner.isValidPublicKey(this.serverPublicKey))
{
throw new TlsFatalAlert(AlertDescription.certificate_unknown);
}
TlsUtils.validateKeyUsage(x509Cert, KeyUsage.digitalSignature);
}
super.processServerCertificate(serverCertificate);
}
public boolean requiresServerKeyExchange()
{
switch (keyExchange)
{
case KeyExchangeAlgorithm.ECDHE_ECDSA:
case KeyExchangeAlgorithm.ECDHE_RSA:
case KeyExchangeAlgorithm.ECDH_anon:
return true;
default:
return false;
}
}
public void validateCertificateRequest(CertificateRequest certificateRequest)
throws IOException
{
/*
* RFC 4492 3. [...] The ECDSA_fixed_ECDH and RSA_fixed_ECDH mechanisms are usable with
* ECDH_ECDSA and ECDH_RSA. Their use with ECDHE_ECDSA and ECDHE_RSA is prohibited because
* the use of a long-term ECDH client key would jeopardize the forward secrecy property of
* these algorithms.
*/
short[] types = certificateRequest.getCertificateTypes();
for (int i = 0; i < types.length; ++i)
{
switch (types[i])
{
case ClientCertificateType.rsa_sign:
case ClientCertificateType.dss_sign:
case ClientCertificateType.ecdsa_sign:
case ClientCertificateType.rsa_fixed_ecdh:
case ClientCertificateType.ecdsa_fixed_ecdh:
break;
default:
throw new TlsFatalAlert(AlertDescription.illegal_parameter);
}
}
}
public void processClientCredentials(TlsCredentials clientCredentials)
throws IOException
{
if (clientCredentials instanceof TlsAgreementCredentials)
{
// TODO Validate client cert has matching parameters (see 'TlsECCUtils.areOnSameCurve')?
this.agreementCredentials = (TlsAgreementCredentials)clientCredentials;
}
else if (clientCredentials instanceof TlsSignerCredentials)
{
// OK
}
else
{
throw new TlsFatalAlert(AlertDescription.internal_error);
}
}
public void generateClientKeyExchange(OutputStream output)
throws IOException
{
if (agreementCredentials != null)
{
return;
}
AsymmetricCipherKeyPair ecAgreeClientKeyPair = TlsECCUtils.generateECKeyPair(context.getSecureRandom(),
ecAgreeServerPublicKey.getParameters());
this.ecAgreeClientPrivateKey = (ECPrivateKeyParameters)ecAgreeClientKeyPair.getPrivate();
byte[] point = TlsECCUtils.serializeECPublicKey(serverECPointFormats,
(ECPublicKeyParameters)ecAgreeClientKeyPair.getPublic());
TlsUtils.writeOpaque8(point, output);
}
public void processClientCertificate(Certificate clientCertificate)
throws IOException
{
// TODO Extract the public key
// TODO If the certificate is 'fixed', take the public key as ecAgreeClientPublicKey
}
public void processClientKeyExchange(InputStream input)
throws IOException
{
if (ecAgreeClientPublicKey != null)
{
// For ecdsa_fixed_ecdh and rsa_fixed_ecdh, the key arrived in the client certificate
return;
}
byte[] point = TlsUtils.readOpaque8(input);
ECDomainParameters curve_params = this.ecAgreeServerPrivateKey.getParameters();
this.ecAgreeClientPublicKey = TlsECCUtils.validateECPublicKey(TlsECCUtils.deserializeECPublicKey(
serverECPointFormats, curve_params, point));
}
public byte[] generatePremasterSecret()
throws IOException
{
if (agreementCredentials != null)
{
return agreementCredentials.generateAgreement(ecAgreeServerPublicKey);
}
if (ecAgreeServerPrivateKey != null)
{
return TlsECCUtils.calculateECDHBasicAgreement(ecAgreeClientPublicKey, ecAgreeServerPrivateKey);
}
if (ecAgreeClientPrivateKey != null)
{
return TlsECCUtils.calculateECDHBasicAgreement(ecAgreeServerPublicKey, ecAgreeClientPrivateKey);
}
throw new TlsFatalAlert(AlertDescription.internal_error);
}
}