/*
*
* Copyright (c) 2013 - 2017 Lijun Liao
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License version 3
* as published by the Free Software Foundation with the addition of the
* following permission added to Section 15 as permitted in Section 7(a):
*
* FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
* THE AUTHOR LIJUN LIAO. LIJUN LIAO DISCLAIMS THE WARRANTY OF NON INFRINGEMENT
* OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License.
*
* You can be released from the requirements of the license by purchasing
* a commercial license. Buying such a license is mandatory as soon as you
* develop commercial activities involving the XiPKI software without
* disclosing the source code of your own applications.
*
* For more information, please contact Lijun Liao at this
* address: lijun.liao@gmail.com
*/
package org.xipki.pki.scep.serveremulator;
import java.io.IOException;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.CertificateEncodingException;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.pkcs.CertificationRequest;
import org.bouncycastle.asn1.pkcs.CertificationRequestInfo;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.CRLReason;
import org.bouncycastle.asn1.x509.Certificate;
import org.bouncycastle.asn1.x509.CertificateList;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.X509CRLHolder;
import org.bouncycastle.cert.X509v2CRLBuilder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.jce.X509KeyUsage;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.ContentVerifierProvider;
import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.bc.BcContentVerifierProviderBuilder;
import org.bouncycastle.operator.bc.BcDSAContentVerifierProviderBuilder;
import org.bouncycastle.operator.bc.BcECContentVerifierProviderBuilder;
import org.bouncycastle.operator.bc.BcRSAContentVerifierProviderBuilder;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import org.bouncycastle.pkcs.PKCSException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xipki.commons.common.util.LogUtil;
import org.xipki.commons.common.util.ParamUtil;
import org.xipki.commons.security.util.KeyUtil;
import org.xipki.pki.scep.crypto.ScepHashAlgoType;
import org.xipki.pki.scep.util.ScepUtil;
/**
* @author Lijun Liao
* @since 2.0.0
*/
public class CaEmulator {
public static final long MIN_IN_MS = 60L * 1000;
public static final long DAY_IN_MS = 24L * 60 * MIN_IN_MS;
private static final Logger LOG = LoggerFactory.getLogger(CaEmulator.class);
private static final DefaultDigestAlgorithmIdentifierFinder DFLT_DIGESTALG_IDENTIFIER_FINDER =
new DefaultDigestAlgorithmIdentifierFinder();
private static final Map<String, BcContentVerifierProviderBuilder> VERIFIER_PROVIDER_BUILDER
= new HashMap<>();
private final PrivateKey caKey;
private final Certificate caCert;
private final X500Name caSubject;
private final byte[] caCertBytes;
private final boolean generateCrl;
private final Map<BigInteger, Certificate> serialCertMap
= new HashMap<BigInteger, Certificate>();
private final Map<X500Name, Certificate> reqSubjectCertMap
= new HashMap<X500Name, Certificate>();
private final AtomicLong serialNumber = new AtomicLong(2);
private final AtomicLong crlNumber = new AtomicLong(2);
private CertificateList crl;
public CaEmulator(final PrivateKey caKey, final Certificate caCert, final boolean generateCrl)
throws CertificateEncodingException {
this.caKey = ParamUtil.requireNonNull("caKey", caKey);
this.caCert = ParamUtil.requireNonNull("caCert", caCert);
this.caSubject = caCert.getSubject();
this.generateCrl = generateCrl;
try {
this.caCertBytes = caCert.getEncoded();
} catch (IOException ex) {
throw new CertificateEncodingException(ex.getMessage(), ex);
}
}
public PrivateKey getCaKey() {
return caKey;
}
public Certificate getCaCert() {
return caCert;
}
public byte[] getCaCertBytes() {
return Arrays.copyOf(caCertBytes, caCertBytes.length);
}
public boolean isGenerateCrl() {
return generateCrl;
}
public Certificate generateCert(final CertificationRequest csr) throws Exception {
if (!verifyPopo(csr)) {
throw new Exception("CSR invalid");
}
CertificationRequestInfo reqInfo = csr.getCertificationRequestInfo();
return generateCert(reqInfo.getSubjectPublicKeyInfo(), reqInfo.getSubject());
}
public Certificate generateCert(final SubjectPublicKeyInfo pubKeyInfo, final X500Name subjectDn)
throws Exception {
return generateCert(pubKeyInfo, subjectDn,
new Date(System.currentTimeMillis() - 10 * CaEmulator.MIN_IN_MS));
}
public Certificate generateCert(final SubjectPublicKeyInfo pubKeyInfo, final X500Name subjectDn,
final Date notBefore) throws Exception {
ParamUtil.requireNonNull("pubKeyInfo", pubKeyInfo);
ParamUtil.requireNonNull("subjectDn", subjectDn);
ParamUtil.requireNonNull("notBefore", notBefore);
Date notAfter = new Date(notBefore.getTime() + 730 * DAY_IN_MS);
BigInteger tmpSerialNumber = BigInteger.valueOf(serialNumber.getAndAdd(1));
X509v3CertificateBuilder certGenerator = new X509v3CertificateBuilder(caSubject,
tmpSerialNumber, notBefore, notAfter, subjectDn, pubKeyInfo);
X509KeyUsage ku = new X509KeyUsage(X509KeyUsage.digitalSignature
| X509KeyUsage.dataEncipherment | X509KeyUsage.keyAgreement
| X509KeyUsage.keyEncipherment);
certGenerator.addExtension(Extension.keyUsage, true, ku);
BasicConstraints bc = new BasicConstraints(false);
certGenerator.addExtension(Extension.basicConstraints, true, bc);
String signatureAlgorithm = ScepUtil.getSignatureAlgorithm(caKey, ScepHashAlgoType.SHA256);
ContentSigner contentSigner = new JcaContentSignerBuilder(signatureAlgorithm).build(caKey);
Certificate asn1Cert = certGenerator.build(contentSigner).toASN1Structure();
serialCertMap.put(tmpSerialNumber, asn1Cert);
reqSubjectCertMap.put(subjectDn, asn1Cert);
return asn1Cert;
}
public Certificate getCert(final X500Name issuer, final BigInteger serialNumber) {
if (!caSubject.equals(issuer)) {
return null;
}
return serialCertMap.get(serialNumber);
}
public Certificate pollCert(final X500Name issuer, final X500Name subject) {
ParamUtil.requireNonNull("issuer", issuer);
ParamUtil.requireNonNull("subject", subject);
if (!caSubject.equals(issuer)) {
return null;
}
return reqSubjectCertMap.get(subject);
}
public synchronized CertificateList getCrl(final X500Name issuer, final BigInteger serialNumber)
throws Exception {
if (crl != null) {
return crl;
}
Date thisUpdate = new Date();
X509v2CRLBuilder crlBuilder = new X509v2CRLBuilder(caSubject, thisUpdate);
Date nextUpdate = new Date(thisUpdate.getTime() + 30 * DAY_IN_MS);
crlBuilder.setNextUpdate(nextUpdate);
Date caStartTime = caCert.getTBSCertificate().getStartDate().getDate();
Date revocationTime = new Date(caStartTime.getTime() + 1);
if (revocationTime.after(thisUpdate)) {
revocationTime = caStartTime;
}
crlBuilder.addCRLEntry(BigInteger.valueOf(2), revocationTime, CRLReason.keyCompromise);
crlBuilder.addExtension(Extension.cRLNumber, false,
new ASN1Integer(crlNumber.getAndAdd(1)));
String signatureAlgorithm = ScepUtil.getSignatureAlgorithm(caKey, ScepHashAlgoType.SHA256);
ContentSigner contentSigner = new JcaContentSignerBuilder(signatureAlgorithm).build(caKey);
X509CRLHolder crl = crlBuilder.build(contentSigner);
return crl.toASN1Structure();
}
private boolean verifyPopo(final CertificationRequest csr) {
ParamUtil.requireNonNull("csr", csr);
try {
PKCS10CertificationRequest p10Req = new PKCS10CertificationRequest(csr);
SubjectPublicKeyInfo pkInfo = p10Req.getSubjectPublicKeyInfo();
PublicKey pk = KeyUtil.generatePublicKey(pkInfo);
ContentVerifierProvider cvp = getContentVerifierProvider(pk);
return p10Req.isSignatureValid(cvp);
} catch (InvalidKeyException | PKCSException | NoSuchAlgorithmException
| InvalidKeySpecException ex) {
LogUtil.error(LOG, ex, "could not validate POPO of CSR");
return false;
}
}
public ContentVerifierProvider getContentVerifierProvider(final PublicKey publicKey)
throws InvalidKeyException {
ParamUtil.requireNonNull("publicKey", publicKey);
String keyAlg = publicKey.getAlgorithm().toUpperCase();
if ("EC".equals(keyAlg)) {
keyAlg = "ECDSA";
}
BcContentVerifierProviderBuilder builder = VERIFIER_PROVIDER_BUILDER.get(keyAlg);
if (builder == null) {
if ("RSA".equals(keyAlg)) {
builder = new BcRSAContentVerifierProviderBuilder(DFLT_DIGESTALG_IDENTIFIER_FINDER);
} else if ("DSA".equals(keyAlg)) {
builder = new BcDSAContentVerifierProviderBuilder(DFLT_DIGESTALG_IDENTIFIER_FINDER);
} else if ("ECDSA".equals(keyAlg)) {
builder = new BcECContentVerifierProviderBuilder(DFLT_DIGESTALG_IDENTIFIER_FINDER);
} else {
throw new InvalidKeyException("unknown key algorithm of the public key " + keyAlg);
}
VERIFIER_PROVIDER_BUILDER.put(keyAlg, builder);
}
AsymmetricKeyParameter keyParam = KeyUtil.generatePublicKeyParameter(publicKey);
try {
return builder.build(keyParam);
} catch (OperatorCreationException ex) {
throw new InvalidKeyException("could not build ContentVerifierProvider: "
+ ex.getMessage(), ex);
}
}
}