/*
* 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.zaproxy.zap.extension.dynssl;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Calendar;
import java.util.Date;
import java.util.Random;
import javax.xml.bind.DatatypeConverter;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.FileUtils;
import org.bouncycastle.asn1.x500.X500NameBuilder;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
import org.bouncycastle.asn1.x509.KeyPurposeId;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
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.jcajce.JcaContentSignerBuilder;
import org.parosproxy.paros.security.SslCertificateService;
/**
* @author MaWoKi
*/
public class SslCertificateUtils {
/**
* The token that indicates the start of the section that contains the certificate, contained in a {@code .pem} file.
*
* @since 2.6.0
*/
public static final String BEGIN_CERTIFICATE_TOKEN = "-----BEGIN CERTIFICATE-----";
/**
* The token that indicates the end of the section that contains the certificate, contained in a {@code .pem} file.
*
* @since 2.6.0
*/
public static final String END_CERTIFICATE_TOKEN = "-----END CERTIFICATE-----";
/**
* The token that indicates the start of the section that contains the private key, contained in a {@code .pem} file.
*
* @since 2.6.0
*/
public static final String BEGIN_PRIVATE_KEY_TOKEN = "-----BEGIN PRIVATE KEY-----";
/**
* The token that indicates the end of the section that contains the private key, contained in a {@code .pem} file.
*
* @since 2.6.0
*/
public static final String END_PRIVATE_KEY_TOKEN = "-----END PRIVATE KEY-----";
private static final long DEFAULT_VALID_DAYS = 365L;
/**
* Creates a new Root CA certificate and returns private and public key as
* {@link KeyStore}. The {@link KeyStore#getDefaultType()} is used.
*
* @return
* @throws NoSuchAlgorithmException If no providers are found
* for 'RSA' key pair generator
* or 'SHA1PRNG' Secure random number generator
* @throws IllegalStateException in case of errors during assembling {@link KeyStore}
*/
public static final KeyStore createRootCA() throws NoSuchAlgorithmException {
final Date startDate = Calendar.getInstance().getTime();
final Date expireDate = new Date(startDate.getTime()+ (DEFAULT_VALID_DAYS * 24L * 60L * 60L * 1000L));
final KeyPairGenerator g = KeyPairGenerator.getInstance("RSA");
g.initialize(2048, SecureRandom.getInstance("SHA1PRNG"));
final KeyPair keypair = g.genKeyPair();
final PrivateKey privKey = keypair.getPrivate();
final PublicKey pubKey = keypair.getPublic();
Security.addProvider(new BouncyCastleProvider());
Random rnd = new Random();
// using the hash code of the user's name and home path, keeps anonymity
// but also gives user a chance to distinguish between each other
X500NameBuilder namebld = new X500NameBuilder(BCStyle.INSTANCE);
namebld.addRDN(BCStyle.CN, "OWASP Zed Attack Proxy Root CA");
namebld.addRDN(BCStyle.L, Integer.toHexString(System.getProperty("user.name").hashCode())
+ Integer.toHexString(System.getProperty("user.home").hashCode()));
namebld.addRDN(BCStyle.O, "OWASP Root CA");
namebld.addRDN(BCStyle.OU, "OWASP ZAP Root CA");
namebld.addRDN(BCStyle.C, "xx");
X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder (
namebld.build(),
BigInteger.valueOf(rnd.nextInt()),
startDate,
expireDate,
namebld.build(),
pubKey
);
KeyStore ks = null;
try {
certGen.addExtension(Extension.subjectKeyIdentifier, false, new SubjectKeyIdentifier(pubKey.getEncoded()));
certGen.addExtension(Extension.basicConstraints, true, new BasicConstraints(true));
certGen.addExtension(Extension.keyUsage, false, new KeyUsage(KeyUsage.keyCertSign | KeyUsage.digitalSignature | KeyUsage.keyEncipherment | KeyUsage.dataEncipherment | KeyUsage.cRLSign));
KeyPurposeId[] eku = {
KeyPurposeId.id_kp_serverAuth,
KeyPurposeId.id_kp_clientAuth,
KeyPurposeId.anyExtendedKeyUsage
};
certGen.addExtension(Extension.extendedKeyUsage, false, new ExtendedKeyUsage(eku));
final ContentSigner sigGen = new JcaContentSignerBuilder("SHA256WithRSAEncryption").setProvider("BC").build(privKey);
final X509Certificate cert = new JcaX509CertificateConverter().setProvider("BC").getCertificate(certGen.build(sigGen));
ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null, null);
ks.setKeyEntry(SslCertificateService.ZAPROXY_JKS_ALIAS, privKey, SslCertificateService.PASSPHRASE, new Certificate[]{cert});
} catch (final Exception e) {
throw new IllegalStateException("Errors during assembling root CA.", e);
}
return ks;
}
/**
* @param keystore
* @return
* @throws KeyStoreException
* @throws IOException
* @throws CertificateException
* @throws NoSuchAlgorithmException
*/
public static final String keyStore2String(KeyStore keystore) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
keystore.store(baos, SslCertificateService.PASSPHRASE);
final byte[] bytes = baos.toByteArray();
baos.close();
return Base64.encodeBase64URLSafeString(bytes);
}
/**
* @param str
* @return
* @throws KeyStoreException
* @throws IOException
* @throws CertificateException
* @throws NoSuchAlgorithmException
*/
public static final KeyStore string2Keystore(String str) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
final byte[] bytes = Base64.decodeBase64(str);
final ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
final KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(bais, SslCertificateService.PASSPHRASE);
bais.close();
return ks;
}
/**
* Code c/o http://stackoverflow.com/questions/12501117/programmatically-obtain-keystore-from-pem
* @param pemFile
* @return
* @throws IOException
* @throws CertificateException
* @throws InvalidKeySpecException
* @throws NoSuchAlgorithmException
* @throws KeyStoreException
*/
public static KeyStore pem2Keystore(File pemFile) throws IOException, CertificateException,
InvalidKeySpecException, NoSuchAlgorithmException, KeyStoreException {
String certAndKey = FileUtils.readFileToString(pemFile, StandardCharsets.US_ASCII);
byte[] certBytes = extractCertificate(certAndKey);
byte[] keyBytes = extractPrivateKey(certAndKey);
return pem2KeyStore(certBytes, keyBytes);
}
/**
* Extracts the certificate from the given {@code .pem} file's contents.
*
* @param pem the contents of the {@code .pem} file.
* @return the certificate, or empty array if the certificate was not found.
* @since 2.6.0
* @throws IllegalArgumentException if the certificate data is not properly {@code base64} encoded.
*/
public static byte[] extractCertificate(String pem) {
return parseDERFromPEM(pem, BEGIN_CERTIFICATE_TOKEN, END_CERTIFICATE_TOKEN);
}
/**
* Extracts the private key from the given {@code .pem} file's contents.
*
* @param pem the contents of the {@code .pem} file.
* @return the private key, or empty array if the private key was not found.
* @since 2.6.0
* @throws IllegalArgumentException if the private key data is not properly {@code base64} encoded.
*/
public static byte[] extractPrivateKey(String pem) {
return parseDERFromPEM(pem, BEGIN_PRIVATE_KEY_TOKEN, END_PRIVATE_KEY_TOKEN);
}
/**
* Tells whether or not the given ({@code .pem} file) contents contain a section with the given begin and end tokens.
*
* @param contents the ({@code .pem} file) contents to check if contains the section.
* @param beginToken the begin token of the section.
* @param endToken the end token of the section.
* @return {@code true} if the section was found, {@code false} otherwise.
*/
private static boolean containsSection(String contents, String beginToken, String endToken) {
int idxToken;
if ((idxToken = contents.indexOf(beginToken)) == -1 || contents.indexOf(endToken) < idxToken) {
return false;
}
return true;
}
public static KeyStore pem2KeyStore(byte[] certBytes, byte[] keyBytes)
throws IOException, CertificateException, InvalidKeySpecException, NoSuchAlgorithmException, KeyStoreException {
X509Certificate cert = generateCertificateFromDER(certBytes);
RSAPrivateKey key = generatePrivateKeyFromDER(keyBytes);
KeyStore keystore = KeyStore.getInstance("JKS");
keystore.load(null);
keystore.setCertificateEntry("cert-alias", cert);
keystore.setKeyEntry(SslCertificateService.ZAPROXY_JKS_ALIAS, key, SslCertificateService.PASSPHRASE, new Certificate[] {cert});
return keystore;
}
private static byte[] parseDERFromPEM(String pem, String beginDelimiter, String endDelimiter) {
if (!containsSection(pem, beginDelimiter, endDelimiter)) {
return new byte[0];
}
String[] tokens = pem.split(beginDelimiter);
tokens = tokens[1].split(endDelimiter);
return DatatypeConverter.parseBase64Binary(tokens[0]);
}
private static RSAPrivateKey generatePrivateKeyFromDER(byte[] keyBytes) throws InvalidKeySpecException, NoSuchAlgorithmException {
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory factory = KeyFactory.getInstance("RSA");
return (RSAPrivateKey)factory.generatePrivate(spec);
}
private static X509Certificate generateCertificateFromDER(byte[] certBytes) throws CertificateException {
CertificateFactory factory = CertificateFactory.getInstance("X.509");
return (X509Certificate)factory.generateCertificate(new ByteArrayInputStream(certBytes));
}
}