diff --git a/appserver/extras/certificate-management/pom.xml b/appserver/extras/certificate-management/pom.xml new file mode 100644 index 00000000000..a4d0e85a918 --- /dev/null +++ b/appserver/extras/certificate-management/pom.xml @@ -0,0 +1,89 @@ + + + + 4.0.0 + + fish.payara.extras + extras + 5.2020.2-SNAPSHOT + + certificate-management + glassfish-jar + Payara Certificate Management + Commands and Services for generating and managing SSL/TLS certificates + + + + + src/main/resources + + **/*.1 + + + + + + + + fish.payara.server.internal.admin + admin-cli + ${project.version} + + + fish.payara.server.internal.admin + server-mgmt + ${project.version} + + + fish.payara.server.internal.cluster + cluster-common + ${project.version} + + + fish.payara.server.internal.cluster + cluster-cli + ${project.version} + + + diff --git a/appserver/extras/certificate-management/src/main/java/fish/payara/certificate/management/CertificateManagementUtils.java b/appserver/extras/certificate-management/src/main/java/fish/payara/certificate/management/CertificateManagementUtils.java new file mode 100644 index 00000000000..1ad614e6585 --- /dev/null +++ b/appserver/extras/certificate-management/src/main/java/fish/payara/certificate/management/CertificateManagementUtils.java @@ -0,0 +1,323 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2020 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ +package fish.payara.certificate.management; + +import com.sun.enterprise.universal.xml.MiniXmlParser; +import com.sun.enterprise.universal.xml.MiniXmlParserException; + +import java.io.File; +import java.util.Map; + +import static com.sun.enterprise.util.StringUtils.ok; + +/** + * Helper methods for various Certificate Management commands. + * + * @author Andrew Pielage + */ +public class CertificateManagementUtils { + + public static final String DEFAULT_KEYSTORE = "${com.sun.aas.instanceRoot}" + + File.separator + "config" + File.separator + "keystore.jks"; + public static final String DEFAULT_TRUSTSTORE = "${com.sun.aas.instanceRoot}" + + File.separator + "config" + File.separator + "cacerts.jks"; + + /** + * Determines and returns the key store. + * + * @param parser The {@link MiniXmlParser} for extracting info from the domain.xml + * @param listener The name of the HTTP or IIOP listener to get the key store from. Can be null. + * @param instanceDir The directory of the target instance, used for relative paths + * @return The key store of the target + * @throws MiniXmlParserException If there's an issue reading the domain.xml + */ + public static File resolveKeyStore(MiniXmlParser parser, String listener, File instanceDir) + throws MiniXmlParserException { + File keystore = null; + if (listener != null) { + // Check if listener is an HTTP listener + keystore = getStoreFromHttpListeners(parser, listener, "key-store", instanceDir); + + if (keystore == null) { + // Check if listener is an IIOP listener + keystore = getStoreFromIiopListeners(parser, listener, "key-store", instanceDir); + } + } + + // Default to getting it from the JVM options if no non-default value found + if (keystore == null) { + keystore = getStoreFromJvmOptions(parser, "keyStore", instanceDir); + } + + // If it's STILL null, just go with default + if (keystore == null) { + keystore = new File(DEFAULT_KEYSTORE); + } + + return keystore; + } + + /** + * Determines and returns the trust store. + * + * @param parser The {@link MiniXmlParser} for extracting info from the domain.xml + * @param listener The name of the HTTP or IIOP listener to get the trust store from. Can be null. + * @param instanceDir The directory of the target instance, used for relative paths + * @return The trust store of the target + * @throws MiniXmlParserException If there's an issue reading the domain.xml + */ + public static File resolveTrustStore(MiniXmlParser parser, String listener, File instanceDir) + throws MiniXmlParserException { + File truststore = null; + if (listener != null) { + // Check if listener is an HTTP listener + truststore = getStoreFromHttpListeners(parser, listener, "trust-store", instanceDir); + + if (truststore == null) { + // Check if listener is an IIOP listener + truststore = getStoreFromIiopListeners(parser, listener, "trust-store", instanceDir); + } + } + + // Default to getting it from the JVM options if no non-default value found + if (truststore == null) { + truststore = getStoreFromJvmOptions(parser, "trustStore", instanceDir); + } + + // If it's STILL null, just go with default + if (truststore == null) { + truststore = new File(DEFAULT_TRUSTSTORE); + } + + return truststore; + } + + /** + * Gets the store from a target HTTP listener + * + * @param parser The {@link MiniXmlParser} for extracting info from the domain.xml + * @param listener The name of the HTTP listener to get the store from. + * @param storeAttribute The name of the store attribute to get (should be "key-store" or "trust-store") + * @param instanceDir The directory of the target instance, used for relative paths + * @return The store of the target, or null if no matching listener or no store configured + * @throws MiniXmlParserException If there's an issue reading the domain.xml + */ + private static File getStoreFromHttpListeners(MiniXmlParser parser, String listener, + String storeAttribute, File instanceDir) throws MiniXmlParserException { + for (Map listenerAttributes : parser.getProtocolAttributes()) { + if (listenerAttributes.get("name").equals(listener)) { + // Get the keystore from the listener if it has a custom one + return getStoreFromListenerAttribute(listenerAttributes.get(storeAttribute), instanceDir); + } + } + return null; + } + + /** + * Gets the store from a target IIOP listener + * + * @param parser The {@link MiniXmlParser} for extracting info from the domain.xml + * @param listener The name of the IIOP listener to get the store from. + * @param storeAttribute The name of the store attribute to get (should be "key-store" or "trust-store") + * @param instanceDir The directory of the target instance, used for relative paths + * @return The store of the target, or null if no matching listener or no store configured + * @throws MiniXmlParserException If there's an issue reading the domain.xml + */ + private static File getStoreFromIiopListeners(MiniXmlParser parser, String listener, + String storeAttribute, File instanceDir) throws MiniXmlParserException { + for (Map listenerAttributes : parser.getIiopSslAttributes()) { + if (listenerAttributes.get("id").equals(listener)) { + // Get the keystore from the listener if it has a custom one + return getStoreFromListenerAttribute(listenerAttributes.get(storeAttribute), instanceDir); + } + } + return null; + } + + /** + * Helper method that returns the store from a target listener's config attribute, + * making any relative paths absolute. + * + * @param storePath The path to the store to return as a {@link File} + * @param instanceDir The instance directory, used for making relative paths absolute + * @return The absolute path of the target store, or null if no store given + */ + private static File getStoreFromListenerAttribute(String storePath, File instanceDir) { + if (!ok(storePath)) { + return null; + } + + File store = new File(storePath); + if (!store.isAbsolute()) { + store = new File(instanceDir.getAbsolutePath() + File.separator + store.getPath()); + } + + return store; + } + + /** + * Gets the store from a target config's JVM options + * + * @param parser The {@link MiniXmlParser} for extracting info from the domain.xml + * @param storeName The JVM option name of the store (should be keyStore or trustStore) + * @param instanceDir The instance directory, used for SystemProperty substitution + * @return The absolute path of the target store + * @throws MiniXmlParserException If there's an issue reading the domain.xml + */ + private static File getStoreFromJvmOptions(MiniXmlParser parser, String storeName, File instanceDir) + throws MiniXmlParserException { + for (MiniXmlParser.JvmOption jvmOption : parser.getJvmOptions()) { + if (jvmOption.toString().startsWith("-Djavax.net.ssl." + storeName + "=")) { + return new File(jvmOption.toString().split("=")[1] + .replace("${com.sun.aas.instanceRoot}", instanceDir.getAbsolutePath())); + } + } + return null; + } + + /** + * Constructs the command to pass to keytool for creating a self-signed cert + * + * @param keystore The key store to add the certificate to + * @param password The password for the key store + * @param alias The alias of the certificate + * @param dname The distinguished name of the certificate + * @param altnames The alternative names of the certificate + * @return A String array to pass to {@link com.sun.enterprise.admin.servermgmt.KeystoreManager.KeytoolExecutor} + */ + public static String[] constructGenerateCertKeytoolCommand(File keystore, char[] password, + String alias, String dname, String[] altnames) { + String[] keytoolCmd = new String[]{"-genkeypair", "-keyalg", "RSA", "-keystore", keystore.getAbsolutePath(), + "-alias", alias, "-dname", dname, + "-validity", "365", "-keypass", new String(password), "-storepass", new String(password)}; + + if (altnames != null && altnames.length != 0) { + keytoolCmd = addSubjectAlternativeNames(keytoolCmd, altnames); + } + + return keytoolCmd; + } + + /** + * Helper method that formats the String array of alternative names into the format that the keytool expects. + * + * @param keytoolCmd The String array containing the keytool command before alternative names have been added. + * @param alternativeNames The String array containing the alternatives names + * @return A String array of the original keytool command with the alternative names added + */ + protected static String[] addSubjectAlternativeNames(String[] keytoolCmd, String[] alternativeNames) { + // Create a new array to make room for the extra commands + String[] expandedKeytoolCmd = new String[keytoolCmd.length + 2]; + System.arraycopy(keytoolCmd, 0, expandedKeytoolCmd, 0, keytoolCmd.length); + + int i = keytoolCmd.length; + expandedKeytoolCmd[i] = "-ext"; + expandedKeytoolCmd[i + 1] = "SAN="; + + for (String altName : alternativeNames) { + // Check if the altname was provided without any additional info, assuming it's DNS if so + if (!altName.contains(",") && !altName.contains(":")) { + expandedKeytoolCmd[i + 1] += "DNS:" + altName; + } else { + expandedKeytoolCmd[i + 1] += altName; + } + expandedKeytoolCmd[i + 1] += ","; + } + + // Remove trailing comma + expandedKeytoolCmd[i + 1] = expandedKeytoolCmd[i + 1].substring(0, expandedKeytoolCmd[i + 1].length() - 1); + + return expandedKeytoolCmd; + } + + /** + * Constructs the command to pass to keytool for adding the self-signed cert to the trust store + * + * @param keystore The target key store that the certificate was added to + * @param truststore The target trust store to add the certificate to + * @param keystorePassword The password for the key store + * @param truststorePassword The password for the trust store + * @param alias The alias of the certificate + * @return A String array to pass to {@link com.sun.enterprise.admin.servermgmt.KeystoreManager.KeytoolExecutor} + */ + public static String[] constructImportCertKeytoolCommand(File keystore, File truststore, char[] keystorePassword, + char[] truststorePassword, String alias) { + String[] keytoolCmd = new String[]{"-importkeystore", "-srckeystore", keystore.getAbsolutePath(), + "-destkeystore", truststore.getAbsolutePath(), "-srcalias", alias, "-destalias", alias, + "-srcstorepass", new String(keystorePassword), "-deststorepass", new String(truststorePassword), + "-srckeypass", new String(keystorePassword), "-destkeypass", new String(truststorePassword), + "-noprompt"}; + + return keytoolCmd; + } + + /** + * @param parser The {@link MiniXmlParser} for extracting info from the domain.xml + * @param listener The name of the listener to get the password from. + * @param attribute The name of the store password attribute (should be key-store-password or trust-store-password) + * @return A char array containing the password of the target listener, or null if no matches or password found + * @throws MiniXmlParserException if there's an issue reading the domain.xml + */ + public static char[] getPasswordFromListener(MiniXmlParser parser, String listener, String attribute) + throws MiniXmlParserException { + char[] password = null; + for (Map listenerAttributes : parser.getProtocolAttributes()) { + if (listenerAttributes.get("name").equals(listener)) { + // Get the keystore from the listener if it has a custom one + if (listenerAttributes.get(attribute) != null) { + password = listenerAttributes.get(attribute).toCharArray(); + } + } + } + + if (password == null || password.length == 0) { + for (Map listenerAttributes : parser.getIiopSslAttributes()) { + if (listenerAttributes.get("id").equals(listener)) { + // Get the keystore from the listener if it has a custom one + if (listenerAttributes.get(attribute) != null) { + password = listenerAttributes.get(attribute).toCharArray(); + } + } + } + } + + return password; + } +} diff --git a/appserver/extras/certificate-management/src/main/java/fish/payara/certificate/management/admin/GenerateSelfSignedCertificateCommand.java b/appserver/extras/certificate-management/src/main/java/fish/payara/certificate/management/admin/GenerateSelfSignedCertificateCommand.java new file mode 100644 index 00000000000..3a564123019 --- /dev/null +++ b/appserver/extras/certificate-management/src/main/java/fish/payara/certificate/management/admin/GenerateSelfSignedCertificateCommand.java @@ -0,0 +1,328 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2020 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ +package fish.payara.certificate.management.admin; + +import com.sun.enterprise.admin.cli.CLICommand; +import com.sun.enterprise.admin.cli.CLIConstants; +import com.sun.enterprise.admin.cli.Environment; +import com.sun.enterprise.admin.cli.ProgramOptions; +import com.sun.enterprise.admin.cli.cluster.SynchronizeInstanceCommand; +import com.sun.enterprise.admin.servermgmt.KeystoreManager; +import com.sun.enterprise.admin.servermgmt.RepositoryException; +import com.sun.enterprise.admin.servermgmt.cli.LocalDomainCommand; +import com.sun.enterprise.universal.xml.MiniXmlParser; +import com.sun.enterprise.universal.xml.MiniXmlParserException; +import com.sun.enterprise.util.SystemPropertyConstants; +import fish.payara.certificate.management.CertificateManagementUtils; +import org.glassfish.api.Param; +import org.glassfish.api.admin.CommandException; +import org.glassfish.config.support.TranslatedConfigView; +import org.glassfish.hk2.api.PerLookup; +import org.jvnet.hk2.annotations.Service; + +import java.io.File; +import java.util.logging.Logger; + +/** + * CLI command for generating self-signed certificates and placing them in an instance or listener's key + * and trust stores. + * + * @author Andrew Pielage + */ +@Service(name = "generate-self-signed-certificate") +@PerLookup +public class GenerateSelfSignedCertificateCommand extends LocalDomainCommand { + + private static final Logger logger = Logger.getLogger(CLICommand.class.getPackage().getName()); + + @Param(name = "domain_name", optional = true) + private String domainName0; + + @Param(name = "distinguishedname", alias = "dn") + private String dn; + + @Param(name = "alternativenames", optional = true, alias = "altnames", separator = ';') + private String[] altnames; + + @Param(name = "listener", optional = true) + private String listener; + + @Param(name = "target", optional = true, defaultValue = SystemPropertyConstants.DAS_SERVER_NAME) + private String target; + + @Param(name = "alias", primary = true) + private String alias; + + private File keystore; + private File truststore; + private char[] keystorePassword; + private char[] truststorePassword; + private char[] masterPassword; + + @Override + protected void validate() throws CommandException { + setDomainName(domainName0); + super.validate(); + } + + @Override + protected int executeCommand() throws CommandException { + // If we're targetting an instance that isn't the DAS, use a different command + if (target != null && !target.equals(SystemPropertyConstants.DAS_SERVER_NAME)) { + GenerateSelfSignedCertificateLocalInstanceCommand localInstanceCommand = + new GenerateSelfSignedCertificateLocalInstanceCommand(programOpts, env); + localInstanceCommand.validate(); + return localInstanceCommand.executeCommand(); + } + + // Parse the location of the key and trust stores, and the passwords required to access them + try { + MiniXmlParser parser = new MiniXmlParser(getDomainXml(), target); + keystore = CertificateManagementUtils.resolveKeyStore(parser, listener, getDomainRootDir()); + truststore = CertificateManagementUtils.resolveTrustStore(parser, listener, getDomainRootDir()); + getStorePasswords(parser, listener, getDomainRootDir()); + } catch (MiniXmlParserException miniXmlParserException) { + throw new CommandException("Error parsing domain.xml", miniXmlParserException); + } + + // Run keytool command to generate self-signed cert and place in keystore + try { + addToKeystore(); + } catch (CommandException ce) { + return CLIConstants.ERROR; + } + + try { + addToTruststore(); + } catch (CommandException ce) { + return CLIConstants.WARNING; + } + + return CLIConstants.SUCCESS; + } + + /** + * Gets the passwords for the key and trust store. + * + * @param parser The {@link MiniXmlParser} for extracting info from the domain.xml + * @param listener The name of the HTTP or IIOP listener to get the key or trust store passwords from. Can be null. + * @param serverDir The directory of the target instance, used for accessing the domain-passwords store + * @throws MiniXmlParserException If there's an issue reading the domain.xml + * @throws CommandException If there's an issue getting the master password + */ + private void getStorePasswords(MiniXmlParser parser, String listener, File serverDir) + throws MiniXmlParserException, CommandException { + if (listener != null) { + // Check if listener has a password set + keystorePassword = CertificateManagementUtils.getPasswordFromListener(parser, listener, "key-store-password"); + truststorePassword = CertificateManagementUtils.getPasswordFromListener(parser, listener, "trust-store-password"); + } + + if (keystorePassword != null && keystorePassword.length > 0) { + // Expand alias if required + if (new String(keystorePassword).startsWith("${ALIAS=")) { + JCEKSDomainPasswordAliasStore passwordAliasStore = new JCEKSDomainPasswordAliasStore( + serverDir.getPath() + File.separator + "config" + File.separator + + "domain-passwords", masterPassword()); + keystorePassword = passwordAliasStore.get( + TranslatedConfigView.getAlias(new String(keystorePassword), "ALIAS")); + } + } else { + // Default to master + keystorePassword = masterPassword(); + } + + if (truststorePassword != null && truststorePassword.length > 0) { + // Expand alias if required + if (new String(truststorePassword).startsWith("${ALIAS=")) { + JCEKSDomainPasswordAliasStore passwordAliasStore = new JCEKSDomainPasswordAliasStore( + serverDir.getPath() + File.separator + "config" + File.separator + "domain-passwords", + masterPassword()); + truststorePassword = passwordAliasStore.get( + TranslatedConfigView.getAlias(new String(truststorePassword), "ALIAS")); + } + } else { + // Default to master + truststorePassword = masterPassword(); + } + } + + /** + * Gets the master password + * + * @return The master password in a char array + * @throws CommandException If there's an issue getting the master password + */ + private char[] masterPassword() throws CommandException { + if (masterPassword == null || masterPassword.length == 0) { + masterPassword = getMasterPassword().toCharArray(); + } + + return masterPassword; + } + + /** + * Generates a self-signed certificate and adds it to the target key store + * + * @throws CommandException If there's an issue adding the certificate to the key store + */ + private void addToKeystore() throws CommandException { + // Run keytool command to generate self-signed cert + KeystoreManager.KeytoolExecutor keytoolExecutor = new KeystoreManager.KeytoolExecutor( + CertificateManagementUtils.constructGenerateCertKeytoolCommand(keystore, keystorePassword, + alias, dn, altnames), 60); + + try { + keytoolExecutor.execute("certNotCreated", keystore); + } catch (RepositoryException re) { + logger.severe(re.getCause().getMessage() + .replace("keytool error: java.lang.Exception: ", "") + .replace("keytool error: java.io.IOException: ", "")); + throw new CommandException(re); + } + } + + /** + * Adds the self-signed certificate to the target trust store + * + * @throws CommandException If there's an issue adding the certificate to the trust store + */ + private void addToTruststore() throws CommandException { + // Run keytool command to place self-signed cert in truststore + KeystoreManager.KeytoolExecutor keytoolExecutor = new KeystoreManager.KeytoolExecutor( + CertificateManagementUtils.constructImportCertKeytoolCommand(keystore, truststore, keystorePassword, + truststorePassword, alias), 60); + + try { + keytoolExecutor.execute("certNotTrusted", keystore); + } catch (RepositoryException re) { + logger.severe(re.getCause().getMessage() + .replace("keytool error: java.lang.Exception: ", "") + .replace("keytool error: java.io.IOException: ", "")); + throw new CommandException(re); + } + } + + /** + * Local instance (non-DAS) version of the parent command. Not intended for use as a standalone CLI command. + */ + private class GenerateSelfSignedCertificateLocalInstanceCommand extends SynchronizeInstanceCommand { + + public GenerateSelfSignedCertificateLocalInstanceCommand(ProgramOptions programOpts, Environment env) { + super.programOpts = programOpts; + super.env = env; + } + + @Override + protected void validate() throws CommandException { + if (ok(target)) + instanceName = target; + super.validate(); + } + + @Override + protected int executeCommand() throws CommandException { + boolean alreadySynced = false; + try { + File domainXml = getDomainXml(); + if (!domainXml.exists()) { + logger.info("No domain.xml found, syncing with the DAS..."); + synchronizeInstance(); + alreadySynced = true; + } + + MiniXmlParser parser = new MiniXmlParser(domainXml, target); + keystore = CertificateManagementUtils.resolveKeyStore(parser, listener, instanceDir); + truststore = CertificateManagementUtils.resolveTrustStore(parser, listener, instanceDir); + getStorePasswords(parser, listener, instanceDir); + } catch (MiniXmlParserException miniXmlParserException) { + throw new CommandException("Error parsing domain.xml", miniXmlParserException); + } + + // If the target is not the DAS and is configured to use the default key or trust store, sync with the + // DAS instead + boolean defaultKeystore = keystore.getAbsolutePath() + .equals(CertificateManagementUtils.DEFAULT_KEYSTORE + .replace("${com.sun.aas.instanceRoot}", instanceDir.getAbsolutePath())); + boolean defaultTruststore = truststore.getAbsolutePath() + .equals(CertificateManagementUtils.DEFAULT_TRUSTSTORE + .replace("${com.sun.aas.instanceRoot}", instanceDir.getAbsolutePath())); + + if (defaultKeystore || defaultTruststore) { + logger.warning("The target instance is using the default key or trust store, any new certificates" + + " added directly to instance stores would be lost upon next sync."); + + if (!alreadySynced) { + logger.warning("Syncing with the DAS instead of generating a new certificate"); + synchronizeInstance(); + } + + if (defaultKeystore && defaultTruststore) { + // Do nothing + } else if (defaultKeystore) { + logger.info("Please add self-signed certificate to truststore manually"); + // TO-DO + // logger.info("Look at using asadmin command 'add-to-truststore'"); + } else { + logger.info("Please add self-signed certificate to keystore manually"); + // TO-DO + // logger.info("Look at using asadmin command 'add-to-keystore'"); + } + + return CLIConstants.WARNING; + } + + // Run keytool command to generate self-signed cert and place in keystore + try { + addToKeystore(); + } catch (CommandException ce) { + return CLIConstants.ERROR; + } + + try { + addToTruststore(); + } catch (CommandException ce) { + return CLIConstants.WARNING; + } + + return CLIConstants.SUCCESS; + } + } +} \ No newline at end of file diff --git a/appserver/extras/certificate-management/src/main/java/fish/payara/certificate/management/admin/JCEKSDomainPasswordAliasStore.java b/appserver/extras/certificate-management/src/main/java/fish/payara/certificate/management/admin/JCEKSDomainPasswordAliasStore.java new file mode 100644 index 00000000000..ce1723d5f84 --- /dev/null +++ b/appserver/extras/certificate-management/src/main/java/fish/payara/certificate/management/admin/JCEKSDomainPasswordAliasStore.java @@ -0,0 +1,61 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2020 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ +package fish.payara.certificate.management.admin; + +import com.sun.enterprise.security.store.DomainScopedPasswordAliasStore; +import org.glassfish.security.services.impl.JCEKSPasswordAliasStore; + +/** + * Non-service version of {@link org.glassfish.security.services.impl.JCEKSDomainPasswordAliasStore} for use from + * the CLI where you have very limited access to HK2 services. Package private since not intended for use elsewhere - + * use the HK2 service. + * + * @author Andrew Pielage + */ +class JCEKSDomainPasswordAliasStore extends JCEKSPasswordAliasStore implements DomainScopedPasswordAliasStore { + + public JCEKSDomainPasswordAliasStore(String aliasStorePath, char[] masterPassword) { + try { + init(aliasStorePath, masterPassword); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } +} diff --git a/appserver/extras/certificate-management/src/main/manpages/fish/payara/certificate/management/admin/generate-self-signed-certificate.1 b/appserver/extras/certificate-management/src/main/manpages/fish/payara/certificate/management/admin/generate-self-signed-certificate.1 new file mode 100644 index 00000000000..6edc0afabc5 --- /dev/null +++ b/appserver/extras/certificate-management/src/main/manpages/fish/payara/certificate/management/admin/generate-self-signed-certificate.1 @@ -0,0 +1,94 @@ +generate-self-signed-certificate(1) asadmin Utility Subcommands generate-self-signed-certificate(1) + +NAME + generate-self-signed-certificate - generates a self-signed certificate using provided + distinguished name, subject alternative names, and alias, before placing it in the + target instance or listener's key and trust stores. + +SYNOPSIS + generate-self-signed-certificate [--help] + [--domain_name domain-name] [--domaindir domain-dir] + [--target instance-name] [--listener listener-name] + [alternativenames (type:value)[;type:value]*] + --distinguishedname + alias + +DESCRIPTION + The generate-self-signed-certificate subcommand generates a self- + signed certificate using the provided distinguished name, alias, and + any provided alternative names. This certificate is then placed in + the key and trust store of the target instance or listener. + + If the instance or listener is configured to use the default key and + trust store, the command will instead synchronise the instance with + the DAS (under the assumption the certificate has been added to the + default key and trust store of the DAS), since any certificates added + to the instance stores would be lost upon next synchronisation. + +OPTIONS + --help, -? + Displays the help text for the subcommand. + + --domain_name + The unique name of the domain you want to start. This operand is + optional, defaulting to domain1 if not provided. + + --domaindir + The domain root directory, which contains the directory of the + target domain. If specified, the path must be accessible in the + file system. The default location of the domain root directory + is as-install/domains. + + --target + The name of the instance to add the generated certificate to the + key and trust store of. Defaults to server (the DAS). + + --listener + The name of the HTTP or IIOP listener to add the generated + certificate to the key and trust store of. Only applicable if + the listener has a key or trust store different to that of the + instance itself. + + --alternativenames, --altnames + The subject alternative names to add to the generated certificate. + Takes the form of a semi-colon separated list of type:value. If no + type is given, it is assumed to be DNS. + +OPERANDS + --distinguishedname, --dn + The distinguished name to use when generating the certificate. + + alias + The alias to use when generating the certificate and storing it + in the key and trust stores. If the alias already exists in the + key store, the command will fail. If the alias already exists in + the trust store, this trusted certificate will be overwritten. + +EXAMPLES + Example 1, Creating a Self-Signed Certificate for the DAS + + This example creates a self-signed certificate for the DAS. + + asadmin> generate-self-signed-certificate --dn "CN=test.payara.fish" test_cert1 + + Example 2, Created a Self-Signed Certificate for a Listener + + This example creates a self-signed certificate with alternative + names for the http-listner-2 listener of an instance named Guppy1. + + asadmin> generate-self-signed-certificate --dn "CN=test.payara.fish" + --listener http-listener-2 + --alternativenames "test2.payara.fish;DNS:test3.payara.fish,IP:127.0.0.1,EMAIL:anon@payara.fish" + --target Guppy1 + test_cert2 + +EXIT STATUS + 0 + command executed successfully + + 1 + error in executing the command + + 4 + command executed successfully but with warnings + diff --git a/appserver/extras/certificate-management/src/test/java/fish/payara/certificate/management/GenerateSelfSignedCertificateCommandTest.java b/appserver/extras/certificate-management/src/test/java/fish/payara/certificate/management/GenerateSelfSignedCertificateCommandTest.java new file mode 100644 index 00000000000..b5fc81c9c59 --- /dev/null +++ b/appserver/extras/certificate-management/src/test/java/fish/payara/certificate/management/GenerateSelfSignedCertificateCommandTest.java @@ -0,0 +1,65 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2020 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ +package fish.payara.certificate.management; + +import org.junit.Assert; +import org.junit.Test; + +public class GenerateSelfSignedCertificateCommandTest { + + @Test + public void testAddSubjectAlternativeNames() { + String[] keytoolCmd = new String[]{"-genkeypair", "-keyalg", "RSA", "-keystore", "/dev/null", "-alias", + "my_test_cert", "-dname", "CN=test.payara.fish", "-validity", "365"}; + + String[] alternativeNames = new String[]{"testy.payara.fish", + "testyroo.payara.fish", + "DNS:wibbles.payara.fish,IP:127.0.0.1,EMAIL:anon@payara.fish", + "EMAIL:anon@ee.mouse,DNS:wobbles.payara.fish"}; + + String[] expandedKeytoolCommand = CertificateManagementUtils.addSubjectAlternativeNames(keytoolCmd, alternativeNames); + + Assert.assertArrayEquals(new String[]{"-genkeypair", "-keyalg", "RSA", "-keystore", "/dev/null", "-alias", + "my_test_cert", "-dname", "CN=test.payara.fish", "-validity", "365", "-ext", + "SAN=DNS:testy.payara.fish,DNS:testyroo.payara.fish,DNS:wibbles.payara.fish," + + "IP:127.0.0.1,EMAIL:anon@payara.fish,EMAIL:anon@ee.mouse,DNS:wobbles.payara.fish"}, + expandedKeytoolCommand); + } +} diff --git a/appserver/extras/pom.xml b/appserver/extras/pom.xml index 55c3c283930..5c2d944d6d3 100755 --- a/appserver/extras/pom.xml +++ b/appserver/extras/pom.xml @@ -88,6 +88,7 @@ embedded + certificate-management diff --git a/appserver/featuresets/payara-web/pom.xml b/appserver/featuresets/payara-web/pom.xml index 5f2fa387c66..67951cd6bcd 100644 --- a/appserver/featuresets/payara-web/pom.xml +++ b/appserver/featuresets/payara-web/pom.xml @@ -298,6 +298,5 @@ ${project.version} zip - diff --git a/appserver/payara-appserver-modules/pom.xml b/appserver/payara-appserver-modules/pom.xml index 2c9cdb498e4..015cc0ec2b8 100644 --- a/appserver/payara-appserver-modules/pom.xml +++ b/appserver/payara-appserver-modules/pom.xml @@ -74,20 +74,10 @@ opentracing-cdi jaxrs-client-tracing yubikey-authentication + microprofile + security-oauth2 + security-openid + healthcheck-checker - - - jdk8 - - [1.8,) - - - microprofile - security-oauth2 - security-openid - healthcheck-checker - - - diff --git a/nucleus/admin/server-mgmt/src/main/java/com/sun/enterprise/admin/servermgmt/KeystoreManager.java b/nucleus/admin/server-mgmt/src/main/java/com/sun/enterprise/admin/servermgmt/KeystoreManager.java index c1c24b7cc2e..e704cdf1a02 100644 --- a/nucleus/admin/server-mgmt/src/main/java/com/sun/enterprise/admin/servermgmt/KeystoreManager.java +++ b/nucleus/admin/server-mgmt/src/main/java/com/sun/enterprise/admin/servermgmt/KeystoreManager.java @@ -138,7 +138,7 @@ public class KeystoreManager { KEYTOOL_CMD = nonFinalKeyTool; } - protected static class KeytoolExecutor extends ProcessExecutor { + public static class KeytoolExecutor extends ProcessExecutor { public KeytoolExecutor(String[] args, long timeoutInSeconds) { super(args, timeoutInSeconds); diff --git a/nucleus/common/common-util/src/main/java/com/sun/enterprise/universal/xml/MiniXmlParser.java b/nucleus/common/common-util/src/main/java/com/sun/enterprise/universal/xml/MiniXmlParser.java index 474aabc7aeb..e39f6174480 100644 --- a/nucleus/common/common-util/src/main/java/com/sun/enterprise/universal/xml/MiniXmlParser.java +++ b/nucleus/common/common-util/src/main/java/com/sun/enterprise/universal/xml/MiniXmlParser.java @@ -168,6 +168,20 @@ public List getAdminAddresses() { return adminAddresses; } + public List> getProtocolAttributes() throws MiniXmlParserException { + if (!valid) { + throw new MiniXmlParserException(strings.get(INVALID)); + } + return protocolAttributes; + } + + public List> getIiopSslAttributes() throws MiniXmlParserException { + if (!valid) { + throw new MiniXmlParserException(strings.get(INVALID)); + } + return iiopSslAttributes; + } + /** * @deprecated use {@link #setupConfigDir(java.io.File)} instead */ @@ -534,6 +548,9 @@ private void parseConfig() throws XMLStreamException, EndDocumentException { case "security-service": populateAdminRealmProperties(); break; + case "iiop-service": + parseIiopService(); + break; default: skipTree(name); break; @@ -581,6 +598,52 @@ private void parseNetworkConfig() } } + /** + * Parses the IIOP service for the SSL enabled IIOP listeners + * @throws XMLStreamException + * @throws EndDocumentException + */ + private void parseIiopService() throws XMLStreamException, EndDocumentException { + // Cursor --> + while (true) { + int event = next(); + // Return when we get to the + if (event == END_ELEMENT) { + if ("iiop-service".equals(parser.getLocalName())) { + return; + } + } else if (event == START_ELEMENT) { + String name = parser.getLocalName(); + if (null == name) { + skipTree(name); + } else switch (name) { + // Cursor --> START_ELEMENT of + case "iiop-listener": + // Get attributes + Map iiopAttributes = parseAttributes(); + + // Skip to ssl config if present + while (true) { + skipToButNotPast("iiop-listener", "ssl"); + name = parser.getLocalName(); + if ("ssl".equals(name)) { + iiopAttributes.putAll(parseAttributes()); + // Only store attributes of SSL enabled IIOP listeners + iiopSslAttributes.add(iiopAttributes); + } else if ("iiop-listener".equals(name)) { + break; + } + } + break; + default: + skipTree(name); + break; + } + } + } + + } + private void parseSysPropsFromServer() throws XMLStreamException, EndDocumentException { // cursor --> // these are the system-properties that OVERRIDE the ones in the @@ -927,10 +990,12 @@ private void parseListeners() throws XMLStreamException, EndDocumentException { private void parseProtocols() throws XMLStreamException, EndDocumentException { // cursor --> START_ELEMENT of protocols while (true) { - skipToButNotPast("protocols", "protocol"); + skipToButNotPast("protocols", "protocol", "ssl"); final String name = parser.getLocalName(); if ("protocol".equals(name)) { protocolAttributes.add(parseAttributes()); + } else if ("ssl".equals(name)) { + protocolAttributes.get(protocolAttributes.size() - 1).putAll(parseAttributes()); } else if ("protocols".equals(name)) { break; } @@ -1131,20 +1196,21 @@ private static class EndDocumentException extends Exception { private String serverName; private String configRef; private List jvmOptions = new ArrayList<>(); - private List profilerJvmOptions = new ArrayList(); + private List profilerJvmOptions = new ArrayList<>(); private Map javaConfig; private Map profilerConfig = Collections.emptyMap(); - private Map profilerSysProps = new HashMap(); + private Map profilerSysProps = new HashMap<>(); private boolean valid = false; - private List adminAddresses = new ArrayList(); + private List adminAddresses = new ArrayList<>(); private String domainName; private static final LocalStringsImpl strings = new LocalStringsImpl(MiniXmlParser.class); private boolean monitoringEnabled = true; // Issue 12762 Absent element means monitoring-enabled=true by default private String adminRealm = null; private Map adminRealmProperties = null; - private List> vsAttributes = new ArrayList>(); - private List> listenerAttributes = new ArrayList>(); - private List> protocolAttributes = new ArrayList>(); + private List> vsAttributes = new ArrayList<>(); + private List> listenerAttributes = new ArrayList<>(); + private List> protocolAttributes = new ArrayList<>(); + private List> iiopSslAttributes = new ArrayList<>(); private boolean sawNetworkConfig; private boolean sawDefaultConfig; private boolean sawConfig;