rhtyd commented on a change in pull request #2239: CLOUDSTACK-9993: Securing Agents Communications URL: https://github.com/apache/cloudstack/pull/2239#discussion_r134774348
########## File path: plugins/ca/root-ca/src/org/apache/cloudstack/ca/provider/RootCAProvider.java ########## @@ -0,0 +1,572 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +package org.apache.cloudstack.ca.provider; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.StringReader; +import java.math.BigInteger; +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.KeyManagementException; +import java.security.KeyPair; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SecureRandom; +import java.security.Security; +import java.security.SignatureException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; +import java.security.cert.X509Certificate; +import java.security.spec.InvalidKeySpecException; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import javax.inject.Inject; +import javax.naming.ConfigurationException; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; + +import org.apache.cloudstack.ca.CAManager; +import org.apache.cloudstack.framework.ca.CAProvider; +import org.apache.cloudstack.framework.ca.Certificate; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.Configurable; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.utils.security.CertUtils; +import org.apache.cloudstack.utils.security.KeyStoreUtils; +import org.apache.log4j.Logger; +import org.bouncycastle.jce.PKCS10CertificationRequest; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.util.io.pem.PemObject; +import org.bouncycastle.util.io.pem.PemReader; + +import com.cloud.certificate.dao.CrlDao; +import com.cloud.utils.PropertiesUtil; +import com.cloud.utils.component.AdapterBase; +import com.cloud.utils.db.DbProperties; +import com.cloud.utils.db.GlobalLock; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.net.NetUtils; +import com.cloud.utils.nio.Link; +import com.google.common.base.Strings; + +public final class RootCAProvider extends AdapterBase implements CAProvider, Configurable { + private static final Logger LOG = Logger.getLogger(RootCAProvider.class); + + public static final Integer caValidityYears = 30; + public static final String caAlias = "root"; + public static final String managementAlias = "management"; + + private static KeyPair caKeyPair = null; + private static X509Certificate caCertificate = null; + + @Inject + private ConfigurationDao configDao; + @Inject + private CrlDao crlDao; + + //////////////////////////////////////////////////// + /////////////// Root CA Settings /////////////////// + //////////////////////////////////////////////////// + + private static ConfigKey<String> rootCAPrivateKey = new ConfigKey<>("Hidden", String.class, + "ca.plugin.root.private.key", + null, + "The ROOT CA private key.", true); + + private static ConfigKey<String> rootCAPublicKey = new ConfigKey<>("Hidden", String.class, + "ca.plugin.root.public.key", + null, + "The ROOT CA public key.", true); + + private static ConfigKey<String> rootCACertificate = new ConfigKey<>("Hidden", String.class, + "ca.plugin.root.ca.certificate", + null, + "The ROOT CA certificate.", true); + + private static ConfigKey<String> rootCAIssuerDN = new ConfigKey<>("Advanced", String.class, + "ca.plugin.root.issuer.dn", + "CN=ca.cloudstack.apache.org", + "The ROOT CA issuer distinguished name.", true); + + protected static ConfigKey<Boolean> rootCAAuthStrictness = new ConfigKey<>("Advanced", Boolean.class, + "ca.plugin.root.auth.strictness", + "false", + "Set client authentication strictness, setting to true will enforce and require client certificate for authentication in applicable CA providers.", true); + + private static ConfigKey<Boolean> rootCAAllowExpiredCert = new ConfigKey<>("Advanced", Boolean.class, + "ca.plugin.root.allow.expired.cert", + "true", + "When set to true, it will allow expired client certificate during SSL handshake.", true); + + + /////////////////////////////////////////////////////////// + /////////////// Root CA Private Methods /////////////////// + /////////////////////////////////////////////////////////// + + private Certificate generateCertificate(final List<String> domainNames, final List<String> ipAddresses, final int validityDays) throws NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, CertificateException, SignatureException, IOException, OperatorCreationException { + if (domainNames == null || domainNames.size() < 1 || Strings.isNullOrEmpty(domainNames.get(0))) { + throw new CloudRuntimeException("No domain name is specified, cannot generate certificate"); + } + final String subject = "CN=" + domainNames.get(0); + + final KeyPair keyPair = CertUtils.generateRandomKeyPair(CAManager.CertKeySize.value()); + final X509Certificate clientCertificate = CertUtils.generateV3Certificate( + caCertificate, + caKeyPair.getPrivate(), + keyPair.getPublic(), + subject, + CAManager.CertSignatureAlgorithm.value(), + validityDays, + domainNames, + ipAddresses); + return new Certificate(clientCertificate, keyPair.getPrivate(), Collections.singletonList(caCertificate)); + } + + private Certificate generateCertificateUsingCsr(final String csr, final List<String> domainNames, final List<String> ipAddresses, final int validityDays) throws NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, CertificateException, SignatureException, IOException, OperatorCreationException { + PemObject pemObject = null; + + try { + final PemReader pemReader = new PemReader(new StringReader(csr)); + pemObject = pemReader.readPemObject(); + } catch (IOException e) { + LOG.error("Failed to read provided CSR string as a PEM object", e); + } + + if (pemObject == null) { + throw new CloudRuntimeException("Unable to read/process CSR: " + csr); + } + + final PKCS10CertificationRequest request = new PKCS10CertificationRequest(pemObject.getContent()); + + final X509Certificate clientCertificate = CertUtils.generateV3Certificate( + caCertificate, caKeyPair.getPrivate(), + request.getPublicKey(), + request.getCertificationRequestInfo().getSubject().toString(), + CAManager.CertSignatureAlgorithm.value(), + validityDays, + domainNames, + ipAddresses); + return new Certificate(clientCertificate, null, Collections.singletonList(caCertificate)); + + } + + //////////////////////////////////////////////////////// + /////////////// Root CA API Handlers /////////////////// + //////////////////////////////////////////////////////// + + @Override + public boolean canProvisionCertificates() { + return true; + } + + @Override + public List<X509Certificate> getCaCertificate() { + return Collections.singletonList(caCertificate); + } + + @Override + public Certificate issueCertificate(final List<String> domainNames, final List<String> ipAddresses, final int validityDays) { + try { + return generateCertificate(domainNames, ipAddresses, validityDays); + } catch (final CertificateException | IOException | SignatureException | NoSuchAlgorithmException | NoSuchProviderException | InvalidKeyException | OperatorCreationException e) { + LOG.error("Failed to create client certificate, due to: ", e); + throw new CloudRuntimeException("Failed to generate certificate due to:" + e.getMessage()); + } + } + + @Override + public Certificate issueCertificate(final String csr, final List<String> domainNames, final List<String> ipAddresses, final int validityDays) { + try { + return generateCertificateUsingCsr(csr, domainNames, ipAddresses, validityDays); + } catch (final CertificateException | IOException | SignatureException | NoSuchAlgorithmException | NoSuchProviderException | InvalidKeyException | OperatorCreationException e) { + LOG.error("Failed to generate certificate from CSR: ", e); + throw new CloudRuntimeException("Failed to generate certificate using CSR due to:" + e.getMessage()); + } + } + + @Override + public boolean revokeCertificate(final BigInteger certSerial, final String certCn) { + return true; + } + + //////////////////////////////////////////////////////////// + /////////////// Root CA Trust Management /////////////////// + //////////////////////////////////////////////////////////// + + public static final class RootCACustomTrustManager implements X509TrustManager { + private String clientAddress = "Unknown"; + private boolean authStrictness = true; + private boolean allowExpiredCertificate = true; + private CrlDao crlDao; + private X509Certificate caCertificate; + private Map<String, X509Certificate> activeCertMap; + + public RootCACustomTrustManager(final String clientAddress, final boolean authStrictness, final boolean allowExpiredCertificate, final Map<String, X509Certificate> activeCertMap, final X509Certificate caCertificate, final CrlDao crlDao) { + if (!Strings.isNullOrEmpty(clientAddress)) { + this.clientAddress = clientAddress.replace("/", "").split(":")[0]; + } + this.authStrictness = authStrictness; + this.allowExpiredCertificate = allowExpiredCertificate; + this.activeCertMap = activeCertMap; + this.caCertificate = caCertificate; + this.crlDao = crlDao; + } + + private void printCertificateChain(final X509Certificate[] certificates, final String s) throws CertificateException { + if (certificates == null) { + return; + } + final StringBuilder builder = new StringBuilder(); + builder.append("A client/agent attempting connection from address=").append(clientAddress).append(" has presented these certificate(s):"); + int counter = 1; + for (final X509Certificate certificate: certificates) { + builder.append("\nCertificate [").append(counter++).append("] :"); + builder.append(String.format("\n Serial: %x", certificate.getSerialNumber())); + builder.append("\n Not Before:" + certificate.getNotBefore()); + builder.append("\n Not After:" + certificate.getNotAfter()); + builder.append("\n Signature Algorithm:" + certificate.getSigAlgName()); + builder.append("\n Version:" + certificate.getVersion()); + builder.append("\n Subject DN:" + certificate.getSubjectDN()); + builder.append("\n Issuer DN:" + certificate.getIssuerDN()); + builder.append("\n Alternative Names:" + certificate.getSubjectAlternativeNames()); + } + LOG.debug(builder.toString()); + } + + @Override + public void checkClientTrusted(final X509Certificate[] certificates, final String s) throws CertificateException { + if (LOG.isDebugEnabled()) { + printCertificateChain(certificates, s); + } + if (!authStrictness) { Review comment: Yep :) ---------------------------------------------------------------- This is an automated message from the Apache Git Service. To respond to the message, please log on GitHub and use the URL above to go to the specific comment. For queries about this service, please contact Infrastructure at: [email protected] With regards, Apache Git Services
