package com.sequenceiq.cloudbreak.client; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.StringReader; import java.math.BigInteger; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.SecureRandom; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.RSAPrivateKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.Collections; import java.util.Date; import java.util.LinkedHashMap; import java.util.Map; import javax.security.auth.x500.X500Principal; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x509.AlgorithmIdentifier; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.X509v3CertificateBuilder; import org.bouncycastle.crypto.CryptoException; import org.bouncycastle.crypto.digests.SHA256Digest; import org.bouncycastle.crypto.engines.RSAEngine; import org.bouncycastle.crypto.params.AsymmetricKeyParameter; import org.bouncycastle.crypto.params.RSAKeyParameters; import org.bouncycastle.crypto.signers.PSSSigner; import org.bouncycastle.crypto.util.PrivateKeyFactory; import org.bouncycastle.openssl.PEMKeyPair; import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.openssl.jcajce.JcaPEMWriter; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder; import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder; import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.bouncycastle.pkcs.PKCS10CertificationRequest; import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder; import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder; import com.google.common.io.BaseEncoding; public class PkiUtil { private static final int KEY_SIZE = 2048; private static final long VALIDITY = 30 * 365 * 24 * 60 * 60 * 1000; private static final Integer SALT_LENGTH = 20; private static final Integer MAX_SIZE = 20; private static final Map<String, RSAKeyParameters> CACHE = Collections.synchronizedMap(new LinkedHashMap<String, RSAKeyParameters>(MAX_SIZE * 4 / 3, 0.75f, true) { @Override protected boolean removeEldestEntry(Map.Entry<String, RSAKeyParameters> eldest) { return size() > MAX_SIZE; } }); private PkiUtil() { } public static byte[] getPublicKeyDer(String privateKeyPem) { try (PEMParser pemParser = new PEMParser(new StringReader(clarifyPemKey(privateKeyPem)))) { PEMKeyPair pemKeyPair = (PEMKeyPair) pemParser.readObject(); return pemKeyPair.getPublicKeyInfo().getEncoded(); } catch (IOException e) { throw new SecurityException(e); } } public static String generateSignature(String privateKeyPem, byte[] data) { RSAKeyParameters rsaKeyParameters = CACHE.get(privateKeyPem); if (rsaKeyParameters == null) { try (PEMParser pEMParser = new PEMParser(new StringReader(clarifyPemKey(privateKeyPem)))) { PEMKeyPair pemKeyPair = (PEMKeyPair) pEMParser.readObject(); KeyFactory factory = KeyFactory.getInstance("RSA"); X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(pemKeyPair.getPublicKeyInfo().getEncoded()); PublicKey publicKey = factory.generatePublic(publicKeySpec); PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(pemKeyPair.getPrivateKeyInfo().getEncoded()); PrivateKey privateKey = factory.generatePrivate(privateKeySpec); KeyPair kp = new KeyPair(publicKey, privateKey); RSAPrivateKeySpec privKeySpec = factory.getKeySpec(kp.getPrivate(), RSAPrivateKeySpec.class); rsaKeyParameters = new RSAKeyParameters(true, privKeySpec.getModulus(), privKeySpec.getPrivateExponent()); CACHE.put(privateKeyPem, rsaKeyParameters); } catch (NoSuchAlgorithmException | IOException | InvalidKeySpecException e) { throw new SecurityException(e); } } PSSSigner signer = new PSSSigner(new RSAEngine(), new SHA256Digest(), SALT_LENGTH); signer.init(true, rsaKeyParameters); signer.update(data, 0, data.length); try { byte[] signature = signer.generateSignature(); return BaseEncoding.base64().encode(signature); } catch (CryptoException e) { throw new SecurityException(e); } } public static KeyPair generateKeypair() { try { KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); keyGen.initialize(KEY_SIZE, new SecureRandom()); return keyGen.generateKeyPair(); } catch (Exception e) { throw new PkiException("Failed to generate PK for the cluster!", e); } } public static X509Certificate cert(KeyPair identity, String publicAddress, KeyPair signKey) { try { PKCS10CertificationRequest csr = generateCsr(identity, publicAddress); return sign(csr, signKey, identity.getPublic()); } catch (Exception e) { throw new PkiException("Failed to create signed cert for the cluster!", e); } } public static String convert(PrivateKey privateKey) { try { return convertToString(privateKey); } catch (Exception e) { throw new PkiException("Failed to convert Private Key for the cluster!", e); } } public static String convert(PublicKey publicKey) { try { return convertToString(publicKey); } catch (Exception e) { throw new PkiException("Failed to convert Public Key for the cluster!", e); } } public static String convert(X509Certificate cert) { try { return convertToString(cert); } catch (Exception e) { throw new PkiException("Failed to convert signed cert to String", e); } } private static X509Certificate sign(PKCS10CertificationRequest inputCSR, KeyPair signKey, PublicKey idenyityPublic) throws Exception { AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder() .find("SHA256withRSA"); AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder() .find(sigAlgId); AsymmetricKeyParameter akp = PrivateKeyFactory.createKey(signKey.getPrivate() .getEncoded()); SubjectPublicKeyInfo keyInfo = SubjectPublicKeyInfo.getInstance(idenyityPublic.getEncoded()); X509v3CertificateBuilder myCertificateGenerator = new X509v3CertificateBuilder( new X500Name("CN=Cloudbreak"), new BigInteger("1"), new Date( System.currentTimeMillis()), new Date( System.currentTimeMillis() + VALIDITY), inputCSR.getSubject(), keyInfo); ContentSigner sigGen = new BcRSAContentSignerBuilder(sigAlgId, digAlgId) .build(akp); X509CertificateHolder holder = myCertificateGenerator.build(sigGen); CertificateFactory cf = CertificateFactory.getInstance("X.509"); return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(holder.toASN1Structure().getEncoded())); } private static PKCS10CertificationRequest generateCsr(KeyPair identity, String publicAddress) throws Exception { PKCS10CertificationRequestBuilder p10Builder = new JcaPKCS10CertificationRequestBuilder( new X500Principal(String.format("C=US, ST=CA, O=Hortonworks, OU=Cloudbreak, CN=%s", publicAddress)), identity.getPublic()); JcaContentSignerBuilder csBuilder = new JcaContentSignerBuilder("SHA256withRSA"); ContentSigner signer = csBuilder.build(identity.getPrivate()); return p10Builder.build(signer); } private static String convertToString(Object o) throws IOException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); OutputStreamWriter output = new OutputStreamWriter(bos); JcaPEMWriter pem = new JcaPEMWriter(output); pem.writeObject(o); pem.close(); return bos.toString(); } private static String clarifyPemKey(String rawPem) { return "-----BEGIN RSA PRIVATE KEY-----\n" + rawPem.replaceAll("-----(.*)-----|\n", "") + "\n-----END RSA PRIVATE KEY-----"; } }