mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-29 07:28:54 +08:00
Merge remote-tracking branch
'origin/GP-3050-2935-ghidra1_ServerAddressAndConnectTimeout--SQUASHED' (Closes #4924, Closes #4928)
This commit is contained in:
@@ -32,7 +32,6 @@ import ghidra.framework.main.FrontEndTool;
|
|||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.*;
|
||||||
import ghidra.framework.plugintool.dialog.ExtensionUtils;
|
import ghidra.framework.plugintool.dialog.ExtensionUtils;
|
||||||
import ghidra.framework.project.DefaultProjectManager;
|
import ghidra.framework.project.DefaultProjectManager;
|
||||||
import ghidra.framework.remote.InetNameLookup;
|
|
||||||
import ghidra.framework.store.LockException;
|
import ghidra.framework.store.LockException;
|
||||||
import ghidra.program.database.ProgramDB;
|
import ghidra.program.database.ProgramDB;
|
||||||
import ghidra.util.*;
|
import ghidra.util.*;
|
||||||
@@ -94,9 +93,6 @@ public class GhidraRun implements GhidraLaunchable {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Automatically disable reverse name lookup if failure occurs
|
|
||||||
InetNameLookup.setDisableOnFailure(true);
|
|
||||||
|
|
||||||
// Start main thread in GhidraThreadGroup
|
// Start main thread in GhidraThreadGroup
|
||||||
Thread mainThread = new Thread(new GhidraThreadGroup(), mainTask, "Ghidra");
|
Thread mainThread = new Thread(new GhidraThreadGroup(), mainTask, "Ghidra");
|
||||||
mainThread.start();
|
mainThread.start();
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ import java.util.*;
|
|||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
import ghidra.framework.remote.InetNameLookup;
|
|
||||||
import ghidra.framework.store.local.IndexedLocalFileSystem;
|
import ghidra.framework.store.local.IndexedLocalFileSystem;
|
||||||
import ghidra.framework.store.local.LocalFileSystem;
|
import ghidra.framework.store.local.LocalFileSystem;
|
||||||
|
import ghidra.server.remote.InetNameLookup;
|
||||||
import ghidra.server.remote.RepositoryServerHandleImpl;
|
import ghidra.server.remote.RepositoryServerHandleImpl;
|
||||||
import ghidra.util.NamingUtilities;
|
import ghidra.util.NamingUtilities;
|
||||||
import ghidra.util.StringUtilities;
|
import ghidra.util.StringUtilities;
|
||||||
|
|||||||
@@ -751,6 +751,7 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
|
|||||||
// keystore has not been identified - use self-signed certificate
|
// keystore has not been identified - use self-signed certificate
|
||||||
ApplicationKeyManagerFactory.setDefaultIdentity(
|
ApplicationKeyManagerFactory.setDefaultIdentity(
|
||||||
new X500Principal("CN=GhidraServer"));
|
new X500Principal("CN=GhidraServer"));
|
||||||
|
ApplicationKeyManagerFactory.addSubjectAlternativeName(hostname);
|
||||||
}
|
}
|
||||||
if (!ApplicationKeyManagerFactory.initialize()) {
|
if (!ApplicationKeyManagerFactory.initialize()) {
|
||||||
log.fatal("Failed to initialize PKI/SSL keystore");
|
log.fatal("Failed to initialize PKI/SSL keystore");
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package ghidra.framework.remote;
|
package ghidra.server.remote;
|
||||||
|
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
@@ -18,7 +18,6 @@ package ghidra.framework.client;
|
|||||||
import java.awt.Component;
|
import java.awt.Component;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.Authenticator;
|
import java.net.Authenticator;
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.rmi.*;
|
import java.rmi.*;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@@ -50,7 +49,7 @@ public class ClientUtil {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Set client authenticator
|
* Set client authenticator
|
||||||
* @param authenticator
|
* @param authenticator client authenticator instance
|
||||||
*/
|
*/
|
||||||
public static synchronized void setClientAuthenticator(ClientAuthenticator authenticator) {
|
public static synchronized void setClientAuthenticator(ClientAuthenticator authenticator) {
|
||||||
clientAuthenticator = authenticator;
|
clientAuthenticator = authenticator;
|
||||||
@@ -106,14 +105,6 @@ public class ClientUtil {
|
|||||||
// ensure that default callback is setup if possible
|
// ensure that default callback is setup if possible
|
||||||
getClientAuthenticator();
|
getClientAuthenticator();
|
||||||
|
|
||||||
host = host.trim().toLowerCase();
|
|
||||||
try {
|
|
||||||
host = InetNameLookup.getCanonicalHostName(host);
|
|
||||||
}
|
|
||||||
catch (UnknownHostException e) {
|
|
||||||
Msg.warn(ClientUtil.class, "Failed to resolve hostname for " + host);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (port <= 0) {
|
if (port <= 0) {
|
||||||
port = GhidraServerHandle.DEFAULT_PORT;
|
port = GhidraServerHandle.DEFAULT_PORT;
|
||||||
}
|
}
|
||||||
@@ -145,22 +136,14 @@ public class ClientUtil {
|
|||||||
* Eliminate the specified repository server from the connection cache
|
* Eliminate the specified repository server from the connection cache
|
||||||
* @param host host name or IP address
|
* @param host host name or IP address
|
||||||
* @param port port (0: use default port)
|
* @param port port (0: use default port)
|
||||||
* @throws IOException
|
|
||||||
*/
|
*/
|
||||||
public static void clearRepositoryAdapter(String host, int port) throws IOException {
|
public static void clearRepositoryAdapter(String host, int port) {
|
||||||
host = host.trim().toLowerCase();
|
|
||||||
String hostAddr = host;
|
|
||||||
try {
|
|
||||||
hostAddr = InetNameLookup.getCanonicalHostName(host);
|
|
||||||
}
|
|
||||||
catch (UnknownHostException e) {
|
|
||||||
throw new IOException("Repository server lookup failed: " + host);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (port == 0) {
|
if (port == 0) {
|
||||||
port = GhidraServerHandle.DEFAULT_PORT;
|
port = GhidraServerHandle.DEFAULT_PORT;
|
||||||
}
|
}
|
||||||
ServerInfo server = new ServerInfo(hostAddr, port);
|
|
||||||
|
ServerInfo server = new ServerInfo(host, port);
|
||||||
RepositoryServerAdapter serverAdapter = serverHandles.remove(server);
|
RepositoryServerAdapter serverAdapter = serverHandles.remove(server);
|
||||||
if (serverAdapter != null) {
|
if (serverAdapter != null) {
|
||||||
serverAdapter.disconnect();
|
serverAdapter.disconnect();
|
||||||
@@ -170,6 +153,7 @@ public class ClientUtil {
|
|||||||
/**
|
/**
|
||||||
* Returns default user login name. Actual user name used by repository
|
* Returns default user login name. Actual user name used by repository
|
||||||
* should be obtained from RepositoryServerAdapter.getUser
|
* should be obtained from RepositoryServerAdapter.getUser
|
||||||
|
* @return default user name
|
||||||
*/
|
*/
|
||||||
public static String getUserName() {
|
public static String getUserName() {
|
||||||
String name = SystemUtilities.getUserName();
|
String name = SystemUtilities.getUserName();
|
||||||
@@ -372,7 +356,7 @@ public class ClientUtil {
|
|||||||
* @param parent dialog parent
|
* @param parent dialog parent
|
||||||
* @param handle server handle
|
* @param handle server handle
|
||||||
* @param serverInfo server information
|
* @param serverInfo server information
|
||||||
* @throws IOException
|
* @throws IOException if error occurs while updating password
|
||||||
*/
|
*/
|
||||||
public static void changePassword(Component parent, RepositoryServerHandle handle,
|
public static void changePassword(Component parent, RepositoryServerHandle handle,
|
||||||
String serverInfo) throws IOException {
|
String serverInfo) throws IOException {
|
||||||
@@ -451,7 +435,7 @@ public class ClientUtil {
|
|||||||
sigCb.getRecognizedAuthorities(), sigCb.getToken());
|
sigCb.getRecognizedAuthorities(), sigCb.getToken());
|
||||||
sigCb.sign(signedToken.certChain, signedToken.signature);
|
sigCb.sign(signedToken.certChain, signedToken.signature);
|
||||||
Msg.info(ClientUtil.class, "PKI Authenticating to " + serverName + " as user '" +
|
Msg.info(ClientUtil.class, "PKI Authenticating to " + serverName + " as user '" +
|
||||||
signedToken.certChain[0].getSubjectDN() + "'");
|
signedToken.certChain[0].getSubjectX500Principal() + "'");
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
String msg = e.getMessage();
|
String msg = e.getMessage();
|
||||||
|
|||||||
+57
-36
@@ -18,10 +18,12 @@ package ghidra.framework.client;
|
|||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
|
import java.net.SocketAddress;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.rmi.*;
|
import java.rmi.*;
|
||||||
import java.rmi.registry.LocateRegistry;
|
import java.rmi.registry.LocateRegistry;
|
||||||
import java.rmi.registry.Registry;
|
import java.rmi.registry.Registry;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
|
||||||
import javax.net.ssl.SSLHandshakeException;
|
import javax.net.ssl.SSLHandshakeException;
|
||||||
@@ -46,6 +48,8 @@ import ghidra.util.task.*;
|
|||||||
*/
|
*/
|
||||||
class ServerConnectTask extends Task {
|
class ServerConnectTask extends Task {
|
||||||
|
|
||||||
|
private static final int LIVENESS_CHECK_TIMEOUT_MS = 3000;
|
||||||
|
|
||||||
private ServerInfo server;
|
private ServerInfo server;
|
||||||
//private String defaultUserID;
|
//private String defaultUserID;
|
||||||
private boolean allowLoginRetry;
|
private boolean allowLoginRetry;
|
||||||
@@ -98,6 +102,7 @@ class ServerConnectTask extends Task {
|
|||||||
* if handle is null after running task. If both the exception
|
* if handle is null after running task. If both the exception
|
||||||
* and handle are null, it implies the connection attempt was cancelled
|
* and handle are null, it implies the connection attempt was cancelled
|
||||||
* by the user.
|
* by the user.
|
||||||
|
* @return exception which occured during a failed connection attempt, or null
|
||||||
*/
|
*/
|
||||||
Exception getException() {
|
Exception getException() {
|
||||||
return exc;
|
return exc;
|
||||||
@@ -123,16 +128,6 @@ class ServerConnectTask extends Task {
|
|||||||
return subj;
|
return subj;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getPreferredHostname(String name) {
|
|
||||||
try {
|
|
||||||
return InetNameLookup.getCanonicalHostName(name);
|
|
||||||
}
|
|
||||||
catch (UnknownHostException e) {
|
|
||||||
Msg.warn(ServerConnectTask.class, "Failed to resolve hostname for " + name);
|
|
||||||
}
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isSSLHandshakeCancelled(SSLHandshakeException e) throws IOException {
|
private static boolean isSSLHandshakeCancelled(SSLHandshakeException e) throws IOException {
|
||||||
if (e.getMessage().indexOf("bad_certificate") > 0) {
|
if (e.getMessage().indexOf("bad_certificate") > 0) {
|
||||||
if (ApplicationKeyManagerFactory.getPreferredKeyStore() == null) {
|
if (ApplicationKeyManagerFactory.getPreferredKeyStore() == null) {
|
||||||
@@ -154,8 +149,8 @@ class ServerConnectTask extends Task {
|
|||||||
* @param server server information
|
* @param server server information
|
||||||
* @param monitor cancellable monitor
|
* @param monitor cancellable monitor
|
||||||
* @return Ghidra Server Handle object
|
* @return Ghidra Server Handle object
|
||||||
* @throws IOException
|
* @throws IOException if a connection error occurs
|
||||||
* @throws CancelledException
|
* @throws CancelledException if connection attempt was cancelled
|
||||||
*/
|
*/
|
||||||
public static GhidraServerHandle getGhidraServerHandle(ServerInfo server, TaskMonitor monitor)
|
public static GhidraServerHandle getGhidraServerHandle(ServerInfo server, TaskMonitor monitor)
|
||||||
throws IOException, CancelledException {
|
throws IOException, CancelledException {
|
||||||
@@ -163,6 +158,7 @@ class ServerConnectTask extends Task {
|
|||||||
GhidraServerHandle gsh = null;
|
GhidraServerHandle gsh = null;
|
||||||
boolean canCancel = monitor.isCancelEnabled(); // original state
|
boolean canCancel = monitor.isCancelEnabled(); // original state
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// Test SSL Handshake to ensure that user is able to decrypt keystore.
|
// Test SSL Handshake to ensure that user is able to decrypt keystore.
|
||||||
// This is intended to work around an RMI issue where a continuous
|
// This is intended to work around an RMI issue where a continuous
|
||||||
// retry condition can occur when a user cancels the password entry
|
// retry condition can occur when a user cancels the password entry
|
||||||
@@ -172,17 +168,10 @@ class ServerConnectTask extends Task {
|
|||||||
monitor.setCancelEnabled(false);
|
monitor.setCancelEnabled(false);
|
||||||
monitor.setMessage("Connecting...");
|
monitor.setMessage("Connecting...");
|
||||||
|
|
||||||
Registry reg;
|
Registry reg =
|
||||||
try {
|
LocateRegistry.getRegistry(server.getServerName(), server.getPortNumber(),
|
||||||
// attempt to connect with older Ghidra Server registry without using SSL/TLS
|
|
||||||
reg = LocateRegistry.getRegistry(server.getServerName(), server.getPortNumber());
|
|
||||||
checkServerBindNames(reg);
|
|
||||||
}
|
|
||||||
catch (IOException e) {
|
|
||||||
reg = LocateRegistry.getRegistry(server.getServerName(), server.getPortNumber(),
|
|
||||||
new SslRMIClientSocketFactory());
|
new SslRMIClientSocketFactory());
|
||||||
checkServerBindNames(reg);
|
checkServerBindNames(reg);
|
||||||
}
|
|
||||||
|
|
||||||
gsh = (GhidraServerHandle) reg.lookup(GhidraServerHandle.BIND_NAME);
|
gsh = (GhidraServerHandle) reg.lookup(GhidraServerHandle.BIND_NAME);
|
||||||
gsh.checkCompatibility(GhidraServerHandle.INTERFACE_VERSION);
|
gsh.checkCompatibility(GhidraServerHandle.INTERFACE_VERSION);
|
||||||
@@ -241,20 +230,17 @@ class ServerConnectTask extends Task {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts server connection and completes any necessary authentication.
|
* Attempts server connection and completes any necessary authentication.
|
||||||
* @param defaultUserID
|
* @param defaultUserID default user ID (actual ID used established during authentication)
|
||||||
* @param monitor task monitor for connection cancellation
|
* @param monitor task monitor for connection cancellation
|
||||||
* @return server handle or null if authentication or connection attempt was cancelled by user
|
* @return server handle or null if authentication or connection attempt was cancelled by user
|
||||||
* @throws IOException
|
* @throws IOException if server connection fails
|
||||||
* @throws LoginException
|
* @throws LoginException login failure
|
||||||
*/
|
*/
|
||||||
private RemoteRepositoryServerHandle getRepositoryServerHandle(String defaultUserID,
|
private RemoteRepositoryServerHandle getRepositoryServerHandle(String defaultUserID,
|
||||||
TaskMonitor monitor)
|
TaskMonitor monitor)
|
||||||
throws IOException, LoginException, CancelledException {
|
throws IOException, LoginException, CancelledException {
|
||||||
|
|
||||||
GhidraServerHandle gsh = getGhidraServerHandle(server, monitor);
|
GhidraServerHandle gsh = getGhidraServerHandle(server, monitor);
|
||||||
if (gsh == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Callback[] callbacks = null;
|
Callback[] callbacks = null;
|
||||||
try {
|
try {
|
||||||
@@ -275,7 +261,6 @@ class ServerConnectTask extends Task {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String serverName = getPreferredHostname(server.getServerName());
|
|
||||||
AnonymousCallback onlyAnonymousCb = null;
|
AnonymousCallback onlyAnonymousCb = null;
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
@@ -298,8 +283,8 @@ class ServerConnectTask extends Task {
|
|||||||
// SSH option only available in conjunction with password
|
// SSH option only available in conjunction with password
|
||||||
// based authentication which will be used if SSH attempt fails
|
// based authentication which will be used if SSH attempt fails
|
||||||
hasSSHSignatureCallback = false; // only try SSH once
|
hasSSHSignatureCallback = false; // only try SSH once
|
||||||
ClientUtil.processSSHSignatureCallback(callbacks, serverName,
|
ClientUtil.processSSHSignatureCallback(callbacks,
|
||||||
defaultUserID);
|
server.getServerName(), defaultUserID);
|
||||||
}
|
}
|
||||||
else if (pkiSignatureCb != null) {
|
else if (pkiSignatureCb != null) {
|
||||||
// when using PKI - no other authentication callback will be used
|
// when using PKI - no other authentication callback will be used
|
||||||
@@ -317,14 +302,15 @@ class ServerConnectTask extends Task {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loopOK = false; // only try once
|
loopOK = false; // only try once
|
||||||
ClientUtil.processSignatureCallback(serverName, pkiSignatureCb);
|
ClientUtil.processSignatureCallback(server.getServerName(),
|
||||||
|
pkiSignatureCb);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// assume all other callback scenarios are password based
|
// assume all other callback scenarios are password based
|
||||||
// anonymous option must be explicitly chosen over username/password
|
// anonymous option must be explicitly chosen over username/password
|
||||||
// when processing password callback
|
// when processing password callback
|
||||||
if (!ClientUtil.processPasswordCallbacks(callbacks, serverName,
|
if (!ClientUtil.processPasswordCallbacks(callbacks,
|
||||||
defaultUserID, loginError)) {
|
server.getServerName(), defaultUserID, loginError)) {
|
||||||
return null; // Cancelled by user
|
return null; // Cancelled by user
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -336,7 +322,7 @@ class ServerConnectTask extends Task {
|
|||||||
gsh.getRepositoryServer(getLocalUserSubject(), callbacks);
|
gsh.getRepositoryServer(getLocalUserSubject(), callbacks);
|
||||||
if (rsh.isReadOnly()) {
|
if (rsh.isReadOnly()) {
|
||||||
Msg.showInfo(this, null, "Anonymous Server Login",
|
Msg.showInfo(this, null, "Anonymous Server Login",
|
||||||
"You have been logged-in anonymously to " + serverName +
|
"You have been logged-in anonymously to " + server.getServerName() +
|
||||||
"\nRead-only permission is granted to repositories which allow anonymous access");
|
"\nRead-only permission is granted to repositories which allow anonymous access");
|
||||||
}
|
}
|
||||||
return rsh;
|
return rsh;
|
||||||
@@ -374,7 +360,30 @@ class ServerConnectTask extends Task {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void testServerSSLConnection(ServerInfo server, TaskMonitor monitor)
|
/**
|
||||||
|
* Socket implementation with very short connect timeout
|
||||||
|
*/
|
||||||
|
private static class FastConnectionFailSocket extends Socket {
|
||||||
|
FastConnectionFailSocket(String host, int port) throws UnknownHostException, IOException {
|
||||||
|
super(host, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void connect(SocketAddress endpoint) throws IOException {
|
||||||
|
connect(endpoint, LIVENESS_CHECK_TIMEOUT_MS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiate an SSLSocket connection in order to ensure that any neccesary client/server
|
||||||
|
* certificate validation is performed.
|
||||||
|
* @param server server to which connection should be verified. For the Ghidra Server
|
||||||
|
* this should correspond to the RMI Registry port {@link GhidraServerHandle#DEFAULT_PORT}.
|
||||||
|
* @param monitor connection task monitor
|
||||||
|
* @return certificate chain of server
|
||||||
|
* @throws IOException if connection failure occurs
|
||||||
|
* @throws CancelledException if connection attempt is cancelled
|
||||||
|
*/
|
||||||
|
private static Certificate[] testServerSSLConnection(ServerInfo server, TaskMonitor monitor)
|
||||||
throws IOException, CancelledException {
|
throws IOException, CancelledException {
|
||||||
|
|
||||||
RMIServerPortFactory portFactory = new RMIServerPortFactory(server.getPortNumber());
|
RMIServerPortFactory portFactory = new RMIServerPortFactory(server.getPortNumber());
|
||||||
@@ -384,7 +393,18 @@ class ServerConnectTask extends Task {
|
|||||||
|
|
||||||
monitor.setCancelEnabled(true);
|
monitor.setCancelEnabled(true);
|
||||||
monitor.setMessage("Checking Server Liveness...");
|
monitor.setMessage("Checking Server Liveness...");
|
||||||
|
|
||||||
|
// Perform simple socket test connection with short timeout to verify connectivity.
|
||||||
|
try (Socket socket = new FastConnectionFailSocket(serverName, sslRmiPort);
|
||||||
|
ConnectCancelledListener cancelListener =
|
||||||
|
new ConnectCancelledListener(monitor, () -> forceClose(socket))) {
|
||||||
|
// do nothing - connect occurs during instantiation
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
monitor.checkCanceled(); // circumvent any IOException which may have occured
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform secure socket test connection to prime keystore use without RMI involvement
|
||||||
try (SSLSocket socket = (SSLSocket) factory.createSocket(serverName, sslRmiPort);
|
try (SSLSocket socket = (SSLSocket) factory.createSocket(serverName, sslRmiPort);
|
||||||
ConnectCancelledListener cancelListener =
|
ConnectCancelledListener cancelListener =
|
||||||
new ConnectCancelledListener(monitor, () -> forceClose(socket))) {
|
new ConnectCancelledListener(monitor, () -> forceClose(socket))) {
|
||||||
@@ -392,6 +412,7 @@ class ServerConnectTask extends Task {
|
|||||||
// which will give user ability to cancel without involving RMI which
|
// which will give user ability to cancel without involving RMI which
|
||||||
// will avoid RMI reconnect attempts
|
// will avoid RMI reconnect attempts
|
||||||
socket.startHandshake();
|
socket.startHandshake();
|
||||||
|
return socket.getSession().getPeerCertificates();
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
monitor.checkCanceled(); // circumvent any IOException which may have occured
|
monitor.checkCanceled(); // circumvent any IOException which may have occured
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
/* ###
|
/* ###
|
||||||
* IP: GHIDRA
|
* IP: GHIDRA
|
||||||
* REVIEWED: YES
|
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -38,7 +37,8 @@ public class ServerInfo implements Serializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the server name.
|
* Get the server hostname or IP address as originally specified.
|
||||||
|
* @return hostname or IP address as originally specified
|
||||||
*/
|
*/
|
||||||
public String getServerName() {
|
public String getServerName() {
|
||||||
return host;
|
return host;
|
||||||
@@ -46,6 +46,7 @@ public class ServerInfo implements Serializable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the port number.
|
* Get the port number.
|
||||||
|
* @return port number
|
||||||
*/
|
*/
|
||||||
public int getPortNumber() {
|
public int getPortNumber() {
|
||||||
return portNumber;
|
return portNumber;
|
||||||
|
|||||||
+37
-4
@@ -21,7 +21,7 @@ import java.net.Socket;
|
|||||||
import java.security.*;
|
import java.security.*;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.Arrays;
|
import java.util.*;
|
||||||
|
|
||||||
import javax.net.ssl.*;
|
import javax.net.ssl.*;
|
||||||
import javax.security.auth.x500.X500Principal;
|
import javax.security.auth.x500.X500Principal;
|
||||||
@@ -62,6 +62,7 @@ public class ApplicationKeyManagerFactory {
|
|||||||
|
|
||||||
private static KeyStorePasswordProvider customPasswordProvider;
|
private static KeyStorePasswordProvider customPasswordProvider;
|
||||||
private static X500Principal defaultIdentity;
|
private static X500Principal defaultIdentity;
|
||||||
|
private static List<String> subjectAlternativeNames;
|
||||||
|
|
||||||
private static ApplicationKeyManagerFactory instance;
|
private static ApplicationKeyManagerFactory instance;
|
||||||
|
|
||||||
@@ -182,16 +183,48 @@ public class ApplicationKeyManagerFactory {
|
|||||||
/**
|
/**
|
||||||
* Set the default self-signed principal identity to be used during initialization
|
* Set the default self-signed principal identity to be used during initialization
|
||||||
* if no keystore defined. Current application key manager will be invalidated.
|
* if no keystore defined. Current application key manager will be invalidated.
|
||||||
* @param identity if not null and a KeyStore path has not be set, this
|
|
||||||
* identity will be used to generate a self-signed certificate and private key
|
|
||||||
* (NOTE: this is intended for server use only when client will not be performing
|
* (NOTE: this is intended for server use only when client will not be performing
|
||||||
* CA validation).
|
* CA validation).
|
||||||
|
* @param identity if not null and a KeyStore path has not be set, this
|
||||||
|
* identity will be used to generate a self-signed certificate and private key
|
||||||
*/
|
*/
|
||||||
public synchronized static void setDefaultIdentity(X500Principal identity) {
|
public synchronized static void setDefaultIdentity(X500Principal identity) {
|
||||||
defaultIdentity = identity;
|
defaultIdentity = identity;
|
||||||
getKeyManagerWrapper().invalidateKey();
|
getKeyManagerWrapper().invalidateKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the optional self-signed subject alternative name to be used during initialization
|
||||||
|
* if no keystore defined. Current application key manager will be invalidated.
|
||||||
|
* (NOTE: this is intended for server use only when client will not be performing
|
||||||
|
* CA validation).
|
||||||
|
* @param subjectAltName name to be added to the current list of alternative subject names.
|
||||||
|
* A null value will clear all names currently set.
|
||||||
|
* name will be used to generate a self-signed certificate and private key
|
||||||
|
*/
|
||||||
|
public synchronized static void addSubjectAlternativeName(String subjectAltName) {
|
||||||
|
if (subjectAltName == null) {
|
||||||
|
subjectAlternativeNames = null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (subjectAlternativeNames == null) {
|
||||||
|
subjectAlternativeNames = new ArrayList<>();
|
||||||
|
}
|
||||||
|
subjectAlternativeNames.add(subjectAltName);
|
||||||
|
}
|
||||||
|
getKeyManagerWrapper().invalidateKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current list of subject alternative names to be used for a self-signed certificate
|
||||||
|
* if no keystore defined.
|
||||||
|
* @return list of subject alternative names to be used for a self-signed certificate
|
||||||
|
* if no keystore defined.
|
||||||
|
*/
|
||||||
|
public synchronized static List<String> getSubjectAlternativeName() {
|
||||||
|
return Collections.unmodifiableList(subjectAlternativeNames);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize key manager if needed. Doing this explicitly independent of an SSL connection
|
* Initialize key manager if needed. Doing this explicitly independent of an SSL connection
|
||||||
* allows application to bail before initiating connection. This will get handshake failure
|
* allows application to bail before initiating connection. This will get handshake failure
|
||||||
@@ -548,7 +581,7 @@ public class ApplicationKeyManagerFactory {
|
|||||||
KeyStore selfSignedKeyStore =
|
KeyStore selfSignedKeyStore =
|
||||||
ApplicationKeyManagerUtils.createKeyStore("defaultSigKey",
|
ApplicationKeyManagerUtils.createKeyStore("defaultSigKey",
|
||||||
defaultIdentity.getName(), SELF_SIGNED_DURATION_DAYS, null, null, "JKS",
|
defaultIdentity.getName(), SELF_SIGNED_DURATION_DAYS, null, null, "JKS",
|
||||||
pwd);
|
subjectAlternativeNames, pwd);
|
||||||
keystoreData = new ProtectedKeyStoreData(selfSignedKeyStore, pwd);
|
keystoreData = new ProtectedKeyStoreData(selfSignedKeyStore, pwd);
|
||||||
isSelfSigned = true;
|
isSelfSigned = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
|
|||||||
import org.bouncycastle.operator.ContentSigner;
|
import org.bouncycastle.operator.ContentSigner;
|
||||||
import org.bouncycastle.operator.OperatorException;
|
import org.bouncycastle.operator.OperatorException;
|
||||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
||||||
|
import org.bouncycastle.util.IPAddress;
|
||||||
|
|
||||||
import generic.random.SecureRandomFactory;
|
import generic.random.SecureRandomFactory;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
@@ -301,15 +302,19 @@ public class ApplicationKeyManagerUtils {
|
|||||||
* @param alias entry alias with keystore
|
* @param alias entry alias with keystore
|
||||||
* @param dn distinguished name (e.g., "CN=Ghidra Test, O=Ghidra, OU=Test, C=US" )
|
* @param dn distinguished name (e.g., "CN=Ghidra Test, O=Ghidra, OU=Test, C=US" )
|
||||||
* @param durationDays number of days which generated certificate should remain valid
|
* @param durationDays number of days which generated certificate should remain valid
|
||||||
* @param caEntry optional CA private key entry. If null, a self-signed CA certificate will be generated.
|
* @param caEntry optional CA private key entry. If null, a self-signed CA certificate will be
|
||||||
|
* generated.
|
||||||
* @param keyFile optional file to load/store resulting {@link KeyStore} (may be null)
|
* @param keyFile optional file to load/store resulting {@link KeyStore} (may be null)
|
||||||
* @param keystoreType support keystore type (e.g., "JKS", "PKCS12")
|
* @param keystoreType support keystore type (e.g., "JKS", "PKCS12")
|
||||||
|
* @param subjectAlternativeNames an optional list of subject alternative names to be included
|
||||||
|
* in certificate (may be null)
|
||||||
* @param protectedPassphrase key and keystore protection password
|
* @param protectedPassphrase key and keystore protection password
|
||||||
* @return keystore containing newly generated certification with key pair
|
* @return keystore containing newly generated certification with key pair
|
||||||
* @throws KeyStoreException if error occurs while updating keystore
|
* @throws KeyStoreException if error occurs while updating keystore
|
||||||
*/
|
*/
|
||||||
public static final KeyStore createKeyStore(String alias, String dn, int durationDays,
|
public static final KeyStore createKeyStore(String alias, String dn, int durationDays,
|
||||||
PrivateKeyEntry caEntry, File keyFile, String keystoreType, char[] protectedPassphrase)
|
PrivateKeyEntry caEntry, File keyFile, String keystoreType,
|
||||||
|
Collection<String> subjectAlternativeNames, char[] protectedPassphrase)
|
||||||
throws KeyStoreException {
|
throws KeyStoreException {
|
||||||
|
|
||||||
PasswordProtection pp = new PasswordProtection(protectedPassphrase);
|
PasswordProtection pp = new PasswordProtection(protectedPassphrase);
|
||||||
@@ -352,9 +357,8 @@ public class ApplicationKeyManagerUtils {
|
|||||||
}
|
}
|
||||||
X509Certificate caX509Cert = (X509Certificate) caCert;
|
X509Certificate caX509Cert = (X509Certificate) caCert;
|
||||||
caX500Name =
|
caX500Name =
|
||||||
new X500Name(caX509Cert.getSubjectDN().getName());
|
new X500Name(caX509Cert.getSubjectX500Principal().getName());
|
||||||
keyUsage = new KeyUsage(
|
keyUsage = new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment);
|
||||||
KeyUsage.digitalSignature | KeyUsage.keyEncipherment);
|
|
||||||
issuerKey = caEntry.getPrivateKey();
|
issuerKey = caEntry.getPrivateKey();
|
||||||
}
|
}
|
||||||
Date notBefore = new Date();
|
Date notBefore = new Date();
|
||||||
@@ -362,18 +366,23 @@ public class ApplicationKeyManagerUtils {
|
|||||||
Date notAfter = new Date(notBefore.getTime() + durationMs);
|
Date notAfter = new Date(notBefore.getTime() + durationMs);
|
||||||
BigInteger serialNumber = new BigInteger(128, random);
|
BigInteger serialNumber = new BigInteger(128, random);
|
||||||
|
|
||||||
// JcaX509ExtensionUtils x509Utils = new JcaX509ExtensionUtils();
|
|
||||||
|
|
||||||
X509v3CertificateBuilder certificateBuilder = new X509v3CertificateBuilder(caX500Name,
|
X509v3CertificateBuilder certificateBuilder = new X509v3CertificateBuilder(caX500Name,
|
||||||
serialNumber, notBefore, notAfter, x500Name, bcPk);
|
serialNumber, notBefore, notAfter, x500Name, bcPk);
|
||||||
certificateBuilder
|
certificateBuilder.addExtension(Extension.keyUsage, true, keyUsage);
|
||||||
// .addExtension(Extension.subjectKeyIdentifier, true, x509Utils.createSubjectKeyIdentifier(bcPk))
|
if (subjectAlternativeNames != null && !subjectAlternativeNames.isEmpty()) {
|
||||||
.addExtension(Extension.keyUsage, true, keyUsage);
|
List<GeneralName> nameList = new ArrayList<GeneralName>();
|
||||||
|
for (String altName : subjectAlternativeNames) {
|
||||||
|
int nameType =
|
||||||
|
IPAddress.isValid(altName) ? GeneralName.iPAddress : GeneralName.dNSName;
|
||||||
|
nameList.add(new GeneralName(nameType, altName));
|
||||||
|
}
|
||||||
|
GeneralName[] altNames = nameList.toArray(GeneralName[]::new);
|
||||||
|
certificateBuilder.addExtension(Extension.subjectAlternativeName, false,
|
||||||
|
new GeneralNames(altNames));
|
||||||
|
}
|
||||||
if (caEntry == null) {
|
if (caEntry == null) {
|
||||||
certificateBuilder
|
certificateBuilder.addExtension(Extension.basicConstraints, true,
|
||||||
.addExtension(Extension.basicConstraints, true, new BasicConstraints(1));
|
new BasicConstraints(1));
|
||||||
// .addExtension(Extension.authorityKeyIdentifier, true, x509Utils.createAuthorityKeyIdentifier(bcPk));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentSigner contentSigner =
|
ContentSigner contentSigner =
|
||||||
@@ -438,18 +447,21 @@ public class ApplicationKeyManagerUtils {
|
|||||||
* @param caEntry optional CA private key entry. If null, a self-signed CA certificate will be generated.
|
* @param caEntry optional CA private key entry. If null, a self-signed CA certificate will be generated.
|
||||||
* @param keyFile optional file to load/store resulting {@link KeyStore} (may be null)
|
* @param keyFile optional file to load/store resulting {@link KeyStore} (may be null)
|
||||||
* @param keystoreType support keystore type (e.g., "JKS", "PKCS12")
|
* @param keystoreType support keystore type (e.g., "JKS", "PKCS12")
|
||||||
|
* @param subjectAlternativeNames an optional list of subject alternative names to be included
|
||||||
|
* in certificate (may be null)
|
||||||
* @param protectedPassphrase key and keystore protection password
|
* @param protectedPassphrase key and keystore protection password
|
||||||
* @return newly generated keystore entry with key pair
|
* @return newly generated keystore entry with key pair
|
||||||
* @throws KeyStoreException if error occurs while updating keystore
|
* @throws KeyStoreException if error occurs while updating keystore
|
||||||
*/
|
*/
|
||||||
public static final PrivateKeyEntry createKeyEntry(String alias, String dn, int durationDays,
|
public static final PrivateKeyEntry createKeyEntry(String alias, String dn, int durationDays,
|
||||||
PrivateKeyEntry caEntry, File keyFile, String keystoreType, char[] protectedPassphrase)
|
PrivateKeyEntry caEntry, File keyFile, String keystoreType,
|
||||||
|
Collection<String> subjectAlternativeNames, char[] protectedPassphrase)
|
||||||
throws KeyStoreException {
|
throws KeyStoreException {
|
||||||
|
|
||||||
PasswordProtection pp = new PasswordProtection(protectedPassphrase);
|
PasswordProtection pp = new PasswordProtection(protectedPassphrase);
|
||||||
try {
|
try {
|
||||||
KeyStore keyStore = createKeyStore(alias, dn, durationDays, caEntry, keyFile,
|
KeyStore keyStore = createKeyStore(alias, dn, durationDays, caEntry, keyFile,
|
||||||
keystoreType, protectedPassphrase);
|
keystoreType, subjectAlternativeNames, protectedPassphrase);
|
||||||
return (PrivateKeyEntry) keyStore.getEntry(alias, pp);
|
return (PrivateKeyEntry) keyStore.getEntry(alias, pp);
|
||||||
}
|
}
|
||||||
catch (NoSuchAlgorithmException | UnrecoverableEntryException e) {
|
catch (NoSuchAlgorithmException | UnrecoverableEntryException e) {
|
||||||
|
|||||||
+1
-1
@@ -74,7 +74,7 @@ public class ApplicationKeyManagerFactoryTest extends AbstractGenericTest {
|
|||||||
keystoreFile.delete();
|
keystoreFile.delete();
|
||||||
|
|
||||||
ApplicationKeyManagerUtils.createKeyStore(ALIAS, TEST_IDENTITY, 2, null, keystoreFile,
|
ApplicationKeyManagerUtils.createKeyStore(ALIAS, TEST_IDENTITY, 2, null, keystoreFile,
|
||||||
"PKCS12", TEST_PWD.toCharArray());
|
"PKCS12", null, TEST_PWD.toCharArray());
|
||||||
|
|
||||||
ApplicationKeyManagerFactory.setKeyStorePasswordProvider(passwordProvider);
|
ApplicationKeyManagerFactory.setKeyStorePasswordProvider(passwordProvider);
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -943,21 +943,21 @@ public class ServerTestUtil {
|
|||||||
Msg.info(ServerTestUtil.class, "Generating self-signed CA cert: " + caPath);
|
Msg.info(ServerTestUtil.class, "Generating self-signed CA cert: " + caPath);
|
||||||
PrivateKeyEntry caEntry =
|
PrivateKeyEntry caEntry =
|
||||||
ApplicationKeyManagerUtils.createKeyEntry("test-CA", TEST_PKI_CA_DN, 2, null, null,
|
ApplicationKeyManagerUtils.createKeyEntry("test-CA", TEST_PKI_CA_DN, 2, null, null,
|
||||||
"PKCS12", ApplicationKeyManagerFactory.DEFAULT_PASSWORD.toCharArray());
|
"PKCS12", null, ApplicationKeyManagerFactory.DEFAULT_PASSWORD.toCharArray());
|
||||||
ApplicationKeyManagerUtils.exportX509Certificates(caEntry.getCertificateChain(), caFile);
|
ApplicationKeyManagerUtils.exportX509Certificates(caEntry.getCertificateChain(), caFile);
|
||||||
|
|
||||||
// Generate User/Client certificate and keystore
|
// Generate User/Client certificate and keystore
|
||||||
Msg.info(ServerTestUtil.class, "Generating test user key/cert (signed by test-CA, pwd: " +
|
Msg.info(ServerTestUtil.class, "Generating test user key/cert (signed by test-CA, pwd: " +
|
||||||
TEST_PKI_USER_PASSPHRASE + "): " + userKeystorePath);
|
TEST_PKI_USER_PASSPHRASE + "): " + userKeystorePath);
|
||||||
ApplicationKeyManagerUtils.createKeyEntry("test-sig", TEST_PKI_USER_DN, 2, caEntry,
|
ApplicationKeyManagerUtils.createKeyEntry("test-sig", TEST_PKI_USER_DN, 2, caEntry,
|
||||||
userKeystoreFile, "PKCS12", TEST_PKI_USER_PASSPHRASE.toCharArray());
|
userKeystoreFile, "PKCS12", null, TEST_PKI_USER_PASSPHRASE.toCharArray());
|
||||||
|
|
||||||
// Generate Server certificate and keystore
|
// Generate Server certificate and keystore
|
||||||
Msg.info(ServerTestUtil.class, "Generating test server key/cert (signed by test-CA, pwd: " +
|
Msg.info(ServerTestUtil.class, "Generating test server key/cert (signed by test-CA, pwd: " +
|
||||||
TEST_PKI_SERVER_PASSPHRASE + "): " + serverKeystorePath);
|
TEST_PKI_SERVER_PASSPHRASE + "): " + serverKeystorePath);
|
||||||
|
|
||||||
ApplicationKeyManagerUtils.createKeyEntry("test-sig", TEST_PKI_SERVER_DN, 2, caEntry,
|
ApplicationKeyManagerUtils.createKeyEntry("test-sig", TEST_PKI_SERVER_DN, 2, caEntry,
|
||||||
serverKeystoreFile, "PKCS12", TEST_PKI_SERVER_PASSPHRASE.toCharArray());
|
serverKeystoreFile, "PKCS12", null, TEST_PKI_SERVER_PASSPHRASE.toCharArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user