From 1f52cd42451ae71a70386f790cbcc2caf80db13d Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Tue, 10 Oct 2017 10:38:13 +0530 Subject: [PATCH] FR12: Have basic constraint in CA certificate (#52) * FR12: Have basic constraint in CA certificate - Refactors certificate generation to use V3 - Removes use of V1 based certificate generator - Puts basic constraint and keyusage extentions in certificate generator when caCert is not provided, i.e. for building CA certificate - For normal certificate generation, skips putting basic constraint instead puts authority key identifier (the ca cert) - Fixes tests to use the V3 certificate generator Signed-off-by: Rohit Yadav * FR12: backup and restore cpvm/ssvm keystore during reboot This is backported from: https://github.com/apache/cloudstack/pull/2278 Signed-off-by: Rohit Yadav --- .../ca/provider/RootCAProvider.java | 21 ++--- .../RootCACustomTrustManagerTest.java | 8 +- .../ca/provider/RootCAProviderTest.java | 2 +- .../cloudstack/ca/CABackgroundTaskTest.java | 4 +- .../cloudstack/ca/CAManagerImplTest.java | 4 +- .../config/opt/cloud/bin/patchsystemvm.sh | 11 +++ .../cloudstack/utils/security/CertUtils.java | 77 +++++++++++-------- .../utils/security/CertUtilsTest.java | 6 +- 8 files changed, 79 insertions(+), 54 deletions(-) diff --git a/plugins/ca/root-ca/src/org/apache/cloudstack/ca/provider/RootCAProvider.java b/plugins/ca/root-ca/src/org/apache/cloudstack/ca/provider/RootCAProvider.java index a904f98b2d9..76e706963e6 100644 --- a/plugins/ca/root-ca/src/org/apache/cloudstack/ca/provider/RootCAProvider.java +++ b/plugins/ca/root-ca/src/org/apache/cloudstack/ca/provider/RootCAProvider.java @@ -35,7 +35,6 @@ import java.security.SecureRandom; import java.security.Security; import java.security.SignatureException; import java.security.UnrecoverableKeyException; -import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateNotYetValidException; @@ -139,11 +138,12 @@ public final class RootCAProvider extends AdapterBase implements CAProvider, Con final String subject = "CN=" + domainNames.get(0); final KeyPair keyPair = CertUtils.generateRandomKeyPair(CAManager.CertKeySize.value()); - final X509Certificate clientCertificate = CertUtils.generateV3Certificate( + final X509Certificate clientCertificate = CertUtils.generateCertificate( caCertificate, - caKeyPair.getPrivate(), + caKeyPair, keyPair.getPublic(), subject, + caCertificate.getIssuerDN().getName(), CAManager.CertSignatureAlgorithm.value(), validityDays, domainNames, @@ -167,10 +167,11 @@ public final class RootCAProvider extends AdapterBase implements CAProvider, Con final PKCS10CertificationRequest request = new PKCS10CertificationRequest(pemObject.getContent()); - final X509Certificate clientCertificate = CertUtils.generateV3Certificate( - caCertificate, caKeyPair.getPrivate(), + final X509Certificate clientCertificate = CertUtils.generateCertificate( + caCertificate, caKeyPair, request.getPublicKey(), request.getCertificationRequestInfo().getSubject().toString(), + caCertificate.getIssuerDN().getName(), CAManager.CertSignatureAlgorithm.value(), validityDays, domainNames, @@ -461,16 +462,18 @@ public final class RootCAProvider extends AdapterBase implements CAProvider, Con } try { LOG.debug("Generating root CA certificate"); - final X509Certificate rootCaCertificate = CertUtils.generateV1Certificate( + final X509Certificate rootCaCertificate = CertUtils.generateCertificate( + null, caKeyPair, + caKeyPair.getPublic(), rootCAIssuerDN.value(), rootCAIssuerDN.value(), - caValidityYears, - CAManager.CertSignatureAlgorithm.value()); + CAManager.CertSignatureAlgorithm.value(), + caValidityYears * 365, null, null); if (!configDao.update(rootCACertificate.key(), rootCACertificate.category(), CertUtils.x509CertificateToPem(rootCaCertificate))) { LOG.error("Failed to update RootCA public/x509 certificate"); } - } catch (final NoSuchAlgorithmException | NoSuchProviderException | CertificateEncodingException | SignatureException | InvalidKeyException | IOException e) { + } catch (final NoSuchAlgorithmException | NoSuchProviderException | CertificateException | SignatureException | InvalidKeyException | IOException e) { LOG.error("Failed to generate RootCA certificate from private/public keys due to exception:", e); return false; } diff --git a/plugins/ca/root-ca/test/org/apache/cloudstack/ca/provider/RootCACustomTrustManagerTest.java b/plugins/ca/root-ca/test/org/apache/cloudstack/ca/provider/RootCACustomTrustManagerTest.java index 5e36bd4e3f9..e7ae14a0a48 100644 --- a/plugins/ca/root-ca/test/org/apache/cloudstack/ca/provider/RootCACustomTrustManagerTest.java +++ b/plugins/ca/root-ca/test/org/apache/cloudstack/ca/provider/RootCACustomTrustManagerTest.java @@ -56,10 +56,10 @@ public class RootCACustomTrustManagerTest { certMap.clear(); caKeypair = CertUtils.generateRandomKeyPair(1024); clientKeypair = CertUtils.generateRandomKeyPair(1024); - caCertificate = CertUtils.generateV1Certificate(caKeypair, "CN=ca", "CN=ca", 1, - "SHA256withRSA"); - expiredClientCertificate = CertUtils.generateV3Certificate(caCertificate, caKeypair.getPrivate(), clientKeypair.getPublic(), - "CN=cloudstack.apache.org", "SHA256withRSA", 0, Collections.singletonList("cloudstack.apache.org"), Collections.singletonList(clientIp)); + caCertificate = CertUtils.generateCertificate(null, caKeypair, caKeypair.getPublic(), "CN=ca", "CN=ca", + "SHA256withRSA", 1, null, null); + expiredClientCertificate = CertUtils.generateCertificate(caCertificate, caKeypair, clientKeypair.getPublic(), + "CN=cloudstack.apache.org", caCertificate.getIssuerDN().getName(), "SHA256withRSA", 0, Collections.singletonList("cloudstack.apache.org"), Collections.singletonList(clientIp)); } @Test diff --git a/plugins/ca/root-ca/test/org/apache/cloudstack/ca/provider/RootCAProviderTest.java b/plugins/ca/root-ca/test/org/apache/cloudstack/ca/provider/RootCAProviderTest.java index 44e2bcfcc49..71c030a3742 100644 --- a/plugins/ca/root-ca/test/org/apache/cloudstack/ca/provider/RootCAProviderTest.java +++ b/plugins/ca/root-ca/test/org/apache/cloudstack/ca/provider/RootCAProviderTest.java @@ -66,7 +66,7 @@ public class RootCAProviderTest { @Before public void setUp() throws Exception { caKeyPair = CertUtils.generateRandomKeyPair(1024); - caCertificate = CertUtils.generateV1Certificate(caKeyPair, "CN=ca", "CN=ca", 1, "SHA256withRSA"); + caCertificate = CertUtils.generateCertificate(null, caKeyPair, caKeyPair.getPublic(), "CN=ca", "CN=ca", "SHA256withRSA", 1, null, null); provider = new RootCAProvider(); diff --git a/server/test/org/apache/cloudstack/ca/CABackgroundTaskTest.java b/server/test/org/apache/cloudstack/ca/CABackgroundTaskTest.java index d2c800d8272..3a315424cdc 100644 --- a/server/test/org/apache/cloudstack/ca/CABackgroundTaskTest.java +++ b/server/test/org/apache/cloudstack/ca/CABackgroundTaskTest.java @@ -68,8 +68,8 @@ public class CABackgroundTaskTest { host.setManagementServerId(ManagementServerNode.getManagementServerId()); task = new CAManagerImpl.CABackgroundTask(caManager, hostDao); final KeyPair keypair = CertUtils.generateRandomKeyPair(1024); - expiredCertificate = CertUtils.generateV1Certificate(keypair, "CN=ca", "CN=ca", 0, - "SHA256withRSA"); + expiredCertificate = CertUtils.generateCertificate(null, keypair, keypair.getPublic(), "CN=ca", "CN=ca", + "SHA256withRSA", 0, null, null); Mockito.when(hostDao.findByIp(Mockito.anyString())).thenReturn(host); Mockito.when(caManager.getActiveCertificatesMap()).thenReturn(certMap); diff --git a/server/test/org/apache/cloudstack/ca/CAManagerImplTest.java b/server/test/org/apache/cloudstack/ca/CAManagerImplTest.java index 87e128c772e..26315476164 100644 --- a/server/test/org/apache/cloudstack/ca/CAManagerImplTest.java +++ b/server/test/org/apache/cloudstack/ca/CAManagerImplTest.java @@ -21,6 +21,7 @@ package org.apache.cloudstack.ca; import java.lang.reflect.Field; import java.math.BigInteger; +import java.security.KeyPair; import java.security.cert.X509Certificate; import java.util.Collections; @@ -108,7 +109,8 @@ public class CAManagerImplTest { public void testProvisionCertificate() throws Exception { final Host host = Mockito.mock(Host.class); Mockito.when(host.getPrivateIpAddress()).thenReturn("1.2.3.4"); - final X509Certificate certificate = CertUtils.generateV1Certificate(CertUtils.generateRandomKeyPair(1024), "CN=ca", "CN=ca", 1, "SHA256withRSA"); + final KeyPair keyPair = CertUtils.generateRandomKeyPair(1024); + final X509Certificate certificate = CertUtils.generateCertificate(null, keyPair, keyPair.getPublic(), "CN=ca", "CN=ca", "SHA256withRSA", 1, null, null); Mockito.when(caProvider.issueCertificate(Mockito.anyString(), Mockito.anyList(), Mockito.anyList(), Mockito.anyInt())).thenReturn(new Certificate(certificate, null, Collections.singletonList(certificate))); Mockito.when(agentManager.send(Mockito.anyLong(), Mockito.any(SetupKeyStoreCommand.class))).thenReturn(new SetupKeystoreAnswer("someCsr")); Mockito.when(agentManager.reconnect(Mockito.anyLong())).thenReturn(true); diff --git a/systemvm/patches/debian/config/opt/cloud/bin/patchsystemvm.sh b/systemvm/patches/debian/config/opt/cloud/bin/patchsystemvm.sh index 3c44fc19ff5..d799c7304c3 100755 --- a/systemvm/patches/debian/config/opt/cloud/bin/patchsystemvm.sh +++ b/systemvm/patches/debian/config/opt/cloud/bin/patchsystemvm.sh @@ -21,10 +21,21 @@ logfile="/var/log/patchsystemvm.log" # To use existing console proxy .zip-based package file patch_console_proxy() { local patchfile=$1 + local backupfolder="/tmp/.conf.backup" + if [ -f /usr/local/cloud/systemvm/conf/cloud.jks ]; then + rm -fr $backupfolder + mkdir -p $backupfolder + cp -r /usr/local/cloud/systemvm/conf/* $backupfolder/ + fi rm /usr/local/cloud/systemvm -rf mkdir -p /usr/local/cloud/systemvm echo "All" | unzip $patchfile -d /usr/local/cloud/systemvm >$logfile 2>&1 find /usr/local/cloud/systemvm/ -name \*.sh | xargs chmod 555 + if [ -f $backupfolder/cloud.jks ]; then + cp -r $backupfolder/* /usr/local/cloud/systemvm/conf/ + echo "Restored keystore file and certs using backup" >> $logfile + fi + rm -fr $backupfolder return 0 } diff --git a/utils/src/org/apache/cloudstack/utils/security/CertUtils.java b/utils/src/org/apache/cloudstack/utils/security/CertUtils.java index 0aafa9b9b52..bf63d197d86 100644 --- a/utils/src/org/apache/cloudstack/utils/security/CertUtils.java +++ b/utils/src/org/apache/cloudstack/utils/security/CertUtils.java @@ -34,7 +34,6 @@ import java.security.PublicKey; import java.security.SecureRandom; import java.security.Security; import java.security.SignatureException; -import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.security.spec.InvalidKeySpecException; @@ -48,13 +47,14 @@ import javax.security.auth.x500.X500Principal; import org.apache.log4j.Logger; import org.bouncycastle.asn1.ASN1Encodable; import org.bouncycastle.asn1.DERSequence; +import org.bouncycastle.asn1.x509.BasicConstraints; import org.bouncycastle.asn1.x509.GeneralName; +import org.bouncycastle.asn1.x509.KeyUsage; import org.bouncycastle.asn1.x509.X509Extensions; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openssl.PEMReader; import org.bouncycastle.openssl.PEMWriter; import org.bouncycastle.util.io.pem.PemObject; -import org.bouncycastle.x509.X509V1CertificateGenerator; import org.bouncycastle.x509.X509V3CertificateGenerator; import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure; import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure; @@ -145,47 +145,56 @@ public class CertUtils { return new BigInteger(64, new SecureRandom()); } - public static X509Certificate generateV1Certificate(final KeyPair keyPair, - final String subjectDN, - final String issuerDN, - final int validityYears, - final String signatureAlgorithm) throws NoSuchAlgorithmException, NoSuchProviderException, CertificateEncodingException, SignatureException, InvalidKeyException { - final DateTime now = DateTime.now(DateTimeZone.UTC); - final X500Principal subjectDn = new X500Principal(subjectDN); - final X500Principal issuerDn = new X500Principal(issuerDN); - final X509V1CertificateGenerator certGen = new X509V1CertificateGenerator(); - certGen.setSerialNumber(generateRandomBigInt()); - certGen.setSubjectDN(subjectDn); - certGen.setIssuerDN(issuerDn); - certGen.setNotBefore(now.minusDays(1).toDate()); - certGen.setNotAfter(now.plusYears(validityYears).toDate()); - certGen.setPublicKey(keyPair.getPublic()); - certGen.setSignatureAlgorithm(signatureAlgorithm); - return certGen.generate(keyPair.getPrivate(), "BC"); - } - - public static X509Certificate generateV3Certificate(final X509Certificate caCert, - final PrivateKey caPrivateKey, - final PublicKey clientPublicKey, - final String subjectDN, - final String signatureAlgorithm, - final int validityDays, - final List dnsNames, - final List publicIPAddresses) throws IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, InvalidKeyException, SignatureException { + /** + * Generates a X509 V3 Certificate based on provided arguments + * @param caCert when caCert is not provided self-signed root CA certificate is generated with basic constraints + * @param caKeyPair the ca KeyPair that contains public and private keys + * @param clientPublicKey the client public key, for CA self-signing this will be CA public key + * @param subjectDN + * @param issuerDN + * @param signatureAlgorithm + * @param validityDays + * @param dnsNames + * @param publicIPAddresses + * @return returns a X509Certificate + * @throws IOException + * @throws NoSuchAlgorithmException + * @throws CertificateException + * @throws NoSuchProviderException + * @throws InvalidKeyException + * @throws SignatureException + */ + public static X509Certificate generateCertificate(final X509Certificate caCert, + final KeyPair caKeyPair, + final PublicKey clientPublicKey, + final String subjectDN, + final String issuerDN, + final String signatureAlgorithm, + final int validityDays, + final List dnsNames, + final List publicIPAddresses) throws IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, InvalidKeyException, SignatureException { final DateTime now = DateTime.now(DateTimeZone.UTC); final BigInteger serial = generateRandomBigInt(); final X500Principal subject = new X500Principal(subjectDN); + final X500Principal issuer = new X500Principal(issuerDN); final X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();; certGen.setSerialNumber(serial); - certGen.setIssuerDN(caCert.getSubjectX500Principal()); certGen.setSubjectDN(subject); + certGen.setIssuerDN(issuer); certGen.setNotBefore(now.minusHours(12).toDate()); certGen.setNotAfter(now.plusDays(validityDays).toDate()); certGen.setPublicKey(clientPublicKey); certGen.setSignatureAlgorithm(signatureAlgorithm); - certGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false, - new AuthorityKeyIdentifierStructure(caCert)); + if (caCert == null) { + certGen.addExtension(X509Extensions.BasicConstraints, true, + new BasicConstraints(true, 0)); + certGen.addExtension(X509Extensions.KeyUsage, true, + new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign)); + } else { + certGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false, + new AuthorityKeyIdentifierStructure(caCert)); + } certGen.addExtension(X509Extensions.SubjectKeyIdentifier, false, new SubjectKeyIdentifierStructure(clientPublicKey)); @@ -212,8 +221,8 @@ public class CertUtils { certGen.addExtension(X509Extensions.SubjectAlternativeName, false, subjectAlternativeNamesExtension); } - final X509Certificate certificate = certGen.generate(caPrivateKey, "BC"); - certificate.verify(caCert.getPublicKey()); + final X509Certificate certificate = certGen.generate(caKeyPair.getPrivate(), "BC"); + certificate.verify(caKeyPair.getPublic()); return certificate; } } diff --git a/utils/test/org/apache/cloudstack/utils/security/CertUtilsTest.java b/utils/test/org/apache/cloudstack/utils/security/CertUtilsTest.java index de0e89ee536..ba74392ac93 100644 --- a/utils/test/org/apache/cloudstack/utils/security/CertUtilsTest.java +++ b/utils/test/org/apache/cloudstack/utils/security/CertUtilsTest.java @@ -39,7 +39,7 @@ public class CertUtilsTest { @Before public void setUp() throws Exception { caKeyPair = CertUtils.generateRandomKeyPair(1024); - caCertificate = CertUtils.generateV1Certificate(caKeyPair, "CN=test", "CN=test", 1, "SHA256WithRSAEncryption"); + caCertificate = CertUtils.generateCertificate(null, caKeyPair, caKeyPair.getPublic(), "CN=test", "CN=test", "SHA256WithRSAEncryption", 365, null, null); } @Test @@ -93,8 +93,8 @@ public class CertUtilsTest { final List domainNames = Arrays.asList("domain1.com", "www.2.domain2.com", "3.domain3.com"); final List addressList = Arrays.asList("1.2.3.4", "192.168.1.1", "2a02:120b:2c16:f6d0:d9df:8ebc:e44a:f181"); - final X509Certificate clientCert = CertUtils.generateV3Certificate(caCertificate, caKeyPair.getPrivate(), clientKeyPair.getPublic(), - "CN=domain.example", "SHA256WithRSAEncryption", 10, domainNames, addressList); + final X509Certificate clientCert = CertUtils.generateCertificate(caCertificate, caKeyPair, clientKeyPair.getPublic(), + "CN=domain.example", caCertificate.getIssuerDN().getName(), "SHA256WithRSAEncryption", 10, domainNames, addressList); clientCert.verify(caKeyPair.getPublic()); Assert.assertEquals(clientCert.getIssuerDN(), caCertificate.getIssuerDN());