diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptActionManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptActionManager.java index cc15b2a3aa..4c5b90b774 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptActionManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptActionManager.java @@ -440,7 +440,12 @@ class GhidraScriptActionManager { } ZipEntry entry = entries.nextElement(); - monitor.setMessage("Extracting " + entry.getName() + "..."); + String name = entry.getName(); + File destination = new File(versionedExtractDir, name); + if (!FileUtilities.isPathContainedWithin(versionedExtractDir, destination)) { + throw new IOException("Zip entry escapes target directory: " + name); + } + monitor.setMessage("Extracting " + name + "..."); writeZipEntry(versionedExtractDir, entry, zipFileObject.getInputStream(entry)); monitor.incrementProgress(1); } diff --git a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/remote/GhidraServer.java b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/remote/GhidraServer.java index ece13fa85b..9c89cbc4d8 100644 --- a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/remote/GhidraServer.java +++ b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/remote/GhidraServer.java @@ -25,8 +25,10 @@ import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.rmi.server.*; import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; import java.util.*; +import javax.net.ssl.X509ExtendedKeyManager; import javax.rmi.ssl.SslRMIClientSocketFactory; import javax.rmi.ssl.SslRMIServerSocketFactory; import javax.security.auth.Subject; @@ -36,16 +38,17 @@ import javax.security.auth.login.FailedLoginException; import javax.security.auth.login.LoginException; import javax.security.auth.x500.X500Principal; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.bouncycastle.asn1.x509.GeneralName; import generic.jar.ResourceFile; import generic.random.SecureRandomFactory; import ghidra.framework.Application; import ghidra.framework.ApplicationConfiguration; import ghidra.framework.remote.*; -import ghidra.net.DefaultKeyManagerFactory; -import ghidra.net.DefaultSSLContextInitializer; +import ghidra.net.*; import ghidra.server.RepositoryManager; import ghidra.server.UserManager; import ghidra.server.security.*; @@ -473,7 +476,7 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan private static String initRemoteAccessHostname() throws UnknownHostException { String hostname = System.getProperty(RMI_SERVER_PROPERTY); - if (hostname == null) { + if (StringUtils.isBlank(hostname)) { if (bindAddress != null) { hostname = bindAddress.getHostAddress(); } @@ -482,7 +485,7 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan if (localhost.isLoopbackAddress()) { localhost = findHost(); if (localhost == null) { - log.fatal("Can't find host ip address!"); + log.fatal("Failed to identify host interface IP address"); System.exit(-1); } } @@ -719,7 +722,7 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan } } - if (rootPath == null) { + if (StringUtils.isBlank(rootPath)) { displayUsage("Repository directory must be specified!"); System.exit(-1); } @@ -802,49 +805,28 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan } String preferredKeyStore = DefaultKeyManagerFactory.getPreferredKeyStore(); - if (preferredKeyStore == null) { - + if (StringUtils.isBlank(preferredKeyStore)) { // keystore has not been identified - use self-signed certificate log.info(" Generating self-signed certificate..."); - log.info(" Subject Alternative Names:"); - log.info(" " + hostname); - - DefaultKeyManagerFactory.setDefaultIdentity(new X500Principal("CN=GhidraServer")); - DefaultKeyManagerFactory.addSubjectAlternativeName(hostname); - - // Collect alternate hostnames for inclusion in certificate - Enumeration nets = NetworkInterface.getNetworkInterfaces(); - while (nets.hasMoreElements()) { - NetworkInterface netint = nets.nextElement(); - Enumeration addrs = netint.getInetAddresses(); - while (addrs.hasMoreElements()) { - InetAddress addr = addrs.nextElement(); - altNames.add(addr.getHostAddress()); - altNames.add(addr.getHostName()); - altNames.add(addr.getCanonicalHostName()); - } - } - altNames.remove(hostname); - for (String name : altNames) { - log.info(" " + name); - DefaultKeyManagerFactory.addSubjectAlternativeName(name); - } + initSelfSignedCertificateData(hostname, altNames); } else { log.info(" Using server certificate keystore: " + preferredKeyStore); + if (!altNames.isEmpty()) { + log.warn(" -ipAlt use ignored with installed server certificate"); + } } if (!DefaultKeyManagerFactory.initialize()) { log.fatal("Failed to initialize PKI/SSL keystore"); System.exit(0); - return; } - - // RMIClassServer.startServer(classSvrPort); - - // String codeBaseProp = "http://" + - // localhost.getCanonicalHostName() + ":" + classSvrPort + "/"; - // System.setProperty(RMI_CODEBASE_PROPERTY, codeBaseProp); + + int signingKeyCount = logServerCertificates("RSA") + logServerCertificates("ECDSA"); + if (signingKeyCount == 0) { + log.fatal("Failed to locate a certificate with digital-signature usage"); + System.exit(0); + } log.info(" RMI Registry port: " + ServerPortFactory.getRMIRegistryPort()); log.info(" RMI SSL port: " + ServerPortFactory.getRMISSLPort()); @@ -852,7 +834,6 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan log.info(" Block Stream compression: " + (RemoteBlockStreamHandle.enableCompressedSerializationOutput ? "enabled" : "disabled")); -// log.info(" Class server port: " + ??); log.info(" Root: " + serverRoot.getAbsolutePath()); log.info(" Auth: " + authMode.getDescription()); if (authMode == PASSWORD_FILE_LOGIN && defaultPasswordExpiration >= 0) { @@ -920,6 +901,105 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan System.exit(-1); } } + + private static void initSelfSignedCertificateData(String preferredHostname, + Set altNames) throws SocketException { + + DefaultKeyManagerFactory.setDefaultIdentity(new X500Principal("CN=GhidraServer")); + DefaultKeyManagerFactory.addSubjectAlternativeName(preferredHostname); + + // Collect alternate hostnames for inclusion in certificate + Enumeration nets = NetworkInterface.getNetworkInterfaces(); + while (nets.hasMoreElements()) { + NetworkInterface netint = nets.nextElement(); + Enumeration addrs = netint.getInetAddresses(); + while (addrs.hasMoreElements()) { + InetAddress addr = addrs.nextElement(); + altNames.add(addr.getHostAddress()); + altNames.add(addr.getHostName()); + altNames.add(addr.getCanonicalHostName()); + } + } + altNames.remove(preferredHostname); // already added as first entry + for (String name : altNames) { + DefaultKeyManagerFactory.addSubjectAlternativeName(name); + } + } + + /** + * Log server certiifcates + * @param keyType + * @return number of certificates that support signing + */ + private static int logServerCertificates(String keyType) { + + X509ExtendedKeyManager km = DefaultKeyManagerFactory.getKeyManager(); + String[] aliases = km.getServerAliases(keyType, null); + if (aliases == null) { + return 0; + } + + String pad = " "; + Date now = new Date(); + int signingCount = 0; + + for (String alias : aliases) { + + X509Certificate[] certificateChain = km.getCertificateChain(alias); + X509Certificate x509Cert = certificateChain[certificateChain.length - 1]; + + if (x509Cert.getKeyUsage()[0]) { + ++signingCount; + } + + X500Principal subj = x509Cert.getSubjectX500Principal(); + X500Principal issuer = x509Cert.getIssuerX500Principal(); + + String label = "'" + alias + "' (" + keyType + "): "; + if (now.compareTo(x509Cert.getNotAfter()) > 0) { + log.error( + pad + label + subj + ", issued by " + issuer + + ", S/N " + x509Cert.getSerialNumber().toString(16) + ", expired " + + x509Cert.getNotAfter() + " **EXPIRED**"); + } + else { + log.info( + pad + label + subj + ", issued by " + issuer + + ", S/N " + x509Cert.getSerialNumber().toString(16) + ", expires " + + x509Cert.getNotAfter()); + } + + log.info(pad + "Key Usage: " + PKIUtils.formatKeyUsage(x509Cert)); + + boolean foundEntries = false; + try { + Collection> sanList = x509Cert.getSubjectAlternativeNames(); + if (sanList != null) { + for (List sanEntry : sanList) { + Integer type = (Integer) sanEntry.get(0); + Object value = sanEntry.get(1); + if (type == GeneralName.iPAddress || type == GeneralName.dNSName) { + if (!foundEntries) { + log.info(pad + "Subject Alternative Names:"); + } + foundEntries = true; + log.info(pad + " " + value); + } + } + } + } + catch (Exception e) { + log.fatal("Error reading certificate SANs: " + e.getMessage()); + System.exit(-1); + } + + // Generally a server signing cert needs SAN entries + if (x509Cert.getKeyUsage()[0] && !foundEntries) { + log.warn(pad + "** No Hostname or IP Address SANs are defined **"); + } + } + return signingCount; + } private static String[] getEnabledTlsProtocols() { String protocolList = System.getProperty(TLS_SERVER_PROTOCOLS_PROPERTY); diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/net/DefaultKeyManagerFactory.java b/Ghidra/Framework/Generic/src/main/java/ghidra/net/DefaultKeyManagerFactory.java index 1d270ed281..0ae8b6d284 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/net/DefaultKeyManagerFactory.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/net/DefaultKeyManagerFactory.java @@ -385,7 +385,7 @@ public class DefaultKeyManagerFactory { private synchronized boolean init(String newKeystorePath) throws CancelledException { if (wrappedKeyManager != null) { - if (StringUtils.equals(keystorePath, newKeystorePath)) { + if (Objects.equals(keystorePath, newKeystorePath)) { return true; } invalidateKey(); @@ -393,7 +393,7 @@ public class DefaultKeyManagerFactory { isSelfSigned = false; try { - if (newKeystorePath != null && newKeystorePath.length() != 0) { + if (!StringUtils.isBlank(newKeystorePath)) { Msg.info(DefaultKeyManagerFactory.class, "Using certificate keystore: " + newKeystorePath); // Password optionally specified via property @@ -411,9 +411,12 @@ public class DefaultKeyManagerFactory { defaultSubjectAlternativeNames, pwd); wrappedKeyManager = ApplicationKeyManagerFactory .getKeyManagerFromKeyStore(selfSignedKeyStore, pwd); + keystorePath = null; isSelfSigned = true; } else { + Msg.error(this, + "Failed to generate certificate without Distinguished Name (DN)"); return false; } return true; diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/net/PKIUtils.java b/Ghidra/Framework/Generic/src/main/java/ghidra/net/PKIUtils.java index d4e38868e6..551aa131fe 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/net/PKIUtils.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/net/PKIUtils.java @@ -565,4 +565,36 @@ public class PKIUtils { x509Cert.getNotAfter()); } } + + private static final String[] KEY_USAGE_NAMES = { + "DigitalSignature", + "NonRepudiation", + "KeyEncipherment", + "DataEncipherment", + "KeyAgreement", + "KeyCertSign", + "CRLSign", + "EncipherOnly", + "DecipherOnly" + }; + + /** + * {@return the key-usages as sequence of comma-separated names for a specified certificate} + * @param cert x509 certificate + */ + public static String formatKeyUsage(X509Certificate cert) { + boolean[] usage = cert.getKeyUsage(); + if (usage == null) { + return "No KeyUsage extension present"; + } + + List enabled = new ArrayList<>(); + for (int i = 0; i < usage.length && i < KEY_USAGE_NAMES.length; i++) { + if (usage[i]) { + enabled.add(KEY_USAGE_NAMES[i]); + } + } + + return String.join(", ", enabled); + } }