/*
* Copyright (c) 2011-2012 ICM Uniwersytet Warszawski All rights reserved.
* See LICENCE.txt file for licensing information.
*
* this work is derived from the implementation copyrighted and licensed as follows:
*
* Copyright (c) 2000 - 2011 The Legion Of The Bouncy Castle (http://www.bouncycastle.org)
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
* FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
package eu.emi.security.authn.x509.helpers.proxy;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.Date;
import org.bouncycastle.asn1.ASN1Encoding;
import org.bouncycastle.asn1.ASN1Object;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.ExtensionsGenerator;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x509.TBSCertificate;
import org.bouncycastle.asn1.x509.Time;
import org.bouncycastle.asn1.x509.V3TBSCertificateGenerator;
/**
* Class to produce an X.509 Version 3 certificate. Based on the BC bcmail
* library and deprecated class of the BC. We don't use BC mail
* as adding an another big dependency only for the certificate
* creation doesn't make much sense.
*/
public class X509v3CertificateBuilder
{
private V3TBSCertificateGenerator tbsGen;
private ExtensionsGenerator extGenerator;
/**
* Create a builder for a version 3 certificate.
*
* @param issuer the certificate issuer
* @param serial the certificate serial number
* @param notBefore the date before which the certificate is not valid
* @param notAfter the date after which the certificate is not valid
* @param subject the certificate subject
* @param publicKeyInfo the info structure for the public key to be associated
* with this certificate.
*/
public X509v3CertificateBuilder(X500Name issuer, BigInteger serial,
Date notBefore, Date notAfter, X500Name subject,
SubjectPublicKeyInfo publicKeyInfo)
{
tbsGen = new V3TBSCertificateGenerator();
tbsGen.setSubject(subject);
tbsGen.setSerialNumber(new ASN1Integer(serial));
tbsGen.setIssuer(issuer);
tbsGen.setStartDate(new Time(notBefore));
tbsGen.setEndDate(new Time(notAfter));
tbsGen.setSubject(subject);
tbsGen.setSubjectPublicKeyInfo(publicKeyInfo);
extGenerator = new ExtensionsGenerator();
}
/**
* Add a given extension field for the standard extensions tag (tag 3)
*
* @param oid the OID defining the extension type.
* @param isCritical true if the extension is critical, false otherwise.
* @param value the ASN.1 structure that forms the extension's value.
* @return this builder object.
* @throws IOException IO exception
*/
public X509v3CertificateBuilder addExtension(ASN1ObjectIdentifier oid,
boolean isCritical, ASN1Object value) throws IOException
{
extGenerator.addExtension(oid, isCritical, value);
return this;
}
/**
* Generate the certificate, signing it with the provided private key and
* using the specified algorithm.
* @param key to be used for signing
* @param sigAlg oid and paramters of the signature alg
* @param sigAlgName name of the signature alg
* @param provider can be null -> default will be used
* @param random can be null -> default will be used
* @return generated certificate
* @throws InvalidKeyException invalid key exception
* @throws CertificateParsingException certificate parsing exception
* @throws NoSuchProviderException no such provider exception
* @throws NoSuchAlgorithmException no such algorithm exception
* @throws SignatureException signature exception
* @throws IOException IO exception
*/
public X509Certificate build(PrivateKey key, AlgorithmIdentifier sigAlg,
String sigAlgName, String provider, SecureRandom random)
throws InvalidKeyException, CertificateParsingException,
NoSuchProviderException, NoSuchAlgorithmException,
SignatureException, IOException
{
if (sigAlg == null || sigAlgName == null)
throw new IllegalStateException(
"no signature algorithm specified");
if (key == null)
throw new IllegalStateException(
"no private key specified");
tbsGen.setSignature(sigAlg);
if (!extGenerator.isEmpty())
tbsGen.setExtensions(extGenerator.generate());
TBSCertificate toSign = tbsGen.generateTBSCertificate();
return sign(toSign, sigAlg, sigAlgName, key, provider, random);
}
private X509Certificate sign(TBSCertificate toSign, AlgorithmIdentifier sigAlg,
String sigAlgName,
PrivateKey key, String provider, SecureRandom random)
throws InvalidKeyException, NoSuchProviderException, NoSuchAlgorithmException,
SignatureException, IOException, CertificateParsingException
{
byte[] signature = calculateSignature(sigAlgName,
provider, key, random, toSign);
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(toSign);
v.add(sigAlg.toASN1Primitive());
v.add(new DERBitString(signature));
DERSequence derCertificate = new DERSequence(v);
CertificateFactory factory;
try
{
factory = CertificateFactory.getInstance("X.509");
ByteArrayInputStream bais = new ByteArrayInputStream(derCertificate.getEncoded(ASN1Encoding.DER));
return (X509Certificate) factory.generateCertificate(bais);
} catch (CertificateException e)
{
throw new RuntimeException("The generated proxy " +
"certificate was not parsed by the JDK", e);
}
}
private byte[] calculateSignature(String sigName, String provider, PrivateKey key,
SecureRandom random, ASN1Object object)
throws IOException, NoSuchProviderException,
NoSuchAlgorithmException, InvalidKeyException,
SignatureException
{
Signature sig;
if (provider != null)
sig = Signature.getInstance(sigName, provider);
else
sig = Signature.getInstance(sigName);
if (random != null)
sig.initSign(key, random);
else
sig.initSign(key);
sig.update(object.getEncoded(ASN1Encoding.DER));
return sig.sign();
}
/**
* Extracts the full algorithm identifier from the given certificate.
* @param cert input certificate
* @return extracted algorithm id
* @throws IOException if parameters of the algorithm can not be parsed
*/
public static AlgorithmIdentifier extractAlgorithmId(X509Certificate cert)
throws IOException
{
String oid = cert.getSigAlgOID();
byte params[] = cert.getSigAlgParams();
if (params != null)
{
ASN1Primitive derParams = ASN1Primitive.fromByteArray(params);
return new AlgorithmIdentifier(new ASN1ObjectIdentifier(oid),
derParams);
} else
{
return new AlgorithmIdentifier(new ASN1ObjectIdentifier(oid));
}
}
}