Skip to content

Commit c3ed1b3

Browse files
authored
CLOUDSTACK-9993: Have basic constraint in CA certificate (#2286)
- Refactors V3 x509 cert generator to put basic constraint and key usage extensions when CA cert is created - Refactors root CA provider to use V3 generator to generate CA cert Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
1 parent 0fedbdd commit c3ed1b3

7 files changed

Lines changed: 61 additions & 55 deletions

File tree

plugins/ca/root-ca/src/org/apache/cloudstack/ca/provider/RootCAProvider.java

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -137,14 +137,9 @@ private Certificate generateCertificate(final List<String> domainNames, final Li
137137

138138
final KeyPair keyPair = CertUtils.generateRandomKeyPair(CAManager.CertKeySize.value());
139139
final X509Certificate clientCertificate = CertUtils.generateV3Certificate(
140-
caCertificate,
141-
caKeyPair.getPrivate(),
142-
keyPair.getPublic(),
143-
subject,
144-
CAManager.CertSignatureAlgorithm.value(),
145-
validityDays,
146-
domainNames,
147-
ipAddresses);
140+
caCertificate, caKeyPair, keyPair.getPublic(),
141+
subject, CAManager.CertSignatureAlgorithm.value(),
142+
validityDays, domainNames, ipAddresses);
148143
return new Certificate(clientCertificate, keyPair.getPrivate(), Collections.singletonList(caCertificate));
149144
}
150145

@@ -164,14 +159,11 @@ private Certificate generateCertificateUsingCsr(final String csr, final List<Str
164159

165160
final PKCS10CertificationRequest request = new PKCS10CertificationRequest(pemObject.getContent());
166161

162+
final String subject = request.getCertificationRequestInfo().getSubject().toString();
167163
final X509Certificate clientCertificate = CertUtils.generateV3Certificate(
168-
caCertificate, caKeyPair.getPrivate(),
169-
request.getPublicKey(),
170-
request.getCertificationRequestInfo().getSubject().toString(),
171-
CAManager.CertSignatureAlgorithm.value(),
172-
validityDays,
173-
domainNames,
174-
ipAddresses);
164+
caCertificate, caKeyPair, request.getPublicKey(),
165+
subject, CAManager.CertSignatureAlgorithm.value(),
166+
validityDays, domainNames, ipAddresses);
175167
return new Certificate(clientCertificate, null, Collections.singletonList(caCertificate));
176168

