/*************************GO-LICENSE-START*********************************
* Copyright 2014 ThoughtWorks, Inc.
*
* 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.
*************************GO-LICENSE-END***********************************/
package com.thoughtworks.go.security;
import com.thoughtworks.go.util.SystemEnvironment;
import org.apache.commons.io.IOUtils;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.X509Extensions;
import org.bouncycastle.jce.PrincipalUtil;
import org.bouncycastle.jce.X509Principal;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.x509.X509V1CertificateGenerator;
import org.bouncycastle.x509.X509V3CertificateGenerator;
import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure;
import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure;
import org.joda.time.DateTime;
import org.springframework.stereotype.Component;
import javax.security.auth.x500.X500Principal;
import java.io.File;
import java.io.FileInputStream;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.Date;
import static com.thoughtworks.go.security.X509PrincipalGenerator.*;
import static com.thoughtworks.go.util.ExceptionUtils.bomb;
import static com.thoughtworks.go.util.SystemEnvironment.GO_SSL_CERTS_ALGORITHM;
import static com.thoughtworks.go.util.SystemEnvironment.GO_SSL_CERTS_PUBLIC_KEY_ALGORITHM;
@Component
public class X509CertificateGenerator {
private static final int YEARS = 10;
private static final String PASSWORD = "Crui3CertSigningPassword";
@Deprecated private static final char[] PASSWORD_AS_CHAR_ARRAY = PASSWORD.toCharArray();
public static final String AGENT_CERT_OU = "Cruise agent certificate";
private static final String INTERMEDIATE_CERT_OU = "Cruise intermediate certificate";
private static final String CERT_EMAIL = "support@thoughtworks.com";
private static final String FRIENDLY_NAME = "cruise";
@Deprecated /*KeyStoreManager*/ private static final String KEYSTORE_TYPE = "JKS";
private final KeyStoreManager keyStoreManager;
public X509CertificateGenerator() {
Security.addProvider(new BouncyCastleProvider());
this.keyStoreManager = new KeyStoreManager();
}
public void createAndStoreX509Certificates(File keystore, File truststore, File agentKeystore,
String password, String principalDn) {
if (!keystore.exists()) {
storeX509Certificate(keystore, password, createCertificateWithDn(principalDn));
}
if (!(truststore.exists() || agentKeystore.exists())) {
storeX509Certificate(truststore, password, createAndStoreCACertificates(agentKeystore));
}
}
private void storeX509Certificate(File file, String passwd, Registration entry) {
try {
PKCS12BagAttributeSetter.usingBagAttributeCarrier(entry.getPrivateKey())
.setFriendlyName(FRIENDLY_NAME)
.setLocalKeyId(entry.getPublicKey());
keyStoreManager.storeX509Certificate(FRIENDLY_NAME, file, passwd, entry);
} catch (Exception e) {
throw bomb(e);
}
}
public Registration createCertificateWithDn(String dn) {
KeyPair keypair = generateKeyPair();
Date epoch = new Date(0);
X509Certificate certificate = createTypeOneX509Certificate(epoch, dn, keypair);
return new Registration(keypair.getPrivate(), certificate);
}
private X509Certificate createTypeOneX509Certificate(Date startDate, String principalDn, KeyPair keyPair) {
X509V1CertificateGenerator certGen = new X509V1CertificateGenerator();
X500Principal principal = new X500Principal(principalDn);
certGen.setSerialNumber(serialNumber());
certGen.setIssuerDN(principal);
certGen.setNotBefore(startDate);
DateTime now = new DateTime(new Date());
certGen.setNotAfter(now.plusYears(YEARS).toDate());
certGen.setSubjectDN(principal); // note: same as issuer
certGen.setPublicKey(keyPair.getPublic());
certGen.setSignatureAlgorithm(new SystemEnvironment().get(GO_SSL_CERTS_ALGORITHM));
try {
return certGen.generate(keyPair.getPrivate(), "BC");
} catch (Exception e) {
throw bomb(e);
}
}
private X509Certificate createIntermediateCertificate(PrivateKey caPrivKey,
X509Certificate caCert,
Date startDate, KeyPair keyPair) throws Exception {
X509Principal issuerDn = PrincipalUtil.getSubjectX509Principal(caCert);
X509Principal subjectDn = createX509Principal(
withOU(INTERMEDIATE_CERT_OU),
withEmailAddress(CERT_EMAIL)
);
X509CertificateGenerator.V3X509CertificateGenerator v3CertGen = new V3X509CertificateGenerator(startDate,
issuerDn, subjectDn, keyPair.getPublic(), serialNumber());
// extensions
v3CertGen.addSubjectKeyIdExtension(keyPair.getPublic());
v3CertGen.addAuthorityKeyIdExtension(caCert);
v3CertGen.addBasicConstraintsExtension();
X509Certificate cert = v3CertGen.generate(caPrivKey);
Date now = new Date();
cert.checkValidity(now);
cert.verify(caCert.getPublicKey());
PKCS12BagAttributeSetter.usingBagAttributeCarrier(cert)
.setFriendlyName(INTERMEDIATE_CERT_OU);
PKCS12BagAttributeSetter.usingBagAttributeCarrier(keyPair.getPrivate())
.setFriendlyName(FRIENDLY_NAME)
.setLocalKeyId(keyPair.getPublic());
return cert;
}
private X509Certificate createAgentCertificate(PublicKey publicKey, PrivateKey intermediatePrivateKey,
PublicKey intermediatePublicKey, String hostname,
Date startDate) throws Exception {
X509Principal issuerDn = createX509Principal(
withOU(INTERMEDIATE_CERT_OU),
withEmailAddress(CERT_EMAIL)
);
X509Principal subjectDn = createX509Principal(
withOU(AGENT_CERT_OU),
withCN(hostname),
withEmailAddress(CERT_EMAIL)
);
X509CertificateGenerator.V3X509CertificateGenerator v3CertGen = new V3X509CertificateGenerator(startDate,
issuerDn, subjectDn, publicKey, BigInteger.valueOf(3));
// add the extensions
v3CertGen.addSubjectKeyIdExtension(publicKey);
v3CertGen.addAuthorityKeyIdExtension(intermediatePublicKey);
X509Certificate cert = v3CertGen.generate(intermediatePrivateKey);
Date now = new Date();
cert.checkValidity(now);
cert.verify(intermediatePublicKey);
PKCS12BagAttributeSetter.usingBagAttributeCarrier(cert)
.setFriendlyName("cruise-agent")
.setLocalKeyId(publicKey);
return cert;
}
public Registration createAndStoreCACertificates(File keystore) {
Date startDate = new Date(0);
String principalDn = "ou=Cruise Server primary certificate, cn=" + getHostname();
try {
KeyPair caKeyPair = generateKeyPair();
X509Certificate caCertificate = createTypeOneX509Certificate(startDate, principalDn, caKeyPair);
KeyPair intKeyPair = generateKeyPair();
X509Certificate intermediateCertificate = createIntermediateCertificate(
caKeyPair.getPrivate(), caCertificate, startDate, intKeyPair);
Registration intermediateEntry = new Registration(intKeyPair.getPrivate(), intermediateCertificate);
keyStoreManager.storeCACertificate(keystore, PASSWORD, caCertificate, intermediateEntry);
return new Registration(intKeyPair.getPrivate(), intermediateCertificate, caCertificate);
} catch (Exception e) {
throw new RuntimeException("Couldn't create server certificates", e);
}
}
public Registration createAgentCertificate(final File authorityKeystore, String agentHostname) {
Date epoch = new Date(0);
KeyPair agentKeyPair = generateKeyPair();
try {
KeyStore store = loadOrCreateCAKeyStore(authorityKeystore);
KeyStore.PrivateKeyEntry intermediateEntry = (KeyStore.PrivateKeyEntry) store.getEntry("ca-intermediate",
new KeyStore.PasswordProtection(PASSWORD_AS_CHAR_ARRAY));
Certificate[] chain = new Certificate[3];
chain[2] = store.getCertificate("ca-cert");
chain[1] = intermediateEntry.getCertificate();
chain[0] = createAgentCertificate(agentKeyPair.getPublic(),
intermediateEntry.getPrivateKey(),
chain[1].getPublicKey(), agentHostname, epoch);
return new Registration(agentKeyPair.getPrivate(), chain);
} catch (Exception e) {
throw bomb("Couldn't create agent certificate", e);
}
}
private KeyStore loadOrCreateCAKeyStore(File authorityKeystore) throws Exception {
KeyStore keyStore = keyStoreManager.tryLoad(authorityKeystore, PASSWORD);
if (keyStore == null) {
createAndStoreCACertificates(authorityKeystore);
keyStore = keyStoreManager.load(authorityKeystore, PASSWORD);
}
return keyStore;
}
// Used for testing
boolean verifySigned(File keystore, Certificate agentCertificate) {
try {
KeyStore store = KeyStore.getInstance(KEYSTORE_TYPE);
FileInputStream inputStream = new FileInputStream(keystore);
store.load(inputStream, PASSWORD_AS_CHAR_ARRAY);
IOUtils.closeQuietly(inputStream);
KeyStore.PrivateKeyEntry intermediateEntry = (KeyStore.PrivateKeyEntry) store.getEntry("ca-intermediate",
new KeyStore.PasswordProtection(PASSWORD_AS_CHAR_ARRAY));
Certificate intermediateCertificate = intermediateEntry.getCertificate();
agentCertificate.verify(intermediateCertificate.getPublicKey());
return true;
} catch (Exception e) {
return false;
}
}
private String getHostname() {
try {
return InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
throw bomb(e);
}
}
private BigInteger serialNumber() {
return new BigInteger(Long.toString(Math.round(Math.random() * 11234455544545L)));
}
private KeyPair generateKeyPair() {
try {
return KeyPairGenerator.getInstance("RSA", "BC").generateKeyPair();
} catch (Exception e) {
throw bomb("Couldn't create public-private key pair", e);
}
}
private class V3X509CertificateGenerator {
private final X509V3CertificateGenerator v3CertGen;
public V3X509CertificateGenerator(Date startDate, X509Principal issuerDn, X509Principal subjectDn,
PublicKey publicKey, BigInteger serialNumber) {
X509V3CertificateGenerator gen = new X509V3CertificateGenerator();
gen.reset();
gen.setSignatureAlgorithm(new SystemEnvironment().get(GO_SSL_CERTS_PUBLIC_KEY_ALGORITHM));
gen.setNotBefore(startDate);
DateTime now = new DateTime(new Date());
gen.setNotAfter(now.plusYears(YEARS).toDate());
gen.setIssuerDN(issuerDn);
gen.setSubjectDN(subjectDn);
gen.setPublicKey(publicKey);
gen.setSerialNumber(serialNumber);
this.v3CertGen = gen;
}
public void addSubjectKeyIdExtension(PublicKey key) throws CertificateParsingException, InvalidKeyException {
v3CertGen.addExtension(X509Extensions.SubjectKeyIdentifier, false,
new SubjectKeyIdentifierStructure(key));
}
public void addAuthorityKeyIdExtension(X509Certificate cert) throws CertificateParsingException {
v3CertGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false,
new AuthorityKeyIdentifierStructure(cert));
}
public void addAuthorityKeyIdExtension(PublicKey key) throws InvalidKeyException {
v3CertGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false,
new AuthorityKeyIdentifierStructure(key));
}
private void addBasicConstraintsExtension() {
v3CertGen.addExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(0));
}
public X509Certificate generate(PrivateKey caPrivKey) throws Exception {
return v3CertGen.generate(caPrivKey, "BC");
}
}
}