package org.bouncycastle.tls.crypto.impl.jcajce;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.PublicKey;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.security.interfaces.DSAPublicKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPublicKey;
import javax.crypto.interfaces.DHPublicKey;
import org.bouncycastle.asn1.x509.Extensions;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.asn1.x509.TBSCertificate;
import org.bouncycastle.jcajce.util.JcaJceHelper;
import org.bouncycastle.tls.AlertDescription;
import org.bouncycastle.tls.ClientCertificateType;
import org.bouncycastle.tls.ConnectionEnd;
import org.bouncycastle.tls.KeyExchangeAlgorithm;
import org.bouncycastle.tls.SignatureAlgorithm;
import org.bouncycastle.tls.TlsFatalAlert;
import org.bouncycastle.tls.crypto.TlsCertificate;
import org.bouncycastle.tls.crypto.TlsCryptoException;
import org.bouncycastle.tls.crypto.TlsVerifier;
/**
* Implementation class for a single X.509 certificate based on the JCA.
*/
public class JcaTlsCertificate
implements TlsCertificate
{
private final JcaJceHelper helper;
public static JcaTlsCertificate convert(TlsCertificate certificate, JcaJceHelper helper) throws IOException
{
if (certificate instanceof JcaTlsCertificate)
{
return (JcaTlsCertificate)certificate;
}
return new JcaTlsCertificate(certificate.getEncoded(), helper);
}
protected final X509Certificate certificate;
protected DHPublicKey pubKeyDH = null;
protected ECPublicKey pubKeyEC = null;
protected RSAPublicKey pubKeyRSA = null;
public JcaTlsCertificate(byte[] encoding, JcaJceHelper helper)
throws IOException
{
try
{
ByteArrayInputStream input = new ByteArrayInputStream(encoding);
this.certificate = (X509Certificate)helper.createCertificateFactory("X.509").generateCertificate(input);
if (input.available() != 0)
{
throw new IOException("Extra data detected in stream");
}
}
catch (GeneralSecurityException e)
{
throw new TlsCryptoException("unable to decode certificate: " + e.getMessage(), e);
}
this.helper = helper;
}
public TlsVerifier createVerifier(short signatureAlgorithm) throws IOException
{
validateKeyUsage(KeyUsage.digitalSignature);
switch (signatureAlgorithm)
{
case SignatureAlgorithm.dsa:
return new JcaTlsDSAVerifier(getPubKeyDSS(), helper);
case SignatureAlgorithm.ecdsa:
return new JcaTlsECDSAVerifier(getPubKeyEC(), helper);
case SignatureAlgorithm.rsa:
return new JcaTlsRSAVerifier(getPubKeyRSA(), helper);
default:
throw new TlsFatalAlert(AlertDescription.certificate_unknown);
}
}
public short getClientCertificateType() throws IOException
{
PublicKey publicKey = getPublicKey();
try
{
/*
* TODO RFC 5246 7.4.6. The certificates MUST be signed using an acceptable hash/
* signature algorithm pair, as described in Section 7.4.4. Note that this relaxes the
* constraints on certificate-signing algorithms found in prior versions of TLS.
*/
/*
* RFC 5246 7.4.6. Client Certificate
*/
/*
* RSA public key; the certificate MUST allow the key to be used for signing with the
* signature scheme and hash algorithm that will be employed in the certificate verify
* message.
*/
if (publicKey instanceof RSAPublicKey)
{
validateKeyUsage(KeyUsage.digitalSignature);
return ClientCertificateType.rsa_sign;
}
/*
* DSA public key; the certificate MUST allow the key to be used for signing with the
* hash algorithm that will be employed in the certificate verify message.
*/
if (publicKey instanceof DSAPublicKey)
{
validateKeyUsage(KeyUsage.digitalSignature);
return ClientCertificateType.dss_sign;
}
/*
* ECDSA-capable public key; the certificate MUST allow the key to be used for signing
* with the hash algorithm that will be employed in the certificate verify message; the
* public key MUST use a curve and point format supported by the server.
*/
if (publicKey instanceof ECPublicKey)
{
validateKeyUsage(KeyUsage.digitalSignature);
// TODO Check the curve and point format
return ClientCertificateType.ecdsa_sign;
}
// TODO Add support for ClientCertificateType.*_fixed_*
}
catch (IOException e)
{
throw e;
}
catch (Exception e)
{
throw new TlsFatalAlert(AlertDescription.unsupported_certificate, e);
}
throw new TlsFatalAlert(AlertDescription.unsupported_certificate);
}
public byte[] getEncoded() throws IOException
{
try
{
// DER encoding enforced by provider - as defined by JCA for X.509 certificates.
return certificate.getEncoded();
}
catch (CertificateEncodingException e)
{
throw new TlsCryptoException("unable to encode certificate: " + e.getMessage(), e);
}
}
DHPublicKey getPubKeyDH() throws IOException
{
DHPublicKey pubKeyDH;
try
{
pubKeyDH = (DHPublicKey)getPublicKey();
}
catch (ClassCastException e)
{
throw new TlsFatalAlert(AlertDescription.certificate_unknown, e);
}
return validatePubKeyDH(pubKeyDH);
}
DSAPublicKey getPubKeyDSS() throws IOException
{
DSAPublicKey pubKeyDSS;
try
{
pubKeyDSS = (DSAPublicKey)getPublicKey();
}
catch (ClassCastException e)
{
throw new TlsFatalAlert(AlertDescription.certificate_unknown, e);
}
return validatePubKeyDSS(pubKeyDSS);
}
ECPublicKey getPubKeyEC() throws IOException
{
ECPublicKey pubKeyEC;
try
{
pubKeyEC = (ECPublicKey)getPublicKey();
}
catch (ClassCastException e)
{
throw new TlsFatalAlert(AlertDescription.certificate_unknown, e);
}
return validatePubKeyEC(pubKeyEC);
}
RSAPublicKey getPubKeyRSA() throws IOException
{
RSAPublicKey pubKeyRSA;
try
{
pubKeyRSA = (RSAPublicKey)getPublicKey();
}
catch (ClassCastException e)
{
throw new TlsFatalAlert(AlertDescription.certificate_unknown, e);
}
return validatePubKeyRSA(pubKeyRSA);
}
public TlsCertificate useInRole(int connectionEnd, int keyExchangeAlgorithm) throws IOException
{
switch (keyExchangeAlgorithm)
{
case KeyExchangeAlgorithm.DH_DSS:
case KeyExchangeAlgorithm.DH_RSA:
{
validateKeyUsage(KeyUsage.keyAgreement);
this.pubKeyDH = getPubKeyDH();
return this;
}
case KeyExchangeAlgorithm.ECDH_ECDSA:
case KeyExchangeAlgorithm.ECDH_RSA:
{
validateKeyUsage(KeyUsage.keyAgreement);
this.pubKeyEC = getPubKeyEC();
return this;
}
}
if (connectionEnd == ConnectionEnd.server)
{
switch (keyExchangeAlgorithm)
{
case KeyExchangeAlgorithm.RSA:
case KeyExchangeAlgorithm.RSA_PSK:
{
validateKeyUsage(KeyUsage.keyEncipherment);
this.pubKeyRSA = getPubKeyRSA();
return this;
}
}
}
throw new TlsFatalAlert(AlertDescription.certificate_unknown);
}
protected PublicKey getPublicKey() throws IOException
{
try
{
return certificate.getPublicKey();
}
catch (RuntimeException e)
{
throw new TlsFatalAlert(AlertDescription.unsupported_certificate, e);
}
}
public X509Certificate getX509Certificate()
{
return certificate;
}
protected void validateKeyUsage(int keyUsageBits)
throws IOException
{
Extensions exts;
try
{
exts = TBSCertificate.getInstance(certificate.getTBSCertificate()).getExtensions();
}
catch (CertificateEncodingException e)
{
throw new TlsCryptoException("unable to parse certificate extensions: " + e.getMessage(), e);
}
if (exts != null)
{
KeyUsage ku = KeyUsage.fromExtensions(exts);
if (ku != null)
{
int bits = ku.getBytes()[0] & 0xff;
if ((bits & keyUsageBits) != keyUsageBits)
{
throw new TlsFatalAlert(AlertDescription.certificate_unknown);
}
}
}
}
protected DHPublicKey validatePubKeyDH(DHPublicKey pubKeyDH) throws IOException
{
return pubKeyDH; // TODO: TlsDHUtils.validateDHPublicKey(pubKeyDH);
}
protected DSAPublicKey validatePubKeyDSS(DSAPublicKey pubKeyDSS) throws IOException
{
// TODO[tls-ops]
return pubKeyDSS;
}
protected ECPublicKey validatePubKeyEC(ECPublicKey pubKeyEC) throws IOException
{
// TODO[tls-ops]
return pubKeyEC;
}
protected RSAPublicKey validatePubKeyRSA(RSAPublicKey pubKeyRSA) throws IOException
{
// TODO[tls-ops]
return pubKeyRSA;
}
}