5959import org .apache .cloudstack .framework .ca .Certificate ;
6060import org .apache .cloudstack .framework .config .ConfigKey ;
6161import org .apache .cloudstack .framework .config .Configurable ;
62+ import org .apache .cloudstack .framework .config .ValidatedConfigKey ;
6263import org .apache .cloudstack .framework .config .dao .ConfigurationDao ;
6364import org .apache .cloudstack .utils .security .CertUtils ;
6465import org .apache .cloudstack .utils .security .KeyStoreUtils ;
@@ -103,25 +104,25 @@ public final class RootCAProvider extends AdapterBase implements CAProvider, Con
103104 /////////////// Root CA Settings ///////////////////
104105 ////////////////////////////////////////////////////
105106
106- private static ConfigKey <String > rootCAPrivateKey = new ConfigKey <>("Hidden" , String .class ,
107- "ca.plugin.root.private.key" ,
108- null ,
107+ private static ConfigKey <String > rootCAPrivateKey = new ValidatedConfigKey <>("Hidden" , String .class ,
108+ "ca.plugin.root.private.key" , null ,
109109 "The ROOT CA private key in PEM format. " +
110110 "When set along with the public key and certificate, CloudStack uses this custom CA instead of auto-generating one. " +
111- "All three ca.plugin.root.* keys must be set together. Restart management server(s) when changed." , false );
111+ "All three ca.plugin.root.* keys must be set together. Restart management server(s) when changed." ,
112+ false , ConfigKey .Scope .Global , null , RootCAProvider ::validatePrivateKeyPem );
112113
113- private static ConfigKey <String > rootCAPublicKey = new ConfigKey <>("Hidden" , String .class ,
114- "ca.plugin.root.public.key" ,
115- null ,
114+ private static ConfigKey <String > rootCAPublicKey = new ValidatedConfigKey <>("Hidden" , String .class ,
115+ "ca.plugin.root.public.key" , null ,
116116 "The ROOT CA public key in PEM format (X.509/SPKI: must start with '-----BEGIN PUBLIC KEY-----'). " +
117- "Required when providing a custom CA. Restart management server(s) when changed." , false );
117+ "Required when providing a custom CA. Restart management server(s) when changed." ,
118+ false , ConfigKey .Scope .Global , null , RootCAProvider ::validatePublicKeyPem );
118119
119- private static ConfigKey <String > rootCACertificate = new ConfigKey <>("Hidden" , String .class ,
120- "ca.plugin.root.ca.certificate" ,
121- null ,
120+ private static ConfigKey <String > rootCACertificate = new ValidatedConfigKey <>("Hidden" , String .class ,
121+ "ca.plugin.root.ca.certificate" , null ,
122122 "The CA certificate(s) in PEM format (must start with '-----BEGIN CERTIFICATE-----'). " +
123123 "For intermediate CAs, concatenate the signing cert first, followed by intermediate(s) and root. " +
124- "Required when providing a custom CA. Restart management server(s) when changed." , false );
124+ "Required when providing a custom CA. Restart management server(s) when changed." ,
125+ false , ConfigKey .Scope .Global , null , RootCAProvider ::validateCACertificatePem );
125126
126127 private static ConfigKey <String > rootCAIssuerDN = new ConfigKey <>("Advanced" , String .class ,
127128 "ca.plugin.root.issuer.dn" ,
@@ -333,18 +334,20 @@ private boolean saveNewRootCACertificate() {
333334 }
334335 try {
335336 logger .debug ("Generating root CA certificate" );
336- final X509Certificate rootCaCertificate = CertUtils .generateV3Certificate (
337+ final X509Certificate generatedCACert = CertUtils .generateV3Certificate (
337338 null , caKeyPair , caKeyPair .getPublic (),
338339 rootCAIssuerDN .value (), CAManager .CertSignatureAlgorithm .value (),
339340 getCaValidityDays (), null , null );
340- if (!configDao .update (rootCACertificate .key (), rootCACertificate .category (), CertUtils .x509CertificateToPem (rootCaCertificate ))) {
341+ if (!configDao .update (rootCACertificate .key (), rootCACertificate .category (), CertUtils .x509CertificateToPem (generatedCACert ))) {
341342 logger .error ("Failed to update RootCA public/x509 certificate" );
342343 }
344+ caCertificates = new ArrayList <>(java .util .Collections .singletonList (generatedCACert ));
345+ caCertificate = generatedCACert ;
343346 } catch (final CertificateException | NoSuchAlgorithmException | NoSuchProviderException | SignatureException | InvalidKeyException | OperatorCreationException | IOException e ) {
344347 logger .error ("Failed to generate RootCA certificate from private/public keys due to exception:" , e );
345348 return false ;
346349 }
347- return loadRootCACertificate () ;
350+ return caCertificate != null ;
348351 }
349352
350353 private boolean loadRootCAKeyPair () {
@@ -361,26 +364,30 @@ private boolean loadRootCAKeyPair() {
361364 }
362365
363366 private boolean loadRootCACertificate () {
367+ caCertificate = null ;
368+ caCertificates = null ;
364369 if (StringUtils .isEmpty (rootCACertificate .value ())) {
365370 return false ;
366371 }
367372 try {
368- caCertificates = CertUtils .pemToX509Certificates (rootCACertificate .value ());
369- if (CollectionUtils .isEmpty (caCertificates )) {
373+ final List < X509Certificate > loadedCerts = CertUtils .pemToX509Certificates (rootCACertificate .value ());
374+ if (CollectionUtils .isEmpty (loadedCerts )) {
370375 logger .error ("No certificates found in ca.plugin.root.ca.certificate" );
371376 return false ;
372377 }
373- caCertificate = caCertificates .get (0 );
378+ final X509Certificate loadedCACert = loadedCerts .get (0 );
374379
375380 // Verify key ownership without enforcing self-signature
376- if (!caCertificate .getPublicKey ().equals (caKeyPair .getPublic ())) {
381+ if (!loadedCACert .getPublicKey ().equals (caKeyPair .getPublic ())) {
377382 logger .error ("The public key in the CA certificate does not match the configured CA public key" );
378383 return false ;
379384 }
380385
381- if (caCertificates .size () > 1 ) {
382- logger .info ("Loaded CA certificate chain with {} certificate(s)" , caCertificates .size ());
386+ if (loadedCerts .size () > 1 ) {
387+ logger .info ("Loaded CA certificate chain with {} certificate(s)" , loadedCerts .size ());
383388 }
389+ caCertificates = loadedCerts ;
390+ caCertificate = loadedCACert ;
384391 } catch (final IOException | CertificateException e ) {
385392 logger .error ("Failed to load saved RootCA certificate due to exception:" , e );
386393 return false ;
@@ -446,6 +453,40 @@ protected void addConfiguredManagementIp(List<String> ipList) {
446453 }
447454
448455
456+ private static void validatePrivateKeyPem (String value ) {
457+ if (StringUtils .isEmpty (value )) return ;
458+ try {
459+ CertUtils .pemToPrivateKey (value );
460+ } catch (InvalidKeySpecException | IOException e ) {
461+ throw new IllegalArgumentException (
462+ "ca.plugin.root.private.key is not a valid PEM private key: " + e .getMessage ());
463+ }
464+ }
465+
466+ private static void validatePublicKeyPem (String value ) {
467+ if (StringUtils .isEmpty (value )) return ;
468+ try {
469+ CertUtils .pemToPublicKey (value );
470+ } catch (InvalidKeySpecException | IOException e ) {
471+ throw new IllegalArgumentException (
472+ "ca.plugin.root.public.key is not a valid PEM public key: " + e .getMessage ());
473+ }
474+ }
475+
476+ private static void validateCACertificatePem (String value ) {
477+ if (StringUtils .isEmpty (value )) return ;
478+ try {
479+ final List <X509Certificate > certs = CertUtils .pemToX509Certificates (value );
480+ if (CollectionUtils .isEmpty (certs )) {
481+ throw new IllegalArgumentException (
482+ "ca.plugin.root.ca.certificate contains no certificates" );
483+ }
484+ } catch (IOException | CertificateException e ) {
485+ throw new IllegalArgumentException (
486+ "ca.plugin.root.ca.certificate is not a valid PEM certificate: " + e .getMessage ());
487+ }
488+ }
489+
449490 private boolean setupCA () {
450491 if (!loadRootCAKeyPair ()) {
451492 if (hasUserProvidedCAKeys ()) {
0 commit comments