/*
* Copyright FuseLMS
*/
package io.milton.grizzly;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.io.StringWriter;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
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.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import org.apache.commons.codec.digest.DigestUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMReader;
import org.bouncycastle.openssl.PEMWriter;
import org.bouncycastle.openssl.PasswordFinder;
import org.bouncycastle.util.encoders.Base64;
/**
*
* @author dylan
*/
public class SSLTools {
public static Certificate parseCertificate(String certificateText) throws CertificateException {
return parseCertificate(certificateText.getBytes());
}
/**
*
* @param certificateBytes
* @return Certificate
* @throws CertificateException
*/
public static Certificate parseCertificate(byte[] certificateBytes) throws CertificateException {
if (certificateBytes.length == 0) {
throw new RuntimeException("Empty certificate");
}
CertificateFactory cf = CertificateFactory.getInstance("X.509");
ByteArrayInputStream bais = new ByteArrayInputStream(certificateBytes);
try {
Certificate cert = cf.generateCertificate(bais);
return cert;
} catch (CertificateException certificateException) {
throw new CertificateException("Could not read certificate", certificateException);
}
}
/**
*
* @param certificateText
* @return
* @throws java.security.cert.CertificateException
*/
public static X509Certificate parseX509Certificate(String certificateText) throws CertificateException {
return (X509Certificate) parseCertificate(certificateText);
}
/**
*
* @param certificateBytes
* @return
* @throws java.security.cert.CertificateException
*/
public static X509Certificate parseX509Certificate(byte[] certificateBytes) throws CertificateException {
return (X509Certificate) parseCertificate(certificateBytes);
}
/**
*
* @param privateKeyText
* @return
* @throws java.security.GeneralSecurityException
* @throws java.io.IOException
*/
public static PrivateKey parsePrivateKey(final String privateKeyText) throws GeneralSecurityException, IOException {
return parsePrivateKey(privateKeyText, null);
}
/**
*
* @param privateKeyText
* @param password
* @return
* @throws GeneralSecurityException
* @throws IOException
*/
public static PrivateKey parsePrivateKey(final String privateKeyText, final char[] password) throws GeneralSecurityException, IOException {
return parsePrivateKey(privateKeyText.getBytes(), password);
}
/**
*
* @param privateKeyBytes
* @return
* @throws GeneralSecurityException
* @throws IOException
*/
public static PrivateKey parsePrivateKey(final byte[] privateKeyBytes) throws GeneralSecurityException, IOException {
return parsePrivateKey(privateKeyBytes, null);
}
/**
*
* @param privateKeyBytes
* @param password
* @return
* @throws java.security.GeneralSecurityException
* @throws java.io.IOException
*/
public static PrivateKey parsePrivateKey(final byte[] privateKeyBytes, final char[] password) throws GeneralSecurityException, IOException {
ByteArrayInputStream bais = new ByteArrayInputStream(privateKeyBytes);
InputStreamReader reader = new InputStreamReader(bais);
PEMReader parser = null;
try {
if (password != null) {
parser = new PEMReader(reader, getPasswordFinder(password));
} else {
parser = new PEMReader(reader);
}
KeyPair caKeyPair = (KeyPair) parser.readObject();
if (caKeyPair == null) {
throw new GeneralSecurityException("Reading CA private key failed");
}
return caKeyPair.getPrivate();
} finally {
if (parser != null) {
parser.close();
}
bais.close();
reader.close();
}
}
/**
*
* @param privateKeyText
* @return
* @throws GeneralSecurityException
* @throws IOException
*/
public static KeyPair parseKeyPair(final String privateKeyText) throws GeneralSecurityException, IOException {
return parseKeyPair(privateKeyText.getBytes(), null);
}
/**
*
* @param privateKeyText
* @param password
* @return
* @throws GeneralSecurityException
* @throws IOException
*/
public static KeyPair parseKeyPair(final String privateKeyText, final String password) throws GeneralSecurityException, IOException {
return parseKeyPair(privateKeyText.getBytes(), password);
}
/**
*
* @param privateKeyBytes
* @param password
* @return
* @throws GeneralSecurityException
* @throws IOException
*/
public static KeyPair parseKeyPair(final byte[] privateKeyBytes, final String password) throws GeneralSecurityException, IOException {
ByteArrayInputStream bais = new ByteArrayInputStream(privateKeyBytes);
InputStreamReader reader = new InputStreamReader(bais);
PEMReader parser = null;
try {
if (password != null) {
parser = new PEMReader(reader, getPasswordFinder(password));
} else {
parser = new PEMReader(reader);
}
KeyPair caKeyPair = (KeyPair) parser.readObject();
if (caKeyPair == null) {
throw new GeneralSecurityException("Reading CA private key failed");
}
return caKeyPair;
} finally {
if (parser != null) {
parser.close();
}
bais.close();
reader.close();
}
}
/**
*
* @param privateKeyBytes
* @param password
* @return
* @throws IOException
* @throws GeneralSecurityException
*/
public static PublicKey getPublicKeyFromPrivateKey(final byte[] privateKeyBytes, final String password) throws IOException, GeneralSecurityException {
KeyPair keypair = parseKeyPair(privateKeyBytes, password);
return keypair.getPublic();
}
/**
*
* @param privateKeyText
* @param password
* @return
* @throws GeneralSecurityException
* @throws IOException
*/
public static RSAPrivateKey parseRSAPrivateKey(final String privateKeyText, final char[] password) throws GeneralSecurityException, IOException {
return (RSAPrivateKey) parsePrivateKey(privateKeyText, password);
}
/**
*
* @param privateKeyText
* @return
* @throws GeneralSecurityException
* @throws IOException
*/
public static RSAPrivateKey parseRSAPrivateKey(final String privateKeyText) throws GeneralSecurityException, IOException {
return (RSAPrivateKey) parsePrivateKey(privateKeyText, null);
}
public static PublicKey parsePublicKey(final String publicKeyPEM) throws IOException {
return (PublicKey) pemReader(publicKeyPEM);
}
/**
* Gets the modulus byte array from the X.509 certificate and calculates the
* SHA-1 digest and return a hex string representation of the SHA-1.
*
* @param cert
* @return String
* @throws java.security.NoSuchAlgorithmException
*/
public static String getCertificateModulusSHA1(final X509Certificate cert) throws NoSuchAlgorithmException {
PublicKey publicKey = cert.getPublicKey();
if (!(publicKey instanceof RSAPublicKey)) {
throw new IllegalArgumentException("Certificate file does not contain an RSA public key but a " + publicKey.getClass().getName());
}
RSAPublicKey rsaPublicKey = (RSAPublicKey) publicKey;
byte[] certModulusData = rsaPublicKey.getModulus().toByteArray();
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
byte[] certID = sha1.digest(certModulusData);
String certIDinHex = toHexString(certID);
return certIDinHex;
}
/**
* Gets the modulus byte array from the RSA private key and calculates the
* SHA-1 digest and return a hex string representation of the SHA-1.
*
* @param rsaPrivateKey
* @return String
*/
public static String getPrivateKeyModulusSHA1(final RSAPrivateKey rsaPrivateKey) {
byte[] keyModulusData = rsaPrivateKey.getModulus().toByteArray();
byte[] keyID = DigestUtils.sha1(keyModulusData);
String keyIDinHex = toHexString(keyID);
return keyIDinHex;
}
/**
* Checks whether the X.509 certificates public key was generated from the
* given private key
*
* @param certificateText
* @param privateKeyText
* @return
* @throws java.security.GeneralSecurityException
* @throws java.io.IOException
*/
public static boolean isCertificateValid(final String certificateText, final String privateKeyText) throws GeneralSecurityException, IOException {
return isCertificateValid(certificateText, privateKeyText, null);
}
/**
* Checks whether the X.509 certificates public key was generated from the
* given RSA private key
*
* @param certificateText
* @param privateKeyText
* @param privateKeyPassword
* @return
* @throws java.security.GeneralSecurityException
* @throws java.io.IOException
*/
public static boolean isCertificateValid(final String certificateText, final String privateKeyText, final char[] privateKeyPassword)
throws GeneralSecurityException, IOException {
RSAPrivateKey pk = parseRSAPrivateKey(privateKeyText, privateKeyPassword);
X509Certificate certificate = (X509Certificate) parseX509Certificate(certificateText);
return isCertificateValid(certificate, pk);
}
/**
* Checks whether the X.509 certificates public key was generated from the
* given RSA private key
*
* @param cert
* @param privateKey
* @return
* @throws java.security.NoSuchAlgorithmException
*/
public static boolean isCertificateValid(final X509Certificate cert, final RSAPrivateKey privateKey) throws NoSuchAlgorithmException {
String certIDinHex = getCertificateModulusSHA1(cert);
String keyIDinHex = getPrivateKeyModulusSHA1(privateKey);
return certIDinHex.equalsIgnoreCase(keyIDinHex);
}
/**
* returns an instance of PasswordFinder containing the given password
*
* @param password
* @return PasswordFinder
*/
public static PasswordFinder getPasswordFinder(final String password) {
return new PasswordFinder() {
@Override
public char[] getPassword() {
final char[] pass = password.toCharArray();
return pass;
}
};
}
/**
* returns an instance of PasswordFinder containing the given password
*
* @param password
* @return PasswordFinder
*/
public static PasswordFinder getPasswordFinder(final char[] password) {
return new PasswordFinder() {
@Override
public char[] getPassword() {
final char[] pass = password;
return pass;
}
};
}
/**
*
* @param privateKeyText
* @return
* @throws java.io.IOException
* @throws java.security.GeneralSecurityException
*/
public static String getDkimDnsTxt(final String privateKeyText) throws IOException, GeneralSecurityException {
StringBuilder sb = new StringBuilder();
sb.append("v=DKIM1; k=rsa; ");
String cleanedPem = cleanRSAPrivateKeyPem(privateKeyText);
PublicKey pk = getPublicKeyFromPrivateKey(cleanedPem.getBytes(), null);
String key = pemWriter(pk);
key = key.replace("-----BEGIN PUBLIC KEY-----", "");
key = key.replace("-----END PUBLIC KEY-----", "");
key = key.replace("\r", "");
key = key.replace("\n", "");
sb.append("p=").append(key);
return sb.toString();
}
/**
*
* @param keysize
* @return
* @throws NoSuchAlgorithmException
*/
public static KeyPair generateKeyPair(int keysize) throws NoSuchAlgorithmException {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA", new BouncyCastleProvider());
keyGen.initialize(keysize, new SecureRandom());
KeyPair keypair = keyGen.generateKeyPair();
return keypair;
}
/**
*
* @param privateKeyText
* @return
*/
public static String cleanRSAPrivateKeyPem(final String privateKeyText) {
String privatePem = privateKeyText;
String header = "-----BEGIN RSA PRIVATE KEY-----\n";
String footer = "-----END RSA PRIVATE KEY-----\n";
privatePem = privatePem.replace("-----BEGIN RSA PRIVATE KEY-----", "");
privatePem = privatePem.replace("-----END RSA PRIVATE KEY-----", "");
privatePem = privatePem.replace("\n", "");
StringBuilder sb = new StringBuilder();
sb.append(header);
int index = 0;
while (index < privatePem.length()) {
sb.append(privatePem.substring(index, Math.min(index + 64, privatePem.length()))).append("\n");
index += 64;
}
sb.append(footer);
return sb.toString();
}
private static PrivateKey generatePrivateKeyByPEM(String privateKey) throws InvalidKeySpecException, NoSuchAlgorithmException {
PrivateKey privKey;
String privatePem = privateKey;
privatePem = privatePem.replace("-----BEGIN RSA PRIVATE KEY-----", "");
privatePem = privatePem.replace("-----END RSA PRIVATE KEY-----", "");
privatePem = privatePem.replace("\n", "");
byte[] encoded = Base64.decode(privatePem);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
KeyFactory kf = KeyFactory.getInstance("RSA");
privKey = kf.generatePrivate(keySpec);
return privKey;
}
final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
/**
* returns a hex representation of the byte array
*
* @param bytes
* @return String
*/
public static String toHexString(final byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
/**
*
* @param o
* @return
* @throws IOException
*/
public static String pemWriter(Object o) throws IOException {
StringWriter sw = new StringWriter();
PEMWriter pw = new PEMWriter(sw);
pw.writeObject(o);
pw.flush();
return sw.toString();
}
/**
*
* @param pem
* @return
* @throws IOException
*/
public static Object pemReader(String pem) throws IOException {
StringReader reader = new StringReader(pem);
PEMReader pr = new PEMReader(reader);
return pr.readObject();
}
}