/*
* Copyright 1999-2008 University of Chicago
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.nimbustools.auto_common.ezpz_ca;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
import org.bouncycastle.asn1.x509.CRLNumber;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.globus.gsi.CertUtil;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.X509V3CertificateGenerator;
import org.bouncycastle.jce.X509V2CRLGenerator;
import org.bouncycastle.jce.X509Principal;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.asn1.x509.X509Extensions;
import org.bouncycastle.asn1.x509.X509Name;
import javax.security.auth.x500.X500Principal;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.SecureRandom;
import java.security.Security;
import java.security.NoSuchProviderException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SignatureException;
import java.security.InvalidKeyException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateException;
import java.util.Date;
import java.util.Calendar;
import java.io.IOException;
import java.io.InputStream;
import java.io.ByteArrayInputStream;
public class EzPzCA {
public static final String replaceToken = "CN=XXXXX";
private final KeyPairGenerator kpGen;
private final X509V3CertificateGenerator certGen;
private final X509Certificate caX509;
private final PrivateKey caPrivate;
private final X509Name caX509Name;
private final String targetString;
private final CertificateFactory factory;
private final X509V2CRLGenerator crlGen;
static {
Security.addProvider(new BouncyCastleProvider());
}
/**
* EzPzCA constructor
*
* @param caCert CA's public cert, X509Certificate
* @param caPrivateKey (unencrypted) private key object
* @param globusCADN only used for logging
* @throws NoSuchProviderException problem initializing keypair generator
* @throws NoSuchAlgorithmException problem initializing keypair generator
* @throws CertificateException problem initializing certificate factory
* @throws IOException file/stream problem
*/
protected EzPzCA(X509Certificate caCert,
PrivateKey caPrivateKey,
String globusCADN)
throws NoSuchProviderException,
NoSuchAlgorithmException,
CertificateException,
IOException {
if (caCert == null) {
throw new IllegalArgumentException("caCert is null");
}
if (caPrivateKey == null) {
throw new IllegalArgumentException("caPrivateKey is null");
}
this.kpGen = KeyPairGenerator.getInstance("RSA", "BC");
this.kpGen.initialize(1024, new SecureRandom());
this.certGen = new X509V3CertificateGenerator();
this.factory = CertificateFactory.getInstance("X.509","BC");
this.caX509 = caCert;
this.caPrivate = caPrivateKey;
this.caX509Name = new X509Principal(
this.caX509.getIssuerX500Principal().getEncoded());
this.initializeGenerator();
this.targetString = deriveSigningTargetString(caCert);
final X500Principal subjectDN = caCert.getSubjectX500Principal();
final String targetBase = subjectDN.getName(X500Principal.RFC2253);
final String msg = "Initialized certificate authority with subject " +
"DN (RFC2253) = '" + targetBase + "' " +
"and Globus style DN = '" + globusCADN + "'. " +
"New DNs will look like this (RFC2253): '" +
this.targetString + "'";
this.crlGen = new X509V2CRLGenerator();
this.crlGen.setIssuerDN(this.caX509Name);
this.crlGen.setSignatureAlgorithm("SHA1withRSA");
}
public static String deriveSigningTargetString(X509Certificate caCert) throws CertificateException {
final X500Principal subjectDN = caCert.getSubjectX500Principal();
final String targetBase = subjectDN.getName(X500Principal.RFC2253);
final String[] parts = targetBase.split(",");
String target = "";
int cnCount = 0;
for (int i = 0; i < parts.length; i++) {
final String newpiece;
if (parts[i].startsWith("CN") || parts[i].startsWith("cn")) {
newpiece = replaceToken;
cnCount += 1;
} else {
newpiece = parts[i];
}
if (i == 0) {
target = newpiece;
} else {
target = newpiece + "," + target;
}
}
if (cnCount == 0) {
throw new CertificateException("Unsupported: CA has no " +
"CN (?)");
}
if (cnCount != 1) {
throw new CertificateException("Unsupported: CA has more " +
"than one CN");
}
return target;
}
protected KeyPair createNewKeyPair() {
return kpGen.generateKeyPair();
}
public X509Certificate signNewCertificate(String cnString,
PublicKey pubkey,
Calendar expires)
throws SignatureException,
InvalidKeyException,
CertificateException,
IOException {
this.setGenerator(this.getTargetDN(cnString),
pubkey,
expires.getTime());
final X509Certificate x509 =
this.certGen.generateX509Certificate(this.caPrivate);
final InputStream in = new ByteArrayInputStream(x509.getEncoded());
final X509Certificate x509Cert =
(X509Certificate) this.factory.generateCertificate(in);
final X500Principal subjectDN = x509Cert.getSubjectX500Principal();
final String DN = subjectDN.getName(X500Principal.RFC2253);
final String globusDN = CertUtil.toGlobusID(DN, false);
final String msg = "Created new certificate with DN (RFC2253) = '" +
DN + "' and Globus style DN = '" + globusDN + "'";
return x509Cert;
}
public X509CRL generateCRL()
throws SignatureException, InvalidKeyException, NoSuchProviderException,
CertificateEncodingException {
this.crlGen.setThisUpdate(new Date());
final Calendar expires = Calendar.getInstance();
// this is fake, expiration does not matter
expires.add(Calendar.MONTH, GenerateNewCert.VALIDITY_MONTHS);
this.crlGen.setNextUpdate(expires.getTime());
// this is how you'd actually add an entry if we wanted one:
//this.crlGen.addCRLEntry(BigInteger.ONE, new Date(), CRLReason.PRIVILEGE_WITHDRAWN);
this.crlGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false,
new AuthorityKeyIdentifier(
new SubjectPublicKeyInfo(
new AlgorithmIdentifier("RSA"), this.caX509.getEncoded())));
this.crlGen.addExtension(X509Extensions.CRLNumber,
false, new CRLNumber(BigInteger.ONE));
return this.crlGen.generateX509CRL(this.caPrivate, "BC");
}
private String getTargetDN(String cnString) {
return getTargetDNfromSchema(this.targetString, "CN=" + cnString);
}
// can only use this if you used deriveSigningTargetString
public static String getTargetDNfromSchema(String targetStr, String finalString) {
return targetStr.replaceAll(replaceToken, finalString);
}
private void initializeGenerator() {
this.certGen.reset();
this.certGen.setSerialNumber(this.caX509.getSerialNumber());
this.certGen.setSignatureAlgorithm(this.caX509.getSigAlgName());
this.certGen.setIssuerDN(this.caX509Name);
this.certGen.addExtension(X509Extensions.BasicConstraints,
true,
new BasicConstraints(false));
this.certGen.addExtension(X509Extensions.KeyUsage,
true,
new KeyUsage(KeyUsage.digitalSignature |
KeyUsage.keyEncipherment));
}
private void setGenerator(String targetDN,
PublicKey pubkey,
Date expires) {
this.certGen.setNotBefore(
new Date(System.currentTimeMillis() - 10000));
this.certGen.setNotAfter(expires);
this.certGen.setSubjectDN(new X509Principal(targetDN));
this.certGen.setPublicKey(pubkey);
}
}