mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 18:29:37 +02:00
GT-2658 GhidraServer authentication via JAAS
Add JAAS auth mode -a4. Supply some example JAAS config files.
This commit is contained in:
parent
90f832bf1d
commit
a62730477e
16 changed files with 915 additions and 122 deletions
|
@ -15,6 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.server.remote;
|
package ghidra.server.remote;
|
||||||
|
|
||||||
|
import static ghidra.server.remote.GhidraServer.AUTH_MODE.*;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.net.*;
|
import java.net.*;
|
||||||
import java.rmi.NoSuchObjectException;
|
import java.rmi.NoSuchObjectException;
|
||||||
|
@ -24,6 +26,7 @@ import java.rmi.registry.Registry;
|
||||||
import java.rmi.server.*;
|
import java.rmi.server.*;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.rmi.ssl.SslRMIClientSocketFactory;
|
import javax.rmi.ssl.SslRMIClientSocketFactory;
|
||||||
import javax.rmi.ssl.SslRMIServerSocketFactory;
|
import javax.rmi.ssl.SslRMIServerSocketFactory;
|
||||||
|
@ -50,7 +53,9 @@ import ghidra.server.stream.BlockStreamServer;
|
||||||
import ghidra.server.stream.RemoteBlockStreamHandle;
|
import ghidra.server.stream.RemoteBlockStreamHandle;
|
||||||
import ghidra.util.SystemUtilities;
|
import ghidra.util.SystemUtilities;
|
||||||
import ghidra.util.exception.AssertException;
|
import ghidra.util.exception.AssertException;
|
||||||
|
import ghidra.util.exception.DuplicateNameException;
|
||||||
import resources.ResourceManager;
|
import resources.ResourceManager;
|
||||||
|
import utilities.util.FileUtilities;
|
||||||
import utility.application.ApplicationLayout;
|
import utility.application.ApplicationLayout;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -69,18 +74,42 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
|
||||||
|
|
||||||
private static String HELP_FILE = "/ghidra/server/remote/ServerHelp.txt";
|
private static String HELP_FILE = "/ghidra/server/remote/ServerHelp.txt";
|
||||||
private static String USAGE_ARGS =
|
private static String USAGE_ARGS =
|
||||||
" [-p<port>] [-a<authMode>] [-d<domain>] [-u] [-anonymous] [-ssh] [-ip <hostname>] [-i <ipAddress>] [-e<expireDays>] [-n] <serverPath>";
|
" [-p<port>] [-a<authMode>] [-d<domain>] [-u] [-anonymous] [-ssh] [-ip <hostname>] [-i <ipAddress>] [-e<expireDays>] [-jaas <path>] [-autoProvision] [-n] <serverPath>";
|
||||||
|
|
||||||
private static final String RMI_SERVER_PROPERTY = "java.rmi.server.hostname";
|
private static final String RMI_SERVER_PROPERTY = "java.rmi.server.hostname";
|
||||||
|
|
||||||
private static final String[] AUTH_MODES =
|
public enum AUTH_MODE {
|
||||||
{ "None", "Password File", "OS Password", "PKI", "OS Password & Password File" };
|
|
||||||
|
|
||||||
public static final int NO_AUTH_LOGIN = -1;
|
NO_AUTH_LOGIN("None"),
|
||||||
public static final int PASSWORD_FILE_LOGIN = 0;
|
PASSWORD_FILE_LOGIN("Password File"),
|
||||||
public static final int OS_PASSWORD_LOGIN = 1;
|
OS_PASSWORD_LOGIN("OS Password"),
|
||||||
public static final int PKI_LOGIN = 2;
|
PKI_LOGIN("PKI"),
|
||||||
public static final int ALT_OS_PASSWORD_LOGIN = 3;
|
ALT_OS_PASSWORD_LOGIN("OS Password & Password File"),
|
||||||
|
JAAS_LOGIN("JAAS");
|
||||||
|
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
AUTH_MODE(String description) {
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AUTH_MODE fromIndex(int index) {
|
||||||
|
//@formatter:off
|
||||||
|
switch ( index) {
|
||||||
|
case 0: return PASSWORD_FILE_LOGIN;
|
||||||
|
case 1: return OS_PASSWORD_LOGIN;
|
||||||
|
case 2: return PKI_LOGIN;
|
||||||
|
case 3: return ALT_OS_PASSWORD_LOGIN;
|
||||||
|
case 4: return JAAS_LOGIN;
|
||||||
|
default: return null;
|
||||||
|
}
|
||||||
|
//@formatter:on
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static GhidraServer server;
|
private static GhidraServer server;
|
||||||
|
|
||||||
|
@ -88,8 +117,8 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
|
||||||
private AuthenticationModule authModule;
|
private AuthenticationModule authModule;
|
||||||
private SSHAuthenticationModule sshAuthModule; // only supported in conjunction with password authentication modes (0 & 1)
|
private SSHAuthenticationModule sshAuthModule; // only supported in conjunction with password authentication modes (0 & 1)
|
||||||
private AnonymousAuthenticationModule anonymousAuthModule;
|
private AnonymousAuthenticationModule anonymousAuthModule;
|
||||||
|
|
||||||
private BlockStreamServer blockStreamServer;
|
private BlockStreamServer blockStreamServer;
|
||||||
|
private boolean autoProvisionAuthedUsers;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Server handle constructor.
|
* Server handle constructor.
|
||||||
|
@ -100,19 +129,24 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
|
||||||
* authentication mode
|
* authentication mode
|
||||||
* @param loginDomain
|
* @param loginDomain
|
||||||
* login domain or null (used for OS_PASSWORD_LOGIN mode only)
|
* login domain or null (used for OS_PASSWORD_LOGIN mode only)
|
||||||
* @param nameCallbackAllowed if true user name may be altered
|
* @param allowUserToSpecifyName if true user name may be altered
|
||||||
* @param altSSHLoginAllowed if true SSH authentication will be permitted
|
* @param altSSHLoginAllowed if true SSH authentication will be permitted
|
||||||
* as an alternate form of authentication
|
* as an alternate form of authentication
|
||||||
* @param defaultPasswordExpirationDays number of days default password will be valid
|
* @param defaultPasswordExpirationDays number of days default password will be valid
|
||||||
* @param allowAnonymousAccess allow anonymous access if true
|
* @param allowAnonymousAccess allow anonymous access if true
|
||||||
|
* @param autoProvisionAuthedUsers flag to turn on automatically adding successfully
|
||||||
|
* authenticated users to the user manager if they don't already exist
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
GhidraServer(File rootDir, int authMode, final String loginDomain, boolean nameCallbackAllowed,
|
GhidraServer(File rootDir, AUTH_MODE authMode, String loginDomain,
|
||||||
boolean altSSHLoginAllowed, int defaultPasswordExpirationDays,
|
boolean allowUserToSpecifyName, boolean altSSHLoginAllowed,
|
||||||
boolean allowAnonymousAccess) throws IOException, CertificateException {
|
int defaultPasswordExpirationDays, boolean allowAnonymousAccess,
|
||||||
|
boolean autoProvisionAuthedUsers) throws IOException, CertificateException {
|
||||||
|
|
||||||
super(ServerPortFactory.getRMISSLPort(), clientSocketFactory, serverSocketFactory);
|
super(ServerPortFactory.getRMISSLPort(), clientSocketFactory, serverSocketFactory);
|
||||||
|
|
||||||
|
this.autoProvisionAuthedUsers = autoProvisionAuthedUsers;
|
||||||
|
|
||||||
if (log == null) {
|
if (log == null) {
|
||||||
// logger generally initialized by main method, however during
|
// logger generally initialized by main method, however during
|
||||||
// testing the main method may be bypassed
|
// testing the main method may be bypassed
|
||||||
|
@ -129,7 +163,7 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
|
||||||
case PASSWORD_FILE_LOGIN:
|
case PASSWORD_FILE_LOGIN:
|
||||||
supportLocalPasswords = true;
|
supportLocalPasswords = true;
|
||||||
requireExplicitPasswordReset = false;
|
requireExplicitPasswordReset = false;
|
||||||
authModule = new PasswordFileAuthenticationModule(nameCallbackAllowed);
|
authModule = new PasswordFileAuthenticationModule(allowUserToSpecifyName);
|
||||||
break;
|
break;
|
||||||
// case ALT_OS_PASSWORD_LOGIN:
|
// case ALT_OS_PASSWORD_LOGIN:
|
||||||
// supportLocalPasswords = true;
|
// supportLocalPasswords = true;
|
||||||
|
@ -161,13 +195,16 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
|
||||||
altSSHLoginAllowed = false;
|
altSSHLoginAllowed = false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case JAAS_LOGIN:
|
||||||
|
authModule = new JAASAuthenticationModule("auth", allowUserToSpecifyName);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Unsupported Authentication mode: " + authMode);
|
throw new IllegalArgumentException("Unsupported Authentication mode: " + authMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (altSSHLoginAllowed) {
|
if (altSSHLoginAllowed) {
|
||||||
SecureRandomFactory.getSecureRandom(); // incur initialization delay up-front
|
SecureRandomFactory.getSecureRandom(); // incur initialization delay up-front
|
||||||
sshAuthModule = new SSHAuthenticationModule(nameCallbackAllowed);
|
sshAuthModule = new SSHAuthenticationModule(allowUserToSpecifyName);
|
||||||
}
|
}
|
||||||
|
|
||||||
mgr = new RepositoryManager(rootDir, supportLocalPasswords, requireExplicitPasswordReset,
|
mgr = new RepositoryManager(rootDir, supportLocalPasswords, requireExplicitPasswordReset,
|
||||||
|
@ -243,25 +280,22 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
|
||||||
RepositoryManager.log(null, null, "Anonymous access allowed", principal.getName());
|
RepositoryManager.log(null, null, "Anonymous access allowed", principal.getName());
|
||||||
}
|
}
|
||||||
else if (authModule != null) {
|
else if (authModule != null) {
|
||||||
for (Callback cb : authCallbacks) {
|
NameCallback nameCb =
|
||||||
if (cb instanceof NameCallback) {
|
AuthenticationModule.getFirstCallbackOfType(NameCallback.class, authCallbacks);
|
||||||
|
if (nameCb != null) {
|
||||||
if (!authModule.isNameCallbackAllowed()) {
|
if (!authModule.isNameCallbackAllowed()) {
|
||||||
RepositoryManager.log(null, null,
|
RepositoryManager.log(null, null,
|
||||||
"Illegal authentictaion callback: NameCallback not permitted",
|
"Illegal authentication callback: NameCallback not permitted", username);
|
||||||
username);
|
throw new LoginException("Illegal authentication callback");
|
||||||
throw new LoginException("Illegal authentictaion callback");
|
|
||||||
}
|
}
|
||||||
NameCallback nameCb = (NameCallback) cb;
|
|
||||||
String name = nameCb.getName();
|
String name = nameCb.getName();
|
||||||
if (name == null) {
|
if (name == null) {
|
||||||
RepositoryManager.log(null, null,
|
RepositoryManager.log(null, null,
|
||||||
"Illegal authentictaion callback: NameCallback must specify login name",
|
"Illegal authentication callback: NameCallback must specify login name",
|
||||||
username);
|
username);
|
||||||
throw new LoginException("Illegal authentictaion callback");
|
throw new LoginException("Illegal authentication callback");
|
||||||
}
|
}
|
||||||
username = name;
|
username = name;
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -286,6 +320,30 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
|
||||||
username = authModule.authenticate(mgr.getUserManager(), user, authCallbacks);
|
username = authModule.authenticate(mgr.getUserManager(), user, authCallbacks);
|
||||||
anonymousAccess = UserManager.ANONYMOUS_USERNAME.equals(username);
|
anonymousAccess = UserManager.ANONYMOUS_USERNAME.equals(username);
|
||||||
if (!anonymousAccess) {
|
if (!anonymousAccess) {
|
||||||
|
if (!mgr.getUserManager().isValidUser(username)) {
|
||||||
|
if (autoProvisionAuthedUsers) {
|
||||||
|
try {
|
||||||
|
mgr.getUserManager().addUser(username);
|
||||||
|
RepositoryManager.log(null, null,
|
||||||
|
"User '" + username + "' successful auto provision",
|
||||||
|
username);
|
||||||
|
}
|
||||||
|
catch (DuplicateNameException | IOException e) {
|
||||||
|
RepositoryManager.log(
|
||||||
|
null, null, "User '" + username +
|
||||||
|
"' auto provision failed. Cause: " + e.getMessage(),
|
||||||
|
username);
|
||||||
|
throw new LoginException(
|
||||||
|
"Error when trying to auto provision successfully authenticated user: " +
|
||||||
|
username);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new LoginException(
|
||||||
|
"User successfully authenticated, but does not exist in Ghidra user list: " +
|
||||||
|
username);
|
||||||
|
}
|
||||||
|
}
|
||||||
RepositoryManager.log(null, null, "User '" + username + "' authenticated",
|
RepositoryManager.log(null, null, "User '" + username + "' authenticated",
|
||||||
principal.getName());
|
principal.getName());
|
||||||
}
|
}
|
||||||
|
@ -357,28 +415,14 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void displayHelp() {
|
private static void displayHelp() {
|
||||||
InputStream in = ResourceManager.getResourceAsStream(HELP_FILE);
|
|
||||||
try {
|
try (InputStream in = ResourceManager.getResourceAsStream(HELP_FILE)) {
|
||||||
BufferedReader br = new BufferedReader(new InputStreamReader(in));
|
List<String> lines = FileUtilities.getLines(in);
|
||||||
while (true) {
|
lines.stream().forEach(s -> System.out.println(s));
|
||||||
String line = br.readLine();
|
|
||||||
if (line == null) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
System.out.println(line);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
catch (IOException e) {
|
||||||
// don't care
|
// don't care
|
||||||
}
|
}
|
||||||
finally {
|
|
||||||
try {
|
|
||||||
in.close();
|
|
||||||
}
|
|
||||||
catch (IOException e) {
|
|
||||||
// we tried
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final int IP_INTERFACE_RETRY_TIME_SEC = 5;
|
private static final int IP_INTERFACE_RETRY_TIME_SEC = 5;
|
||||||
|
@ -463,13 +507,14 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
|
||||||
}
|
}
|
||||||
|
|
||||||
int basePort = DEFAULT_PORT;
|
int basePort = DEFAULT_PORT;
|
||||||
int authMode = NO_AUTH_LOGIN;
|
AUTH_MODE authMode = NO_AUTH_LOGIN;
|
||||||
boolean nameCallbackAllowed = false;
|
boolean nameCallbackAllowed = false;
|
||||||
boolean altSSHLoginAllowed = false;
|
boolean altSSHLoginAllowed = false;
|
||||||
boolean allowAnonymousAccess = false;
|
boolean allowAnonymousAccess = false;
|
||||||
String loginDomain = null;
|
String loginDomain = null;
|
||||||
String rootPath = null;
|
String rootPath = null;
|
||||||
int defaultPasswordExpiration = -1;
|
int defaultPasswordExpiration = -1;
|
||||||
|
boolean autoProvision = false;
|
||||||
|
|
||||||
// Network name resolution disabled by default
|
// Network name resolution disabled by default
|
||||||
InetNameLookup.setLookupEnabled(false);
|
InetNameLookup.setLookupEnabled(false);
|
||||||
|
@ -490,13 +535,28 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (s.startsWith("-a") && s.length() == 3) { // Authentication Mode
|
else if (s.startsWith("-a") && s.length() == 3) { // Authentication Mode
|
||||||
|
int authModeNum = Integer.MIN_VALUE;
|
||||||
try {
|
try {
|
||||||
authMode = Integer.parseInt(s.substring(2));
|
authModeNum = Integer.parseInt(s.substring(2));
|
||||||
}
|
}
|
||||||
catch (NumberFormatException e1) {
|
catch (NumberFormatException e1) {
|
||||||
displayUsage("Invalid option: " + s);
|
displayUsage("Invalid option: " + s);
|
||||||
System.exit(-1);
|
System.exit(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
authMode = AUTH_MODE.fromIndex(authModeNum);
|
||||||
|
|
||||||
|
if (authMode == null) {
|
||||||
|
displayUsage("Invalid authentication mode: " + s);
|
||||||
|
System.exit(-1);
|
||||||
|
}
|
||||||
|
if (authMode == OS_PASSWORD_LOGIN || authMode == ALT_OS_PASSWORD_LOGIN) {
|
||||||
|
if (OperatingSystem.CURRENT_OPERATING_SYSTEM != OperatingSystem.WINDOWS) {
|
||||||
|
displayUsage("Authentication mode (" + authMode +
|
||||||
|
") only supported under Microsoft Windows");
|
||||||
|
System.exit(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (s.startsWith("-ip")) { // setting server remote access hostname
|
else if (s.startsWith("-ip")) { // setting server remote access hostname
|
||||||
int nextArgIndex = i + 1;
|
int nextArgIndex = i + 1;
|
||||||
|
@ -566,6 +626,27 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
|
||||||
System.out.println("Default password expiration has been disbaled.");
|
System.out.println("Default password expiration has been disbaled.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (s.equals("-jaas")) {
|
||||||
|
int nextArgIndex = i + 1;
|
||||||
|
if (!(nextArgIndex < args.length - 1)) {
|
||||||
|
// length - 1 -> don't count mandatory repo path, which is always last arg
|
||||||
|
displayUsage("Missing -jaas config file path argument");
|
||||||
|
System.exit(-1);
|
||||||
|
}
|
||||||
|
String jaasConfigFileStr = args[nextArgIndex];
|
||||||
|
i++;
|
||||||
|
File jaasConfigFile = new File(jaasConfigFileStr);
|
||||||
|
if (!jaasConfigFile.exists() || !jaasConfigFile.isFile()) {
|
||||||
|
displayUsage("JAAS config file does not exist or is not file: " +
|
||||||
|
(new File("./").getAbsolutePath()));
|
||||||
|
System.exit(-1);
|
||||||
|
}
|
||||||
|
// NOTE: there is a leading '=' char to force this path to be the one-and-only config file
|
||||||
|
System.setProperty("java.security.auth.login.config", "=" + jaasConfigFileStr);
|
||||||
|
}
|
||||||
|
else if (s.equals("-autoProvision")) {
|
||||||
|
autoProvision = true;
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
if (i < (args.length - 1)) {
|
if (i < (args.length - 1)) {
|
||||||
displayUsage("Invalid usage!");
|
displayUsage("Invalid usage!");
|
||||||
|
@ -575,18 +656,6 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authMode < NO_AUTH_LOGIN || authMode > ALT_OS_PASSWORD_LOGIN) {
|
|
||||||
displayUsage("Invalid authentication mode!");
|
|
||||||
System.exit(-1);
|
|
||||||
}
|
|
||||||
if (authMode == OS_PASSWORD_LOGIN || authMode == ALT_OS_PASSWORD_LOGIN) {
|
|
||||||
if (OperatingSystem.CURRENT_OPERATING_SYSTEM != OperatingSystem.WINDOWS) {
|
|
||||||
displayUsage("Authentication mode (" + authMode +
|
|
||||||
") only supported under Microsoft Windows");
|
|
||||||
System.exit(-1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rootPath == null) {
|
if (rootPath == null) {
|
||||||
displayUsage("Repository directory must be specified!");
|
displayUsage("Repository directory must be specified!");
|
||||||
System.exit(-1);
|
System.exit(-1);
|
||||||
|
@ -686,7 +755,7 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
|
||||||
: "disabled"));
|
: "disabled"));
|
||||||
// log.info(" Class server port: " + ??);
|
// log.info(" Class server port: " + ??);
|
||||||
log.info(" Root: " + rootPath);
|
log.info(" Root: " + rootPath);
|
||||||
log.info(" Auth: " + AUTH_MODES[authMode + 1]);
|
log.info(" Auth: " + authMode.getDescription());
|
||||||
if (authMode == PASSWORD_FILE_LOGIN && defaultPasswordExpiration >= 0) {
|
if (authMode == PASSWORD_FILE_LOGIN && defaultPasswordExpiration >= 0) {
|
||||||
log.info(" Default password expiration: " +
|
log.info(" Default password expiration: " +
|
||||||
(defaultPasswordExpiration == 0 ? "disabled"
|
(defaultPasswordExpiration == 0 ? "disabled"
|
||||||
|
@ -712,9 +781,9 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
|
||||||
};
|
};
|
||||||
clientSocketFactory = new SslRMIClientSocketFactory();
|
clientSocketFactory = new SslRMIClientSocketFactory();
|
||||||
|
|
||||||
GhidraServer svr =
|
GhidraServer svr = new GhidraServer(serverRoot, authMode, loginDomain,
|
||||||
new GhidraServer(serverRoot, authMode, loginDomain, nameCallbackAllowed,
|
nameCallbackAllowed, altSSHLoginAllowed, defaultPasswordExpiration,
|
||||||
altSSHLoginAllowed, defaultPasswordExpiration, allowAnonymousAccess);
|
allowAnonymousAccess, autoProvision);
|
||||||
|
|
||||||
log.info("Registering Ghidra Server...");
|
log.info("Registering Ghidra Server...");
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
Ghidra server startup parameters.
|
Ghidra server startup parameters.
|
||||||
Command line parameters: [-ip <hostname>] [-i #.#.#.#] [-p#] [-a#] [-d<ntDomain>] [-e<days>] [-u] [-n] <repository_path>
|
Command line parameters: [-ip <hostname>] [-i #.#.#.#] [-p#] [-a#] [-d<ntDomain>] [-e<days>] [-u] [-jaas <path_to_jaas_config_file>] [-autoProvision] [-n] <repository_path>
|
||||||
|
|
||||||
-ip <hostname> : identifies the remote access IPv4 address or hostname (FQDN) which should be
|
-ip <hostname> : identifies the remote access IPv4 address or hostname (FQDN) which should be
|
||||||
used by remote clients to access the server.
|
used by remote clients to access the server.
|
||||||
|
@ -8,9 +8,10 @@ Command line parameters: [-ip <hostname>] [-i #.#.#.#] [-p#] [-a#] [-d<ntDomain>
|
||||||
|
|
||||||
-p# : base TCP port to be used (default: 13100) [see Note 1]
|
-p# : base TCP port to be used (default: 13100) [see Note 1]
|
||||||
|
|
||||||
-a# : an optional authentication mode where # is a value 0 or 2
|
-a# : an optional authentication mode where # is a value 0 or 2 or 4
|
||||||
0 - Private user password
|
0 - Private user password
|
||||||
2 - PKI Authentication
|
2 - PKI Authentication
|
||||||
|
4 - JAAS Authentication controlled by config file pointed to by -jaas
|
||||||
|
|
||||||
-anonymous : enables anonymous repository access (see svrREADME.html for details)
|
-anonymous : enables anonymous repository access (see svrREADME.html for details)
|
||||||
|
|
||||||
|
@ -20,6 +21,12 @@ Command line parameters: [-ip <hostname>] [-i #.#.#.#] [-p#] [-a#] [-d<ntDomain>
|
||||||
|
|
||||||
-u : enable users to be prompted for user ID (does not apply to -a2 PKI mode)
|
-u : enable users to be prompted for user ID (does not apply to -a2 PKI mode)
|
||||||
|
|
||||||
|
-jaas /path/to/jaas_config_file : specifies config file to use for JAAS (enabled by -a4)
|
||||||
|
|
||||||
|
-autoProvision : enable the auto-creation of Ghidra users when the authenticator module
|
||||||
|
(ie. OS or other authentication method specified by JAAS) authenticates
|
||||||
|
a new unknown user.
|
||||||
|
|
||||||
-n : enable reverse name lookup for IP addresses when logging (requires proper configuration
|
-n : enable reverse name lookup for IP addresses when logging (requires proper configuration
|
||||||
of reverse lookup by your DNS server)
|
of reverse lookup by your DNS server)
|
||||||
|
|
||||||
|
@ -36,3 +43,11 @@ NOTES:
|
||||||
1. The server utilizes a total of 3 consecutive TCP ports starting with the
|
1. The server utilizes a total of 3 consecutive TCP ports starting with the
|
||||||
base port (default: 13100). The wrapper.log can be examined for server
|
base port (default: 13100). The wrapper.log can be examined for server
|
||||||
console output which will indicate the startup port assignments.
|
console output which will indicate the startup port assignments.
|
||||||
|
|
||||||
|
2. Ghidra's JAAS authentication mode uses the "auth" section of the JAAS file pointed to by the -jaas
|
||||||
|
argument.
|
||||||
|
|
||||||
|
3. Example JAAS config files are included in the /server/jaas directory of the Ghidra distro. It is the
|
||||||
|
system administrator's responsibility to create the necessary JAAS config for their system.
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
@ -16,12 +15,12 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.server.security;
|
package ghidra.server.security;
|
||||||
|
|
||||||
import ghidra.framework.remote.AnonymousCallback;
|
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import javax.security.auth.callback.Callback;
|
import javax.security.auth.callback.Callback;
|
||||||
|
|
||||||
|
import ghidra.framework.remote.AnonymousCallback;
|
||||||
|
|
||||||
public class AnonymousAuthenticationModule {
|
public class AnonymousAuthenticationModule {
|
||||||
|
|
||||||
public Callback[] addAuthenticationCallbacks(Callback[] primaryAuthCallbacks) {
|
public Callback[] addAuthenticationCallbacks(Callback[] primaryAuthCallbacks) {
|
||||||
|
@ -34,15 +33,9 @@ public class AnonymousAuthenticationModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean anonymousAccessRequested(Callback[] callbacks) {
|
public boolean anonymousAccessRequested(Callback[] callbacks) {
|
||||||
if (callbacks != null) {
|
AnonymousCallback anonCb =
|
||||||
for (int i = 0; i < callbacks.length; i++) {
|
AuthenticationModule.getFirstCallbackOfType(AnonymousCallback.class, callbacks);
|
||||||
if (callbacks[i] instanceof AnonymousCallback) {
|
return anonCb != null && anonCb.anonymousAccessRequested();
|
||||||
AnonymousCallback anonCb = (AnonymousCallback) callbacks[i];
|
|
||||||
return anonCb.anonymousAccessRequested();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
@ -16,16 +15,31 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.server.security;
|
package ghidra.server.security;
|
||||||
|
|
||||||
import ghidra.server.UserManager;
|
|
||||||
|
|
||||||
import javax.security.auth.Subject;
|
import javax.security.auth.Subject;
|
||||||
import javax.security.auth.callback.Callback;
|
import javax.security.auth.callback.*;
|
||||||
import javax.security.auth.login.LoginException;
|
import javax.security.auth.login.LoginException;
|
||||||
|
|
||||||
|
import ghidra.server.UserManager;
|
||||||
|
|
||||||
public interface AuthenticationModule {
|
public interface AuthenticationModule {
|
||||||
|
|
||||||
|
public static final String USERNAME_CALLBACK_PROMPT = "User ID";
|
||||||
|
public static final String PASSWORD_CALLBACK_PROMPT = "Password";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Complete the authentication process
|
* Complete the authentication process.
|
||||||
|
* <p>
|
||||||
|
* Note to AuthenticationModule implementors:
|
||||||
|
* <ul>
|
||||||
|
* <li>The authentication callback objects are not guaranteed to be the same
|
||||||
|
* instances as those returned by the {@link #getAuthenticationCallbacks()}.<br>
|
||||||
|
* (they may have been cloned or duplicated or copied in some manner)</li>
|
||||||
|
* <li>The authentication callback array may contain callback instances other than
|
||||||
|
* the ones your module specified in its {@link #getAuthenticationCallbacks()}</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
* @param userMgr Ghidra server user manager
|
* @param userMgr Ghidra server user manager
|
||||||
* @param subject unauthenticated user ID (must be used if name callback not provided/allowed)
|
* @param subject unauthenticated user ID (must be used if name callback not provided/allowed)
|
||||||
* @param callbacks authentication callbacks
|
* @param callbacks authentication callbacks
|
||||||
|
@ -41,6 +55,8 @@ public interface AuthenticationModule {
|
||||||
Callback[] getAuthenticationCallbacks();
|
Callback[] getAuthenticationCallbacks();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Allows an AuthenticationModule to deny default anonymous login steps.
|
||||||
|
* <p>
|
||||||
* @return true if a separate AnonymousCallback is allowed and may be
|
* @return true if a separate AnonymousCallback is allowed and may be
|
||||||
* added to the array returned by getAuthenticationCallbacks.
|
* added to the array returned by getAuthenticationCallbacks.
|
||||||
* @see #getAuthenticationCallbacks()
|
* @see #getAuthenticationCallbacks()
|
||||||
|
@ -52,4 +68,32 @@ public interface AuthenticationModule {
|
||||||
*/
|
*/
|
||||||
boolean isNameCallbackAllowed();
|
boolean isNameCallbackAllowed();
|
||||||
|
|
||||||
|
static Callback[] createSimpleNamePasswordCallbacks(boolean allowUserToSpecifyName) {
|
||||||
|
PasswordCallback passCb = new PasswordCallback(PASSWORD_CALLBACK_PROMPT + ":", false);
|
||||||
|
if (allowUserToSpecifyName) {
|
||||||
|
NameCallback nameCb = new NameCallback(USERNAME_CALLBACK_PROMPT + ":");
|
||||||
|
return new Callback[] { nameCb, passCb };
|
||||||
|
}
|
||||||
|
return new Callback[] { passCb };
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T extends Callback> T getFirstCallbackOfType(Class<T> callbackClass,
|
||||||
|
Callback[] callbackArray) {
|
||||||
|
if (callbackArray == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// dunno if this approach is warranted. the second loop with its isInstance() may be fine.
|
||||||
|
for (Callback cb : callbackArray) {
|
||||||
|
if (callbackClass == cb.getClass()) {
|
||||||
|
return callbackClass.cast(cb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (Callback cb : callbackArray) {
|
||||||
|
if (callbackClass.isInstance(cb.getClass())) {
|
||||||
|
return callbackClass.cast(cb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,125 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.server.security;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import javax.security.auth.Subject;
|
||||||
|
import javax.security.auth.callback.*;
|
||||||
|
import javax.security.auth.login.LoginContext;
|
||||||
|
import javax.security.auth.login.LoginException;
|
||||||
|
import javax.security.auth.spi.LoginModule;
|
||||||
|
|
||||||
|
import ghidra.framework.remote.GhidraPrincipal;
|
||||||
|
import ghidra.server.UserManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapter between Ghidra {@link AuthenticationModule}s and simple JAAS {@link LoginModule}s.
|
||||||
|
* <p>
|
||||||
|
* JAAS is typically configured via an external file that specifies the stack of LoginModules
|
||||||
|
* per login context configuration "name".
|
||||||
|
* <p>
|
||||||
|
* This implementation only supports JAAS LoginModules that use Name and Password callbacks,
|
||||||
|
* and ignores any customization in the name and password callbacks in favor of its own
|
||||||
|
* callbacks.
|
||||||
|
* <p>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class JAASAuthenticationModule implements AuthenticationModule {
|
||||||
|
|
||||||
|
private boolean allowUserToSpecifyName;
|
||||||
|
private String loginContextName;
|
||||||
|
|
||||||
|
public JAASAuthenticationModule(String loginContextName, boolean allowUserToSpecifyName) {
|
||||||
|
this.loginContextName = loginContextName;
|
||||||
|
this.allowUserToSpecifyName = allowUserToSpecifyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String authenticate(UserManager userMgr, Subject subject, Callback[] callbacks)
|
||||||
|
throws LoginException {
|
||||||
|
GhidraPrincipal principal = GhidraPrincipal.getGhidraPrincipal(subject);
|
||||||
|
AtomicReference<String> loginName = new AtomicReference<>();
|
||||||
|
LoginContext loginCtx = new LoginContext(loginContextName, loginModuleCallbacks -> {
|
||||||
|
loginName.set(copyCallbackValues(callbacks, loginModuleCallbacks, principal));
|
||||||
|
});
|
||||||
|
|
||||||
|
// this is where the callback is triggered
|
||||||
|
loginCtx.login();
|
||||||
|
|
||||||
|
String loginNameResult = loginName.get();
|
||||||
|
return (loginNameResult != null) ? loginNameResult : principal.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Callback[] getAuthenticationCallbacks() {
|
||||||
|
// We don't know for sure what callbacks the JAAS LoginModule is going to throw at us
|
||||||
|
// during the login() method. Therefore, to keep things simple, we are going to limit
|
||||||
|
// the supported JAAS LoginModules to ones that only use Name and Password callbacks.
|
||||||
|
return AuthenticationModule.createSimpleNamePasswordCallbacks(allowUserToSpecifyName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean anonymousCallbacksAllowed() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isNameCallbackAllowed() {
|
||||||
|
return allowUserToSpecifyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copies the callback values from the callback instances in the src list to the
|
||||||
|
* corresponding instances (matched by callback class type) in the dest list, and
|
||||||
|
* then returns the user name.
|
||||||
|
*
|
||||||
|
* @param srcInstances array of callback instances to copy from
|
||||||
|
* @param destInstances array of callback instances to copy to
|
||||||
|
* @param principal the user principal (ie. default) name, used when no
|
||||||
|
* name callback is found
|
||||||
|
* @return the effective user name, either the principal or value from name callback.
|
||||||
|
* @throws IOException if missing password callback
|
||||||
|
*/
|
||||||
|
private String copyCallbackValues(Callback[] srcInstances, Callback[] destInstances,
|
||||||
|
GhidraPrincipal principal) throws IOException {
|
||||||
|
PasswordCallback srcPcb =
|
||||||
|
AuthenticationModule.getFirstCallbackOfType(PasswordCallback.class, srcInstances);
|
||||||
|
NameCallback srcNcb =
|
||||||
|
AuthenticationModule.getFirstCallbackOfType(NameCallback.class, srcInstances);
|
||||||
|
|
||||||
|
String userName = null;
|
||||||
|
NameCallback destNcb =
|
||||||
|
AuthenticationModule.getFirstCallbackOfType(NameCallback.class, destInstances);
|
||||||
|
if (destNcb != null) {
|
||||||
|
userName =
|
||||||
|
(allowUserToSpecifyName && srcNcb != null) ? srcNcb.getName() : principal.getName();
|
||||||
|
destNcb.setName(userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
PasswordCallback destPcb =
|
||||||
|
AuthenticationModule.getFirstCallbackOfType(PasswordCallback.class, destInstances);
|
||||||
|
if (destPcb != null) {
|
||||||
|
if (srcPcb == null) {
|
||||||
|
throw new IOException("Missing password callback value");
|
||||||
|
}
|
||||||
|
destPcb.setPassword(srcPcb.getPassword());
|
||||||
|
}
|
||||||
|
return userName;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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.
|
||||||
|
@ -16,9 +15,6 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.server.security;
|
package ghidra.server.security;
|
||||||
|
|
||||||
import ghidra.framework.remote.GhidraPrincipal;
|
|
||||||
import ghidra.server.UserManager;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
@ -27,6 +23,11 @@ import javax.security.auth.callback.*;
|
||||||
import javax.security.auth.login.FailedLoginException;
|
import javax.security.auth.login.FailedLoginException;
|
||||||
import javax.security.auth.login.LoginException;
|
import javax.security.auth.login.LoginException;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import ghidra.framework.remote.GhidraPrincipal;
|
||||||
|
import ghidra.server.UserManager;
|
||||||
|
|
||||||
public class PasswordFileAuthenticationModule implements AuthenticationModule {
|
public class PasswordFileAuthenticationModule implements AuthenticationModule {
|
||||||
|
|
||||||
private final boolean nameCallbackAllowed;
|
private final boolean nameCallbackAllowed;
|
||||||
|
@ -43,13 +44,9 @@ public class PasswordFileAuthenticationModule implements AuthenticationModule {
|
||||||
/*
|
/*
|
||||||
* @see ghidra.server.security.AuthenticationModule#getAuthenticationCallbacks()
|
* @see ghidra.server.security.AuthenticationModule#getAuthenticationCallbacks()
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public Callback[] getAuthenticationCallbacks() {
|
public Callback[] getAuthenticationCallbacks() {
|
||||||
PasswordCallback passCb = new PasswordCallback("Password:", false);
|
return AuthenticationModule.createSimpleNamePasswordCallbacks(nameCallbackAllowed);
|
||||||
if (nameCallbackAllowed) {
|
|
||||||
NameCallback nameCb = new NameCallback("User ID:");
|
|
||||||
return new Callback[] { nameCb, passCb };
|
|
||||||
}
|
|
||||||
return new Callback[] { passCb };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -60,6 +57,7 @@ public class PasswordFileAuthenticationModule implements AuthenticationModule {
|
||||||
/*
|
/*
|
||||||
* @see ghidra.server.security.AuthenticationModule#authenticate(ghidra.server.UserManager, javax.security.auth.Subject, javax.security.auth.callback.Callback[])
|
* @see ghidra.server.security.AuthenticationModule#authenticate(ghidra.server.UserManager, javax.security.auth.Subject, javax.security.auth.callback.Callback[])
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public String authenticate(UserManager userMgr, Subject subject, Callback[] callbacks)
|
public String authenticate(UserManager userMgr, Subject subject, Callback[] callbacks)
|
||||||
throws LoginException {
|
throws LoginException {
|
||||||
GhidraPrincipal user = GhidraPrincipal.getGhidraPrincipal(subject);
|
GhidraPrincipal user = GhidraPrincipal.getGhidraPrincipal(subject);
|
||||||
|
@ -68,23 +66,15 @@ public class PasswordFileAuthenticationModule implements AuthenticationModule {
|
||||||
}
|
}
|
||||||
String username = user.getName();
|
String username = user.getName();
|
||||||
|
|
||||||
NameCallback nameCb = null;
|
NameCallback nameCb =
|
||||||
PasswordCallback passCb = null;
|
AuthenticationModule.getFirstCallbackOfType(NameCallback.class, callbacks);
|
||||||
if (callbacks != null) {
|
PasswordCallback passCb =
|
||||||
for (int i = 0; i < callbacks.length; i++) {
|
AuthenticationModule.getFirstCallbackOfType(PasswordCallback.class, callbacks);
|
||||||
if (callbacks[i] instanceof NameCallback) {
|
|
||||||
nameCb = (NameCallback) callbacks[i];
|
|
||||||
}
|
|
||||||
else if (callbacks[i] instanceof PasswordCallback) {
|
|
||||||
passCb = (PasswordCallback) callbacks[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nameCallbackAllowed && nameCb != null) {
|
if (nameCallbackAllowed && nameCb != null) {
|
||||||
username = nameCb.getName();
|
username = nameCb.getName();
|
||||||
}
|
}
|
||||||
if (username == null || username.length() == 0) {
|
if (StringUtils.isBlank(username)) {
|
||||||
throw new FailedLoginException("User ID must be specified");
|
throw new FailedLoginException("User ID must be specified");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,10 +82,10 @@ public class PasswordFileAuthenticationModule implements AuthenticationModule {
|
||||||
throw new FailedLoginException("Password callback required");
|
throw new FailedLoginException("Password callback required");
|
||||||
}
|
}
|
||||||
|
|
||||||
char[] pass = null;
|
char[] pass = passCb.getPassword();
|
||||||
try {
|
|
||||||
pass = passCb.getPassword();
|
|
||||||
passCb.clearPassword();
|
passCb.clearPassword();
|
||||||
|
|
||||||
|
try {
|
||||||
userMgr.authenticateUser(username, pass);
|
userMgr.authenticateUser(username, pass);
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
catch (IOException e) {
|
||||||
|
|
|
@ -0,0 +1,337 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.server.security.loginmodule;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import javax.security.auth.Subject;
|
||||||
|
import javax.security.auth.callback.*;
|
||||||
|
import javax.security.auth.login.FailedLoginException;
|
||||||
|
import javax.security.auth.login.LoginException;
|
||||||
|
import javax.security.auth.spi.LoginModule;
|
||||||
|
|
||||||
|
import com.sun.security.auth.UserPrincipal;
|
||||||
|
|
||||||
|
import ghidra.server.RepositoryManager;
|
||||||
|
import ghidra.util.DateUtils;
|
||||||
|
import ghidra.util.timer.Watchdog;
|
||||||
|
import utilities.util.FileUtilities;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A JAAS {@link LoginModule} that executes an external program that decides if the username
|
||||||
|
* and password are authorized.
|
||||||
|
* <p>
|
||||||
|
* Compatible with Apache's mod_authnz_external.
|
||||||
|
* <p>
|
||||||
|
* JAAS will create a new instance of this class for each login operation.
|
||||||
|
* <p>
|
||||||
|
* The options for this module (the path to the external program, timeout values, etc)
|
||||||
|
* are supplied to the {@link #initialize(Subject, CallbackHandler, Map, Map)}
|
||||||
|
* by JAAS and are typically read from a config file that looks like:
|
||||||
|
* <pre>
|
||||||
|
* auth {
|
||||||
|
* ghidra.server.security.loginmodule.ExternalProgramLoginModule required
|
||||||
|
* PROGRAM="jaas_external_program.example.sh"
|
||||||
|
* ARG_00="arg1" ARG_01="test arg2"
|
||||||
|
* TIMEOUT="1000"
|
||||||
|
* USER_PROMPT="Enter username"
|
||||||
|
* PASSWORD_PROMPT="Enter password"
|
||||||
|
* ;
|
||||||
|
* };
|
||||||
|
* </pre>
|
||||||
|
* <p>
|
||||||
|
* The external program is fed the username\n and password\n on its STDIN (ie. two text lines).
|
||||||
|
* The external authenticator needs to exit with 0 (zero) error level
|
||||||
|
* if the authentication was successful, or a non-zero error level if not successful.
|
||||||
|
* <p>
|
||||||
|
* This implementation tries to follow best practices for JAAS LoginModules, even
|
||||||
|
* though Ghidra does not utilize the entire API.
|
||||||
|
* <p>
|
||||||
|
* For instance, Ghidra will override JAAS LoginModule's prompt values for name and password.
|
||||||
|
* <p>
|
||||||
|
* Options:
|
||||||
|
* <ul>
|
||||||
|
* <li>PROGRAM - path to an executable program or script.</li>
|
||||||
|
* <li>ARG_* - any number of arguments to be passed to the program.<br>
|
||||||
|
* Example: ARG_00="foo" ARG_01="bar". Arguments are ordered according to their natural
|
||||||
|
* sorting order, so it is advisable to keep the suffixes to the same length.</li>
|
||||||
|
* <li>TIMEOUT - number of milliseconds to wait for the external program to return results</li>
|
||||||
|
* <li>USER_PROMPT - a string to send to the user to prompt them to type their name (not used in Ghidra)</li>
|
||||||
|
* <li>PASSWORD_PROMPT - a string to send to the user to prompt them to type their password (not used in Ghidra)</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class ExternalProgramLoginModule implements LoginModule {
|
||||||
|
// private static final String USERNAME_KEY = "javax.security.auth.login.name";
|
||||||
|
// private static final String PASSWORD_KEY = "javax.security.auth.login.password";
|
||||||
|
private static final String USER_PROMPT_OPTION_NAME = "USER_PROMPT";
|
||||||
|
private static final String PASSWORD_PROMPT_OPTION_NAME = "PASSWORD_PROMPT";
|
||||||
|
private static final String TIMEOUT_OPTION_NAME = "TIMEOUT";
|
||||||
|
private static final String PROGRAM_OPTION_NAME = "PROGRAM";
|
||||||
|
private static final String ARG_OPTION_NAME = "ARG_";
|
||||||
|
private static final long DEFAULT_TIMEOUT_MS = DateUtils.MS_PER_SEC * 10;
|
||||||
|
|
||||||
|
private Subject subject;
|
||||||
|
private CallbackHandler callbackHandler;
|
||||||
|
//private Map<String, Object> sharedState;
|
||||||
|
private Map<String, Object> options;
|
||||||
|
//private boolean useSharedState;
|
||||||
|
//private boolean clearSharedCreds;
|
||||||
|
private UserPrincipal user;
|
||||||
|
private String username;
|
||||||
|
private char[] password;
|
||||||
|
private String[] cmdArray;
|
||||||
|
private String extProgramName;
|
||||||
|
private boolean success;
|
||||||
|
private boolean committed;
|
||||||
|
private long timeout_ms = DEFAULT_TIMEOUT_MS;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public void initialize(Subject subject, CallbackHandler callbackHandler,
|
||||||
|
Map<String, ?> sharedState, Map<String, ?> options) {
|
||||||
|
this.subject = subject;
|
||||||
|
this.callbackHandler = callbackHandler;
|
||||||
|
//this.sharedState = (Map<String, Object>) sharedState;
|
||||||
|
this.options = (Map<String, Object>) options;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean login() throws LoginException {
|
||||||
|
readOptions();
|
||||||
|
getNameAndPassword();
|
||||||
|
callExternalProgram();
|
||||||
|
success = true;
|
||||||
|
user = new UserPrincipal(username);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean commit() throws LoginException {
|
||||||
|
if (!success) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!subject.isReadOnly()) {
|
||||||
|
if (!user.implies(subject)) {
|
||||||
|
subject.getPrincipals().add(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
committed = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean abort() throws LoginException {
|
||||||
|
if (!success) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!committed) {
|
||||||
|
success = false;
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
logout();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean logout() throws LoginException {
|
||||||
|
if (subject.isReadOnly()) {
|
||||||
|
cleanup();
|
||||||
|
throw new LoginException("Subject is read-only");
|
||||||
|
}
|
||||||
|
subject.getPrincipals().remove(user);
|
||||||
|
|
||||||
|
cleanup();
|
||||||
|
success = false;
|
||||||
|
committed = false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cleanup() {
|
||||||
|
user = null;
|
||||||
|
username = null;
|
||||||
|
if (password != null) {
|
||||||
|
Arrays.fill(password, '\0');
|
||||||
|
password = null;
|
||||||
|
}
|
||||||
|
/* not impl yet
|
||||||
|
if (clearSharedCreds) {
|
||||||
|
sharedState.remove(USERNAME_KEY);
|
||||||
|
sharedState.remove(PASSWORD_KEY);
|
||||||
|
} */
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readOptions() throws LoginException {
|
||||||
|
String timeoutStr = (String) options.get(TIMEOUT_OPTION_NAME);
|
||||||
|
if (timeoutStr != null) {
|
||||||
|
try {
|
||||||
|
timeout_ms = Long.parseLong(timeoutStr);
|
||||||
|
}
|
||||||
|
catch (NumberFormatException e) {
|
||||||
|
// ignore, leave timeout at default 10sec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
readExtProgOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void callExternalProgram() throws LoginException {
|
||||||
|
|
||||||
|
AtomicReference<Process> process = new AtomicReference<>();
|
||||||
|
|
||||||
|
try (Watchdog watchdog = new Watchdog(timeout_ms, () -> {
|
||||||
|
Process local_p = process.get();
|
||||||
|
if (local_p != null) {
|
||||||
|
local_p.destroyForcibly();
|
||||||
|
}
|
||||||
|
})) {
|
||||||
|
watchdog.arm();
|
||||||
|
Process p = Runtime.getRuntime().exec(cmdArray);
|
||||||
|
process.set(p);
|
||||||
|
|
||||||
|
FileUtilities.asyncForEachLine(p.getInputStream(), (stdOutStr) -> {
|
||||||
|
RepositoryManager.log(null, null, extProgramName + " STDOUT: " + stdOutStr, null);
|
||||||
|
});
|
||||||
|
FileUtilities.asyncForEachLine(p.getErrorStream(), (errStr) -> {
|
||||||
|
RepositoryManager.log(null, null, extProgramName + " STDERR: " + errStr, null);
|
||||||
|
});
|
||||||
|
|
||||||
|
PrintWriter outputWriter = new PrintWriter(p.getOutputStream());
|
||||||
|
outputWriter.write(username);
|
||||||
|
outputWriter.write("\n");
|
||||||
|
outputWriter.write(password);
|
||||||
|
outputWriter.write("\n");
|
||||||
|
outputWriter.flush();
|
||||||
|
|
||||||
|
int exitValue = p.waitFor();
|
||||||
|
if (exitValue != 0) {
|
||||||
|
throw new FailedLoginException(
|
||||||
|
"Login failed: external command exited with error " + exitValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException | InterruptedException e) {
|
||||||
|
RepositoryManager.log(null, null,
|
||||||
|
"Exception when executing " + extProgramName + ":" + e.getMessage(), null);
|
||||||
|
throw new LoginException("Error executing external program");
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
Process p = process.get();
|
||||||
|
if (p != null && p.isAlive()) {
|
||||||
|
if (p.isAlive()) {
|
||||||
|
try {
|
||||||
|
p.waitFor(timeout_ms, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
catch (InterruptedException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
p.destroyForcibly();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readExtProgOptions() throws LoginException {
|
||||||
|
String externalProgram = (String) options.get(PROGRAM_OPTION_NAME);
|
||||||
|
if (externalProgram == null || externalProgram.isBlank()) {
|
||||||
|
throw new LoginException(
|
||||||
|
"Missing " + PROGRAM_OPTION_NAME + "=path_to_external_program in options");
|
||||||
|
}
|
||||||
|
File extProFile = new File(externalProgram).getAbsoluteFile();
|
||||||
|
if (!extProFile.exists()) {
|
||||||
|
throw new LoginException(
|
||||||
|
"Bad " + PROGRAM_OPTION_NAME + "=path_to_external_program in options");
|
||||||
|
}
|
||||||
|
extProgramName = extProFile.getName();
|
||||||
|
|
||||||
|
List<String> argKeys = options.keySet().stream().filter(
|
||||||
|
key -> key.startsWith(ARG_OPTION_NAME)).sorted().collect(Collectors.toList());
|
||||||
|
List<String> cmdArrayValues = new ArrayList<>();
|
||||||
|
cmdArrayValues.add(externalProgram.toString());
|
||||||
|
for (String argKey : argKeys) {
|
||||||
|
String val = options.get(argKey).toString();
|
||||||
|
cmdArrayValues.add(val);
|
||||||
|
}
|
||||||
|
cmdArray = cmdArrayValues.toArray(new String[cmdArrayValues.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void getNameAndPassword() throws LoginException {
|
||||||
|
String userPrompt = options.getOrDefault(USER_PROMPT_OPTION_NAME, "User name").toString();
|
||||||
|
String passPrompt =
|
||||||
|
options.getOrDefault(PASSWORD_PROMPT_OPTION_NAME, "Password").toString();
|
||||||
|
|
||||||
|
List<Callback> callbacks = new ArrayList<>();
|
||||||
|
NameCallback ncb = null;
|
||||||
|
PasswordCallback pcb = null;
|
||||||
|
|
||||||
|
/* not impl yet
|
||||||
|
if (useSharedState) {
|
||||||
|
username = (String) sharedState.get(USERNAME_KEY);
|
||||||
|
password = (char[]) sharedState.get(PASSWORD_KEY);
|
||||||
|
if (password != null) {
|
||||||
|
password = password.clone();
|
||||||
|
}
|
||||||
|
} */
|
||||||
|
|
||||||
|
if (username == null) {
|
||||||
|
ncb = new NameCallback(userPrompt);
|
||||||
|
callbacks.add(ncb);
|
||||||
|
}
|
||||||
|
if (password == null) {
|
||||||
|
pcb = new PasswordCallback(passPrompt, false);
|
||||||
|
callbacks.add(pcb);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!callbacks.isEmpty()) {
|
||||||
|
try {
|
||||||
|
callbackHandler.handle(callbacks.toArray(new Callback[callbacks.size()]));
|
||||||
|
if (ncb != null) {
|
||||||
|
username = ncb.getName();
|
||||||
|
}
|
||||||
|
if (pcb != null) {
|
||||||
|
password = pcb.getPassword();
|
||||||
|
pcb.clearPassword();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (username == null || password == null) {
|
||||||
|
throw new LoginException("Failed to get username or password");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException | UnsupportedCallbackException e) {
|
||||||
|
throw new LoginException("Error during callback: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
validateUsernameAndPasswordFormat();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateUsernameAndPasswordFormat() throws LoginException {
|
||||||
|
if (username.contains("\n") || username.contains("\0")) {
|
||||||
|
throw new LoginException("Bad characters in username");
|
||||||
|
}
|
||||||
|
String tmpPass = String.valueOf(password);
|
||||||
|
if (tmpPass.contains("\n") || tmpPass.contains("\0")) {
|
||||||
|
throw new LoginException("Bad characters in password");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -54,8 +54,8 @@ public class GhidraServerAWTTest extends AbstractGenericTest {
|
||||||
|
|
||||||
// directly instantiate to avoid GhidraServer.main which may
|
// directly instantiate to avoid GhidraServer.main which may
|
||||||
// invoke System.exit
|
// invoke System.exit
|
||||||
GhidraServer server =
|
GhidraServer server = new GhidraServer(myTmpDir, GhidraServer.AUTH_MODE.NO_AUTH_LOGIN,
|
||||||
new GhidraServer(myTmpDir, GhidraServer.NO_AUTH_LOGIN, null, true, true, -1, true);
|
null, true, true, -1, true, false);
|
||||||
|
|
||||||
// exercise server elements, including a repository and buffer file
|
// exercise server elements, including a repository and buffer file
|
||||||
RepositoryManager mgr = (RepositoryManager) getInstanceField("mgr", server);
|
RepositoryManager mgr = (RepositoryManager) getInstanceField("mgr", server);
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.util.timer;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
import ghidra.util.Msg;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reusable watchdog that will execute a callback if the watchdog is not disarmed before
|
||||||
|
* it expires.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class Watchdog implements Closeable {
|
||||||
|
private long defaultWatchdogTimeoutMS;
|
||||||
|
private AtomicLong watchdogExpiresAt = new AtomicLong();
|
||||||
|
private Runnable timeoutMethod;
|
||||||
|
private GTimerMonitor watchdogTimer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a watchdog (initially disarmed) that will poll for expiration every
|
||||||
|
* defaultTimeoutMS milliseconds, calling {@code timeoutMethod} when triggered.
|
||||||
|
* <p>
|
||||||
|
* @param defaultTimeoutMS number of milliseconds that the watchdog will wait after
|
||||||
|
* being armed before calling the timeout method.
|
||||||
|
* @param timeoutMethod {@link Runnable} functional callback.
|
||||||
|
*/
|
||||||
|
public Watchdog(long defaultTimeoutMS, Runnable timeoutMethod) {
|
||||||
|
this.defaultWatchdogTimeoutMS = defaultTimeoutMS;
|
||||||
|
this.timeoutMethod = timeoutMethod;
|
||||||
|
this.watchdogTimer = GTimer.scheduleRepeatingRunnable(defaultTimeoutMS, defaultTimeoutMS,
|
||||||
|
this::watchdogWorker);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void finalize() {
|
||||||
|
if (watchdogTimer != null) {
|
||||||
|
close();
|
||||||
|
Msg.warn(this, "Unclosed Watchdog");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Releases the background timer that this watchdog uses.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
if (watchdogTimer != null) {
|
||||||
|
watchdogTimer.cancel();
|
||||||
|
}
|
||||||
|
watchdogTimer = null;
|
||||||
|
timeoutMethod = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called from a timer, checks to see if the watchdog is armed, and if it has expired.
|
||||||
|
* <p>
|
||||||
|
* Disarms itself before calling the timeoutMethod if the timeout period expired.
|
||||||
|
*/
|
||||||
|
private void watchdogWorker() {
|
||||||
|
long expiresAt = watchdogExpiresAt.get();
|
||||||
|
if (expiresAt > 0) {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
if (now > expiresAt) {
|
||||||
|
setEnabled(false);
|
||||||
|
timeoutMethod.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setEnabled(boolean b) {
|
||||||
|
watchdogExpiresAt.set(b ? System.currentTimeMillis() + defaultWatchdogTimeoutMS : -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return watchdogExpiresAt.get() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables this watchdog so that at {@link #defaultWatchdogTimeoutMS} milliseconds in the
|
||||||
|
* future the {@link #timeoutMethod} will be called.
|
||||||
|
*/
|
||||||
|
public void arm() {
|
||||||
|
setEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disables this watchdog.
|
||||||
|
*/
|
||||||
|
public void disarm() {
|
||||||
|
setEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -25,6 +25,7 @@ import java.nio.file.FileSystem;
|
||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
import java.text.NumberFormat;
|
import java.text.NumberFormat;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import generic.jar.ResourceFile;
|
import generic.jar.ResourceFile;
|
||||||
import ghidra.util.*;
|
import ghidra.util.*;
|
||||||
|
@ -1214,4 +1215,30 @@ public final class FileUtilities {
|
||||||
}
|
}
|
||||||
Desktop.getDesktop().open(file);
|
Desktop.getDesktop().open(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void asyncForEachLine(InputStream is, Consumer<String> consumer) {
|
||||||
|
asyncForEachLine(new BufferedReader(new InputStreamReader(is)), consumer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void asyncForEachLine(BufferedReader reader, Consumer<String> consumer) {
|
||||||
|
new Thread(() -> {
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
String line = reader.readLine();
|
||||||
|
if (line == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
consumer.accept(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException ioe) {
|
||||||
|
// ignore io errors while reading because thats normal when hitting EOF
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
Msg.error(FileUtilities.class, "Exception while reading", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}, "Threaded Stream Reader Thread").start();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
// Example JAAS config file for Ghidra server when operating in -a4 authmode.
|
||||||
|
// Ghidra only uses the "auth" section from the JAAS configuration.
|
||||||
|
// You may need to adjust the PROGRAM="" to include the full path to the example script
|
||||||
|
|
||||||
|
auth {
|
||||||
|
ghidra.server.security.loginmodule.ExternalProgramLoginModule required
|
||||||
|
PROGRAM="server/jaas/jaas_external_program.example.sh"
|
||||||
|
TIMEOUT="1000"
|
||||||
|
ARG_00="arg1" ARG_01="test arg2"
|
||||||
|
;
|
||||||
|
};
|
||||||
|
|
28
Ghidra/RuntimeScripts/Common/server/jaas/jaas_external_program.example.sh
Executable file
28
Ghidra/RuntimeScripts/Common/server/jaas/jaas_external_program.example.sh
Executable file
|
@ -0,0 +1,28 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# This is a trivial example to show how the Ghidra ExternalProgramLoginModule
|
||||||
|
# communicates with the external authenticator.
|
||||||
|
#
|
||||||
|
# The username and password will be supplied on STDIN separated by a newline.
|
||||||
|
# No other data will be sent on STDIN.
|
||||||
|
#
|
||||||
|
# The external authenticator (this script) needs to exit with 0 (zero) error level
|
||||||
|
# if the authentication was successful, or a non-zero error level if not successful.
|
||||||
|
#
|
||||||
|
|
||||||
|
echo "Starting example JAAS external auth script" 1>&2
|
||||||
|
|
||||||
|
read NAME
|
||||||
|
read PASSWORD
|
||||||
|
|
||||||
|
|
||||||
|
if [[ ${NAME} =~ "bad" ]]
|
||||||
|
then
|
||||||
|
echo "Login failed: username has 'bad' in it: $NAME" 1>&2
|
||||||
|
exit 100
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Returning from script" 1>&2
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
// Example JAAS config file to use the local Linux PAM system when operating in -a4 authmode.
|
||||||
|
// JPAM is not included in the Ghidra distro.
|
||||||
|
// Additionally:
|
||||||
|
// the libjpam.so native library needs to be copied to your ${JAVA_HOME}/lib directory.
|
||||||
|
// the JPAM-x.y.jar java library needs to be inserted into the GhidraServer's classpath.
|
||||||
|
|
||||||
|
auth {
|
||||||
|
net.sf.jpam.jaas.JpamLoginModule required
|
||||||
|
// the serviceName parameter controls which PAM service Ghidra will try to authenticate against
|
||||||
|
serviceName="system-auth"
|
||||||
|
;
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
// Example JAAS config file to use an Active Directory LDAP server to authenticate users when operating in -a4 authmode.
|
||||||
|
//
|
||||||
|
// The special string "{USERNAME}" in the authIdentity and userFilter parameters is replaced with the Ghidra user's name
|
||||||
|
// at runtime by the LdapLoginModule, and should not be modified.
|
||||||
|
//
|
||||||
|
// The ldap DNS hostname for your Active Directory server needs to be fixed-up in the userProvider parameter,
|
||||||
|
// and the domain name portion of your user's identity (ie. user@domain.tld) needs to be fixed up in the
|
||||||
|
// authIdentity parameter.
|
||||||
|
//
|
||||||
|
// In this mode, the Ghidra Server will bind to the LDAP server using the Ghidra user's name and password. It will
|
||||||
|
// then query for that same user (sAMAccountName={USERNAME}) to confirm that user's DN.
|
||||||
|
//
|
||||||
|
// See https://docs.oracle.com/javase/8/docs/jre/api/security/jaas/spec/com/sun/security/auth/module/LdapLoginModule.html
|
||||||
|
// for more information about the LdapLoginModule and its configuration.
|
||||||
|
//
|
||||||
|
auth {
|
||||||
|
com.sun.security.auth.module.LdapLoginModule REQUIRED
|
||||||
|
userProvider="ldaps://<your_active_directory_ldap_server_hostname>:3269"
|
||||||
|
authIdentity="{USERNAME}@<your_active_directory_domain_name>"
|
||||||
|
userFilter="(sAMAccountName={USERNAME})"
|
||||||
|
debug=true;
|
||||||
|
};
|
|
@ -106,7 +106,7 @@ ghidra.repositories.dir=./repositories
|
||||||
# Ghidra server startup parameters.
|
# Ghidra server startup parameters.
|
||||||
#
|
#
|
||||||
# Command line parameters: (Add command line parameters as needed and renumber each starting from .1)
|
# Command line parameters: (Add command line parameters as needed and renumber each starting from .1)
|
||||||
# [-ip <hostname>] [-i ###.###.###.###] [-p#] [-a#] [-anonymous] [-ssh] [-d<ntDomain>] [-e<days>] [-u] [-n] <repositories_path>
|
# [-ip <hostname>] [-i ###.###.###.###] [-p#] [-a#] [-anonymous] [-ssh] [-d<ntDomain>] [-e<days>] [-u] [-jaas <config_file>] [-autoProvision] [-n] <repositories_path>
|
||||||
#
|
#
|
||||||
# -ip <hostname> : remote access hostname or IPv4 address to be used by clients
|
# -ip <hostname> : remote access hostname or IPv4 address to be used by clients
|
||||||
# -i #.#.#.# : interface IPv4 address to accept connections on (default all interfaces)
|
# -i #.#.#.# : interface IPv4 address to accept connections on (default all interfaces)
|
||||||
|
@ -114,10 +114,15 @@ ghidra.repositories.dir=./repositories
|
||||||
# -a# : an optional authentication mode where # is a value 0 or 2
|
# -a# : an optional authentication mode where # is a value 0 or 2
|
||||||
# 0 - Private user password
|
# 0 - Private user password
|
||||||
# 2 - PKI Authentication
|
# 2 - PKI Authentication
|
||||||
|
# 4 - JAAS Authentication
|
||||||
# -anonymous : enables anonymous repository access (see svrREADME.html for details)
|
# -anonymous : enables anonymous repository access (see svrREADME.html for details)
|
||||||
# -ssh : enables SSH authentication for headless clients
|
# -ssh : enables SSH authentication for headless clients
|
||||||
# -e<days> : specifies default password expiration time in days (-a0 mode only, default is 1-day)
|
# -e<days> : specifies default password expiration time in days (-a0 mode only, default is 1-day)
|
||||||
# -u : enable users to be prompted for user ID (does not apply to -a2 PKI mode)
|
# -u : enable users to be prompted for user ID (does not apply to -a2 PKI mode)
|
||||||
|
# -jaas <path_to_config_file> : specifies JAAS config file.
|
||||||
|
# -autoProvision : enable the auto-creation of Ghidra users when the authenticator module
|
||||||
|
# (ie. OS or other authentication method specified by JAAS) authenticates
|
||||||
|
# a new unknown user.
|
||||||
# -n : enable reverse name lookup for IP addresses when logging (requires proper configuration
|
# -n : enable reverse name lookup for IP addresses when logging (requires proper configuration
|
||||||
# of reverse lookup by your DNS server)
|
# of reverse lookup by your DNS server)
|
||||||
# ${ghidra.repositories.dir} : config variable (defined above) which identifies the directory
|
# ${ghidra.repositories.dir} : config variable (defined above) which identifies the directory
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
##VERSION: 2.0
|
##VERSION: 2.0
|
||||||
##MODULE IP: Copyright Distribution Permitted
|
##MODULE IP: Copyright Distribution Permitted
|
||||||
|
Common/server/jaas/jaas_external_program.example.conf||GHIDRA||||END|
|
||||||
|
Common/server/jaas/jaas_external_program.example.sh||GHIDRA||||END|
|
||||||
|
Common/server/jaas/jaas_jpam.example.conf||GHIDRA||||END|
|
||||||
|
Common/server/jaas/jaas_ldap_ad.example.conf||GHIDRA||||END|
|
||||||
Common/server/server.conf||GHIDRA||||END|
|
Common/server/server.conf||GHIDRA||||END|
|
||||||
Common/server/svrREADME.html||GHIDRA||||END|
|
Common/server/svrREADME.html||GHIDRA||||END|
|
||||||
Common/support/analyzeHeadlessREADME.html||GHIDRA||||END|
|
Common/support/analyzeHeadlessREADME.html||GHIDRA||||END|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue