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