177169
}
@@ -257,6 +249,10 @@ public SSLEngine createSSLEngine(final SSLContext sslContext, final String remot
257249
/////////////// Root CA Config ///////////////////
258250
//////////////////////////////////////////////////
259251

252+
private int getCaValidityDays() {
253+
return 365 * caValidityYears;
254+
}
255+
260256
private char[] findKeyStorePassphrase() {
261257
char[] passphrase = KeyStoreUtils.defaultKeystorePassphrase;
262258
final String configuredPassphrase = DbProperties.getDbProperties().getProperty("db.cloud.keyStorePassphrase");
@@ -268,7 +264,7 @@ private char[] findKeyStorePassphrase() {
268264

269265
private boolean createManagementServerKeystore(final String keyStoreFilePath, final char[] passphrase) {
270266
final Certificate managementServerCertificate = issueCertificate(Collections.singletonList(NetUtils.getHostName()),
271-
Collections.singletonList(NetUtils.getDefaultHostIp()), caValidityYears * 365);
267+
Collections.singletonList(NetUtils.getDefaultHostIp()), getCaValidityDays());
272268
if (managementServerCertificate == null || managementServerCertificate.getPrivateKey() == null) {
273269
throw new CloudRuntimeException("Failed to generate certificate and setup management server keystore");
274270
}
@@ -347,12 +343,10 @@ private boolean saveNewRootCACertificate() {
347343
}
348344
try {
349345
LOG.debug("Generating root CA certificate");
350-
final X509Certificate rootCaCertificate = CertUtils.generateV1Certificate(
351-
caKeyPair,
352-
rootCAIssuerDN.value(),
353-
rootCAIssuerDN.value(),
354-
caValidityYears,
355-
CAManager.CertSignatureAlgorithm.value());
346+
final X509Certificate rootCaCertificate = CertUtils.generateV3Certificate(
347+
null, caKeyPair, caKeyPair.getPublic(),
348+
rootCAIssuerDN.value(), CAManager.CertSignatureAlgorithm.value(),
349+
getCaValidityDays(), null, null);
356350
if (!configDao.update(rootCACertificate.key(), rootCACertificate.category(), CertUtils.x509CertificateToPem(rootCaCertificate))) {
357351
LOG.error("Failed to update RootCA public/x509 certificate");
358352
}

plugins/ca/root-ca/test/org/apache/cloudstack/ca/provider/RootCACustomTrustManagerTest.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,8 @@ public void setUp() throws Exception {
5656
certMap.clear();
5757
caKeypair = CertUtils.generateRandomKeyPair(1024);
5858
clientKeypair = CertUtils.generateRandomKeyPair(1024);
59-
caCertificate = CertUtils.generateV1Certificate(caKeypair, "CN=ca", "CN=ca", 1,
60-
"SHA256withRSA");
61-
expiredClientCertificate = CertUtils.generateV3Certificate(caCertificate, caKeypair.getPrivate(), clientKeypair.getPublic(),
59+
caCertificate = CertUtils.generateV3Certificate(null, caKeypair, caKeypair.getPublic(), "CN=ca", "SHA256withRSA", 365, null, null);
60+
expiredClientCertificate = CertUtils.generateV3Certificate(caCertificate, caKeypair, clientKeypair.getPublic(),
6261
"CN=cloudstack.apache.org", "SHA256withRSA", 0, Collections.singletonList("cloudstack.apache.org"), Collections.singletonList(clientIp));
6362
}
6463

plugins/ca/root-ca/test/org/apache/cloudstack/ca/provider/RootCAProviderTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ private void overrideDefaultConfigValue(final ConfigKey configKey, final String
6666
@Before
6767
public void setUp() throws Exception {
6868
caKeyPair = CertUtils.generateRandomKeyPair(1024);
69-
caCertificate = CertUtils.generateV1Certificate(caKeyPair, "CN=ca", "CN=ca", 1, "SHA256withRSA");
69+
caCertificate = CertUtils.generateV3Certificate(null, caKeyPair, caKeyPair.getPublic(), "CN=ca", "SHA256withRSA", 365, null, null);
7070

7171
provider = new RootCAProvider();
7272

server/test/org/apache/cloudstack/ca/CABackgroundTaskTest.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,7 @@ public void setUp() throws Exception {
6868
host.setManagementServerId(ManagementServerNode.getManagementServerId());
6969
task = new CAManagerImpl.CABackgroundTask(caManager, hostDao);
7070
final KeyPair keypair = CertUtils.generateRandomKeyPair(1024);
71-
expiredCertificate = CertUtils.generateV1Certificate(keypair, "CN=ca", "CN=ca", 0,
72-
"SHA256withRSA");
71+
expiredCertificate = CertUtils.generateV3Certificate(null, keypair, keypair.getPublic(), "CN=ca", "SHA256withRSA", 0, null, null);
7372

7473
Mockito.when(hostDao.findByIp(Mockito.anyString())).thenReturn(host);
7574
Mockito.when(caManager.getActiveCertificatesMap()).thenReturn(certMap);

server/test/org/apache/cloudstack/ca/CAManagerImplTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import java.lang.reflect.Field;
2323
import java.math.BigInteger;
24+
import java.security.KeyPair;
2425
import java.security.cert.X509Certificate;
2526
import java.util.Collections;
2627

@@ -108,7 +109,8 @@ public void testRevokeCertificate() throws Exception {
108109
public void testProvisionCertificate() throws Exception {
109110
final Host host = Mockito.mock(Host.class);
110111
Mockito.when(host.getPrivateIpAddress()).thenReturn("1.2.3.4");
111-
final X509Certificate certificate = CertUtils.generateV1Certificate(CertUtils.generateRandomKeyPair(1024), "CN=ca", "CN=ca", 1, "SHA256withRSA");
112+
final KeyPair keyPair = CertUtils.generateRandomKeyPair(1024);
113+
final X509Certificate certificate = CertUtils.generateV3Certificate(null, keyPair, keyPair.getPublic(), "CN=ca", "SHA256withRSA", 365, null, null);
112114
Mockito.when(caProvider.issueCertificate(Mockito.anyString(), Mockito.anyList(), Mockito.anyList(), Mockito.anyInt())).thenReturn(new Certificate(certificate, null, Collections.singletonList(certificate)));
113115
Mockito.when(agentManager.send(Mockito.anyLong(), Mockito.any(SetupKeyStoreCommand.class))).thenReturn(new SetupKeystoreAnswer("someCsr"));
114116
Mockito.when(agentManager.reconnect(Mockito.anyLong())).thenReturn(true);

utils/src/main/java/org/apache/cloudstack/utils/security/CertUtils.java

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,11 @@
4848
import org.bouncycastle.asn1.ASN1Encodable;
4949
import org.bouncycastle.asn1.DERSequence;
5050
import org.bouncycastle.asn1.x500.X500Name;
51+
import org.bouncycastle.asn1.x509.BasicConstraints;
5152
import org.bouncycastle.asn1.x509.Extension;
5253
import org.bouncycastle.asn1.x509.GeneralName;
5354
import org.bouncycastle.asn1.x509.GeneralNames;
55+
import org.bouncycastle.asn1.x509.KeyUsage;
5456
import org.bouncycastle.cert.X509CertificateHolder;
5557
import org.bouncycastle.cert.X509v1CertificateBuilder;
5658
import org.bouncycastle.cert.X509v3CertificateBuilder;
@@ -176,7 +178,7 @@ public static X509Certificate generateV1Certificate(final KeyPair keyPair,
176178
}
177179

178180
public static X509Certificate generateV3Certificate(final X509Certificate caCert,
179-
final PrivateKey caPrivateKey,
181+
final KeyPair caKeyPair,
180182
final PublicKey clientPublicKey,
181183
final String subject,
182184
final String signatureAlgorithm,
@@ -186,25 +188,34 @@ public static X509Certificate generateV3Certificate(final X509Certificate caCert
186188

187189
final DateTime now = DateTime.now(DateTimeZone.UTC);
188190
final BigInteger serial = generateRandomBigInt();
189-
final X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(
190-
caCert,
191-
serial,
192-
now.minusHours(12).toDate(),
193-
now.plusDays(validityDays).toDate(),
194-
new X500Principal(subject),
195-
clientPublicKey);
196-
197191
final JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
192+
final X509v3CertificateBuilder certBuilder;
193+
if (caCert == null) {
194+
// Generate CA certificate
195+
certBuilder = new JcaX509v3CertificateBuilder(
196+
new X500Name(subject),
197+
serial,
198+
now.minusHours(12).toDate(),
199+
now.plusDays(validityDays).toDate(),
200+
new X500Name(subject),
201+
clientPublicKey);
202+
203+
certBuilder.addExtension(Extension.basicConstraints, true, new BasicConstraints(true));
204+
certBuilder.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign));
205+
} else {
206+
// Generate client certificate
207+
certBuilder = new JcaX509v3CertificateBuilder(
208+
caCert,
209+
serial,
210+
now.minusHours(12).toDate(),
211+
now.plusDays(validityDays).toDate(),
212+
new X500Principal(subject),
213+
clientPublicKey);
214+
215+
certBuilder.addExtension(Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(caCert));
216+
}
198217

199-
certBuilder.addExtension(
200-
Extension.subjectKeyIdentifier,
201-
false,
202-
extUtils.createSubjectKeyIdentifier(clientPublicKey));
203-
204-
certBuilder.addExtension(
205-
Extension.authorityKeyIdentifier,
206-
false,
207-
extUtils.createAuthorityKeyIdentifier(caCert));
218+
certBuilder.addExtension(Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(clientPublicKey));
208219

209220
final List<ASN1Encodable> subjectAlternativeNames = new ArrayList<ASN1Encodable>();
210221
if (publicIPAddresses != null) {
@@ -225,16 +236,17 @@ public static X509Certificate generateV3Certificate(final X509Certificate caCert
225236
}
226237
if (subjectAlternativeNames.size() > 0) {
227238
final GeneralNames subjectAltNames = GeneralNames.getInstance(new DERSequence(subjectAlternativeNames.toArray(new ASN1Encodable[] {})));
228-
certBuilder.addExtension(
229-
Extension.subjectAlternativeName,
230-
false,
231-
subjectAltNames);
239+
certBuilder.addExtension(Extension.subjectAlternativeName, false, subjectAltNames);
232240
}
233241

234-
final ContentSigner signer = new JcaContentSignerBuilder(signatureAlgorithm).setProvider("BC").build(caPrivateKey);
242+
final ContentSigner signer = new JcaContentSignerBuilder(signatureAlgorithm).setProvider("BC").build(caKeyPair.getPrivate());
235243
final X509CertificateHolder certHolder = certBuilder.build(signer);
236244
final X509Certificate cert = new JcaX509CertificateConverter().setProvider("BC").getCertificate(certHolder);
237-
cert.verify(caCert.getPublicKey());
245+
if (caCert != null) {
246+
cert.verify(caCert.getPublicKey());
247+
} else {
248+
cert.verify(caKeyPair.getPublic());
249+
}
238250
return cert;
239251
}
240252
}

utils/src/test/java/org/apache/cloudstack/utils/security/CertUtilsTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public class CertUtilsTest {
3939
@Before
4040
public void setUp() throws Exception {
4141
caKeyPair = CertUtils.generateRandomKeyPair(1024);
42-
caCertificate = CertUtils.generateV1Certificate(caKeyPair, "CN=test", "CN=test", 1, "SHA256WithRSAEncryption");
42+
caCertificate = CertUtils.generateV3Certificate(null, caKeyPair, caKeyPair.getPublic(), "CN=test", "SHA256WithRSAEncryption", 365, null, null);
4343
}
4444

4545
@Test
@@ -93,7 +93,7 @@ public void testGenerateCertificate() throws Exception {
9393
final List<String> domainNames = Arrays.asList("domain1.com", "www.2.domain2.com", "3.domain3.com");
9494
final List<String> addressList = Arrays.asList("1.2.3.4", "192.168.1.1", "2a02:120b:2c16:f6d0:d9df:8ebc:e44a:f181");
9595

96-
final X509Certificate clientCert = CertUtils.generateV3Certificate(caCertificate, caKeyPair.getPrivate(), clientKeyPair.getPublic(),
96+
final X509Certificate clientCert = CertUtils.generateV3Certificate(caCertificate, caKeyPair, clientKeyPair.getPublic(),
9797
"CN=domain.example", "SHA256WithRSAEncryption", 10, domainNames, addressList);
9898

9999
clientCert.verify(caKeyPair.getPublic());

0 commit comments

Comments
 (0)