/*
* Zed Attack Proxy (ZAP) and its related class files.
*
* ZAP is an HTTP/HTTPS proxy for assessing web application security.
*
* Copyright 2011 mawoki@ymail.com
*
* 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.parosproxy.paros.security;
import java.io.IOException;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.SignatureException;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.util.Date;
import java.util.Random;
import java.util.concurrent.atomic.AtomicLong;
import org.bouncycastle.asn1.x500.X500NameBuilder;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
/**
* This is a singleton class. Use {@link #getService()} method to
* obtain a service bean. This implementation is totally unbuffered and creates
* every time you call {@link #createCertForHost(String)} a new certificate.
* If you want to have a cached solution, have a look at {@link CachedSslCertifificateServiceImpl}.
* This class is designed to be thread safe.
*
* A word about serial numbers ... There have to be different serial numbers
* generated, cause if multiple certificates with different finger prints
* do have the same serial from the same CA, the browser gets crazy.
* At least, Firefox v3.x does.
*
* @author MaWoKi
* @see CachedSslCertifificateServiceImpl for a cached SslCertificateService
*/
public final class SslCertificateServiceImpl implements SslCertificateService {
private X509Certificate caCert = null;
private PublicKey caPubKey = null;
private PrivateKey caPrivKey = null;
private final AtomicLong serial;
private static final SslCertificateService singleton = new SslCertificateServiceImpl();
private SslCertificateServiceImpl() {
Security.addProvider(new BouncyCastleProvider());
final Random rnd = new Random();
rnd.setSeed(System.currentTimeMillis());
// prevent browser certificate caches, cause of doubled serial numbers
// using 48bit random number
long sl = ((long)rnd.nextInt()) << 32 | (rnd.nextInt() & 0xFFFFFFFFL);
// let reserve of 16 bit for increasing, serials have to be positive
sl = sl & 0x0000FFFFFFFFFFFFL;
this.serial = new AtomicLong(sl);
}
@Override
public synchronized void initializeRootCA(KeyStore keystore) throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException {
if (keystore == null) {
this.caCert = null;
this.caPrivKey = null;
this.caPubKey = null;
} else {
this.caCert = (X509Certificate)keystore.getCertificate(ZAPROXY_JKS_ALIAS);
this.caPrivKey = (RSAPrivateKey) keystore.getKey(ZAPROXY_JKS_ALIAS, SslCertificateService.PASSPHRASE);
this.caPubKey = this.caCert.getPublicKey();
}
}
@Override
public KeyStore createCertForHost(String hostname) throws NoSuchAlgorithmException, InvalidKeyException, CertificateException, NoSuchProviderException, SignatureException, KeyStoreException, IOException, UnrecoverableKeyException {
if (hostname == null) {
throw new IllegalArgumentException("Error, 'hostname' is not allowed to be null!");
}
if (this.caCert == null || this.caPrivKey == null || this.caPubKey == null) {
throw new MissingRootCertificateException(this.getClass() + " wasn't initialized! Got to options 'Dynamic SSL Certs' and create one.");
}
final KeyPair mykp = this.createKeyPair();
final PrivateKey privKey = mykp.getPrivate();
final PublicKey pubKey = mykp.getPublic();
X500NameBuilder namebld = new X500NameBuilder(BCStyle.INSTANCE);
namebld.addRDN(BCStyle.CN, hostname);
namebld.addRDN(BCStyle.OU, "Zed Attack Proxy Project");
namebld.addRDN(BCStyle.O, "OWASP");
namebld.addRDN(BCStyle.C, "xx");
namebld.addRDN(BCStyle.EmailAddress, "owasp-zed-attack-proxy@lists.owasp.org");
X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder (
new X509CertificateHolder(caCert.getEncoded()).getSubject(),
BigInteger.valueOf(serial.getAndIncrement()),
new Date(System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 30),
new Date(System.currentTimeMillis() + 100*(1000L * 60 * 60 * 24 * 30)),
namebld.build(),
pubKey
);
certGen.addExtension(Extension.subjectKeyIdentifier, false, new SubjectKeyIdentifier(pubKey.getEncoded()));
certGen.addExtension(Extension.basicConstraints, false, new BasicConstraints(false));
certGen.addExtension(Extension.subjectAlternativeName, false, new GeneralNames(new GeneralName(GeneralName.dNSName, hostname)));
ContentSigner sigGen;
try {
sigGen = new JcaContentSignerBuilder("SHA256WithRSAEncryption").setProvider("BC").build(caPrivKey);
} catch (OperatorCreationException e) {
throw new CertificateException(e);
}
final X509Certificate cert = new JcaX509CertificateConverter().setProvider("BC").getCertificate(certGen.build(sigGen));
cert.checkValidity(new Date());
cert.verify(caPubKey);
final KeyStore ks = KeyStore.getInstance("JKS");
ks.load(null, null);
final Certificate[] chain = new Certificate[2];
chain[1] = this.caCert;
chain[0] = cert;
ks.setKeyEntry(ZAPROXY_JKS_ALIAS, privKey, PASSPHRASE, chain);
return ks;
}
/**
* Generates a 2048 bit RSA key pair using SHA1PRNG.
*
* @return the key pair
* @throws NoSuchAlgorithmException if no provider supports the used algorithms.
*/
private KeyPair createKeyPair() throws NoSuchAlgorithmException {
final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
final SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
random.setSeed(Long.toString(System.currentTimeMillis()).getBytes());
keyGen.initialize(2048, random);
final KeyPair keypair = keyGen.generateKeyPair();
return keypair;
}
/**
* @return return the current {@link SslCertificateService}
*/
public static SslCertificateService getService() {
return singleton;
}
}