mirror of
https://github.com/hierynomus/sshj.git
synced 2025-12-06 15:20:54 +03:00
Merge pull request #195 from bluekeyes/feature/gss-api
Add support for "gssapi-with-mic" authentication (Kerberos)
This commit is contained in:
@@ -49,6 +49,7 @@ import net.schmizz.sshj.userauth.keyprovider.KeyFormat;
|
|||||||
import net.schmizz.sshj.userauth.keyprovider.KeyPairWrapper;
|
import net.schmizz.sshj.userauth.keyprovider.KeyPairWrapper;
|
||||||
import net.schmizz.sshj.userauth.keyprovider.KeyProvider;
|
import net.schmizz.sshj.userauth.keyprovider.KeyProvider;
|
||||||
import net.schmizz.sshj.userauth.keyprovider.KeyProviderUtil;
|
import net.schmizz.sshj.userauth.keyprovider.KeyProviderUtil;
|
||||||
|
import net.schmizz.sshj.userauth.method.AuthGssApiWithMic;
|
||||||
import net.schmizz.sshj.userauth.method.AuthKeyboardInteractive;
|
import net.schmizz.sshj.userauth.method.AuthKeyboardInteractive;
|
||||||
import net.schmizz.sshj.userauth.method.AuthMethod;
|
import net.schmizz.sshj.userauth.method.AuthMethod;
|
||||||
import net.schmizz.sshj.userauth.method.AuthPassword;
|
import net.schmizz.sshj.userauth.method.AuthPassword;
|
||||||
@@ -58,15 +59,19 @@ import net.schmizz.sshj.userauth.password.PasswordFinder;
|
|||||||
import net.schmizz.sshj.userauth.password.PasswordUtils;
|
import net.schmizz.sshj.userauth.password.PasswordUtils;
|
||||||
import net.schmizz.sshj.userauth.password.Resource;
|
import net.schmizz.sshj.userauth.password.Resource;
|
||||||
import net.schmizz.sshj.xfer.scp.SCPFileTransfer;
|
import net.schmizz.sshj.xfer.scp.SCPFileTransfer;
|
||||||
|
import org.ietf.jgss.Oid;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.security.auth.login.LoginContext;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.ServerSocket;
|
import java.net.ServerSocket;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Deque;
|
import java.util.Deque;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
@@ -365,6 +370,30 @@ public class SSHClient
|
|||||||
authPublickey(username, keyProviders);
|
authPublickey(username, keyProviders);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authenticate {@code username} using the {@code "gssapi-with-mic"} authentication method, given a login context
|
||||||
|
* for the peer GSS machine and a list of supported OIDs.
|
||||||
|
* <p/>
|
||||||
|
* Supported OIDs should be ordered by preference as the SSH server will choose the first OID that it also
|
||||||
|
* supports. At least one OID is required
|
||||||
|
*
|
||||||
|
* @param username user to authenticate
|
||||||
|
* @param context {@code LoginContext} for the peer GSS machine
|
||||||
|
* @param supportedOid first supported OID
|
||||||
|
* @param supportedOids other supported OIDs
|
||||||
|
*
|
||||||
|
* @throws UserAuthException in case of authentication failure
|
||||||
|
* @throws TransportException if there was a transport-layer error
|
||||||
|
*/
|
||||||
|
public void authGssApiWithMic(String username, LoginContext context, Oid supportedOid, Oid... supportedOids)
|
||||||
|
throws UserAuthException, TransportException {
|
||||||
|
// insert supportedOid to the front of the list since ordering matters
|
||||||
|
List<Oid> oids = new ArrayList<Oid>(Arrays.asList(supportedOids));
|
||||||
|
oids.add(0, supportedOid);
|
||||||
|
|
||||||
|
auth(username, new AuthGssApiWithMic(context, oids));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disconnects from the connected SSH server. {@code SSHClient} objects are not reusable therefore it is incorrect
|
* Disconnects from the connected SSH server. {@code SSHClient} objects are not reusable therefore it is incorrect
|
||||||
* to attempt connection after this method has been called.
|
* to attempt connection after this method has been called.
|
||||||
|
|||||||
@@ -46,6 +46,9 @@ public enum Message {
|
|||||||
USERAUTH_60(60),
|
USERAUTH_60(60),
|
||||||
USERAUTH_INFO_RESPONSE(61),
|
USERAUTH_INFO_RESPONSE(61),
|
||||||
|
|
||||||
|
USERAUTH_GSSAPI_EXCHANGE_COMPLETE(63),
|
||||||
|
USERAUTH_GSSAPI_MIC(66),
|
||||||
|
|
||||||
GLOBAL_REQUEST(80),
|
GLOBAL_REQUEST(80),
|
||||||
REQUEST_SUCCESS(81),
|
REQUEST_SUCCESS(81),
|
||||||
REQUEST_FAILURE(82),
|
REQUEST_FAILURE(82),
|
||||||
|
|||||||
@@ -0,0 +1,188 @@
|
|||||||
|
package net.schmizz.sshj.userauth.method;
|
||||||
|
|
||||||
|
import java.security.PrivilegedActionException;
|
||||||
|
import java.security.PrivilegedExceptionAction;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.security.auth.Subject;
|
||||||
|
import javax.security.auth.login.LoginContext;
|
||||||
|
|
||||||
|
import org.ietf.jgss.GSSContext;
|
||||||
|
import org.ietf.jgss.GSSCredential;
|
||||||
|
import org.ietf.jgss.GSSException;
|
||||||
|
import org.ietf.jgss.GSSManager;
|
||||||
|
import org.ietf.jgss.GSSName;
|
||||||
|
import org.ietf.jgss.Oid;
|
||||||
|
|
||||||
|
import net.schmizz.sshj.common.Buffer.BufferException;
|
||||||
|
import net.schmizz.sshj.common.Buffer.PlainBuffer;
|
||||||
|
import net.schmizz.sshj.common.Message;
|
||||||
|
import net.schmizz.sshj.common.SSHPacket;
|
||||||
|
import net.schmizz.sshj.transport.TransportException;
|
||||||
|
import net.schmizz.sshj.userauth.UserAuthException;
|
||||||
|
|
||||||
|
/** Implements authentication by GSS-API. */
|
||||||
|
public class AuthGssApiWithMic
|
||||||
|
extends AbstractAuthMethod {
|
||||||
|
|
||||||
|
private final LoginContext loginContext;
|
||||||
|
private final List<Oid> mechanismOids;
|
||||||
|
private final GSSManager manager;
|
||||||
|
|
||||||
|
private GSSContext secContext;
|
||||||
|
|
||||||
|
public AuthGssApiWithMic(LoginContext loginContext, List<Oid> mechanismOids) {
|
||||||
|
this(loginContext, mechanismOids, GSSManager.getInstance());
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthGssApiWithMic(LoginContext loginContext, List<Oid> mechanismOids, GSSManager manager) {
|
||||||
|
super("gssapi-with-mic");
|
||||||
|
this.loginContext = loginContext;
|
||||||
|
this.mechanismOids = mechanismOids;
|
||||||
|
this.manager = manager;
|
||||||
|
|
||||||
|
secContext = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SSHPacket buildReq()
|
||||||
|
throws UserAuthException {
|
||||||
|
SSHPacket packet = super.buildReq() // the generic stuff
|
||||||
|
.putUInt32(mechanismOids.size()); // number of OIDs we support
|
||||||
|
for (Oid oid : mechanismOids) {
|
||||||
|
try {
|
||||||
|
packet.putString(oid.getDER());
|
||||||
|
} catch (GSSException e) {
|
||||||
|
throw new UserAuthException("Mechanism OID could not be encoded: " + oid.toString(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return packet;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PrivilegedExceptionAction to be executed within the given LoginContext for
|
||||||
|
* initializing the GSSContext.
|
||||||
|
*
|
||||||
|
* @author Ben Hamme
|
||||||
|
*/
|
||||||
|
private class InitializeContextAction implements PrivilegedExceptionAction<GSSContext> {
|
||||||
|
|
||||||
|
private final Oid selectedOid;
|
||||||
|
|
||||||
|
public InitializeContextAction(Oid selectedOid) {
|
||||||
|
this.selectedOid = selectedOid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GSSContext run() throws GSSException {
|
||||||
|
GSSName clientName = manager.createName(params.getUsername(), GSSName.NT_USER_NAME);
|
||||||
|
GSSCredential clientCreds = manager.createCredential(clientName, GSSContext.DEFAULT_LIFETIME, selectedOid, GSSCredential.INITIATE_ONLY);
|
||||||
|
GSSName peerName = manager.createName("host@" + params.getTransport().getRemoteHost(), GSSName.NT_HOSTBASED_SERVICE);
|
||||||
|
|
||||||
|
GSSContext context = manager.createContext(peerName, selectedOid, clientCreds, GSSContext.DEFAULT_LIFETIME);
|
||||||
|
context.requestMutualAuth(true);
|
||||||
|
context.requestInteg(true);
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendToken(byte[] token) throws TransportException {
|
||||||
|
SSHPacket packet = new SSHPacket(Message.USERAUTH_INFO_RESPONSE).putString(token);
|
||||||
|
params.getTransport().write(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleContextInitialization(SSHPacket buf)
|
||||||
|
throws UserAuthException, TransportException {
|
||||||
|
byte[] bytes;
|
||||||
|
try {
|
||||||
|
bytes = buf.readBytes();
|
||||||
|
} catch (BufferException e) {
|
||||||
|
throw new UserAuthException("Failed to read byte array from message buffer", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
Oid selectedOid;
|
||||||
|
try {
|
||||||
|
selectedOid = new Oid(bytes);
|
||||||
|
} catch (GSSException e) {
|
||||||
|
throw new UserAuthException("Exception constructing OID from server response", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("Server selected OID: {}", selectedOid.toString());
|
||||||
|
log.debug("Initializing GSSAPI context");
|
||||||
|
|
||||||
|
Subject subject = loginContext.getSubject();
|
||||||
|
|
||||||
|
try {
|
||||||
|
secContext = Subject.doAs(subject, new InitializeContextAction(selectedOid));
|
||||||
|
} catch (PrivilegedActionException e) {
|
||||||
|
throw new UserAuthException("Exception during context initialization", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("Sending initial token");
|
||||||
|
byte[] inToken = new byte[0];
|
||||||
|
try {
|
||||||
|
byte[] outToken = secContext.initSecContext(inToken, 0, inToken.length);
|
||||||
|
sendToken(outToken);
|
||||||
|
} catch (GSSException e) {
|
||||||
|
throw new UserAuthException("Exception sending initial token", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] handleTokenFromServer(SSHPacket buf) throws UserAuthException {
|
||||||
|
byte[] token;
|
||||||
|
|
||||||
|
try {
|
||||||
|
token = buf.readStringAsBytes();
|
||||||
|
} catch (BufferException e) {
|
||||||
|
throw new UserAuthException("Failed to read string from message buffer", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return secContext.initSecContext(token, 0, token.length);
|
||||||
|
} catch (GSSException e) {
|
||||||
|
throw new UserAuthException("Exception during token exchange", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] generateMIC() throws UserAuthException {
|
||||||
|
byte[] msg = new PlainBuffer().putString(params.getTransport().getSessionID())
|
||||||
|
.putByte(Message.USERAUTH_REQUEST.toByte())
|
||||||
|
.putString(params.getUsername())
|
||||||
|
.putString(params.getNextServiceName())
|
||||||
|
.putString(getName())
|
||||||
|
.getCompactData();
|
||||||
|
|
||||||
|
try {
|
||||||
|
return secContext.getMIC(msg, 0, msg.length, null);
|
||||||
|
} catch (GSSException e) {
|
||||||
|
throw new UserAuthException("Exception getting message integrity code", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(Message cmd, SSHPacket buf)
|
||||||
|
throws UserAuthException, TransportException {
|
||||||
|
if (cmd == Message.USERAUTH_60) {
|
||||||
|
handleContextInitialization(buf);
|
||||||
|
} else if (cmd == Message.USERAUTH_INFO_RESPONSE) {
|
||||||
|
byte[] token = handleTokenFromServer(buf);
|
||||||
|
|
||||||
|
if (!secContext.isEstablished()) {
|
||||||
|
log.debug("Sending token");
|
||||||
|
sendToken(token);
|
||||||
|
} else {
|
||||||
|
if (secContext.getIntegState()) {
|
||||||
|
log.debug("Per-message integrity protection available: finalizing authentication with message integrity code");
|
||||||
|
params.getTransport().write(new SSHPacket(Message.USERAUTH_GSSAPI_MIC).putString(generateMIC()));
|
||||||
|
} else {
|
||||||
|
log.debug("Per-message integrity protection unavailable: finalizing authentication");
|
||||||
|
params.getTransport().write(new SSHPacket(Message.USERAUTH_GSSAPI_EXCHANGE_COMPLETE));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
super.handle(cmd, buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
66
src/test/java/net/schmizz/sshj/userauth/GssApiTest.java
Normal file
66
src/test/java/net/schmizz/sshj/userauth/GssApiTest.java
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package net.schmizz.sshj.userauth;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import javax.security.auth.login.AppConfigurationEntry;
|
||||||
|
import javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag;
|
||||||
|
import javax.security.auth.login.Configuration;
|
||||||
|
import javax.security.auth.login.LoginContext;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import net.schmizz.sshj.userauth.method.AuthGssApiWithMic;
|
||||||
|
import net.schmizz.sshj.util.BasicFixture;
|
||||||
|
import net.schmizz.sshj.util.gss.BogusGSSAuthenticator;
|
||||||
|
import net.schmizz.sshj.util.gss.BogusGSSManager;
|
||||||
|
|
||||||
|
public class GssApiTest {
|
||||||
|
|
||||||
|
private static final String LOGIN_CONTEXT_NAME = "TestLoginContext";
|
||||||
|
|
||||||
|
private static class TestAuthConfiguration extends Configuration {
|
||||||
|
private AppConfigurationEntry entry = new AppConfigurationEntry(
|
||||||
|
"testLoginModule",
|
||||||
|
LoginModuleControlFlag.REQUIRED,
|
||||||
|
Collections.<String, Object> emptyMap());
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
|
||||||
|
if (name.equals(LOGIN_CONTEXT_NAME)) {
|
||||||
|
return new AppConfigurationEntry[] { entry };
|
||||||
|
} else {
|
||||||
|
return new AppConfigurationEntry[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final BasicFixture fixture = new BasicFixture();
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
fixture.setGssAuthenticator(new BogusGSSAuthenticator());
|
||||||
|
fixture.init(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() throws IOException, InterruptedException {
|
||||||
|
fixture.done();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticated() throws Exception {
|
||||||
|
AuthGssApiWithMic authMethod = new AuthGssApiWithMic(
|
||||||
|
new LoginContext(LOGIN_CONTEXT_NAME, null, null, new TestAuthConfiguration()),
|
||||||
|
Collections.singletonList(BogusGSSManager.KRB5_MECH),
|
||||||
|
new BogusGSSManager());
|
||||||
|
|
||||||
|
fixture.getClient().auth("user", authMethod);
|
||||||
|
assertTrue(fixture.getClient().isAuthenticated());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -33,9 +33,11 @@ package net.schmizz.sshj.util;
|
|||||||
import net.schmizz.sshj.SSHClient;
|
import net.schmizz.sshj.SSHClient;
|
||||||
import net.schmizz.sshj.transport.TransportException;
|
import net.schmizz.sshj.transport.TransportException;
|
||||||
import net.schmizz.sshj.userauth.UserAuthException;
|
import net.schmizz.sshj.userauth.UserAuthException;
|
||||||
|
|
||||||
import org.apache.sshd.SshServer;
|
import org.apache.sshd.SshServer;
|
||||||
import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
|
import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
|
||||||
import org.apache.sshd.server.PasswordAuthenticator;
|
import org.apache.sshd.server.PasswordAuthenticator;
|
||||||
|
import org.apache.sshd.server.auth.gss.GSSAuthenticator;
|
||||||
import org.apache.sshd.server.session.ServerSession;
|
import org.apache.sshd.server.session.ServerSession;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -50,6 +52,8 @@ public class BasicFixture {
|
|||||||
public static final String hostname = "localhost";
|
public static final String hostname = "localhost";
|
||||||
public final int port = gimmeAPort();
|
public final int port = gimmeAPort();
|
||||||
|
|
||||||
|
private GSSAuthenticator gssAuthenticator;
|
||||||
|
|
||||||
private SSHClient client;
|
private SSHClient client;
|
||||||
private SshServer server;
|
private SshServer server;
|
||||||
|
|
||||||
@@ -99,6 +103,7 @@ public class BasicFixture {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
server.setGSSAuthenticator(gssAuthenticator);
|
||||||
server.start();
|
server.start();
|
||||||
serverRunning = true;
|
serverRunning = true;
|
||||||
}
|
}
|
||||||
@@ -137,6 +142,10 @@ public class BasicFixture {
|
|||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setGssAuthenticator(GSSAuthenticator gssAuthenticator) {
|
||||||
|
this.gssAuthenticator = gssAuthenticator;
|
||||||
|
}
|
||||||
|
|
||||||
public void dummyAuth()
|
public void dummyAuth()
|
||||||
throws UserAuthException, TransportException {
|
throws UserAuthException, TransportException {
|
||||||
server.setPasswordAuthenticator(new BogusPasswordAuthenticator());
|
server.setPasswordAuthenticator(new BogusPasswordAuthenticator());
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package net.schmizz.sshj.util.gss;
|
||||||
|
|
||||||
|
import org.apache.sshd.server.auth.gss.GSSAuthenticator;
|
||||||
|
import org.ietf.jgss.GSSCredential;
|
||||||
|
import org.ietf.jgss.GSSException;
|
||||||
|
import org.ietf.jgss.GSSManager;
|
||||||
|
|
||||||
|
public class BogusGSSAuthenticator
|
||||||
|
extends GSSAuthenticator {
|
||||||
|
|
||||||
|
private final GSSManager manager = new BogusGSSManager();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GSSManager getGSSManager() {
|
||||||
|
return manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GSSCredential getGSSCredential(GSSManager mgr) throws GSSException {
|
||||||
|
return manager.createCredential(GSSCredential.ACCEPT_ONLY);
|
||||||
|
}
|
||||||
|
}
|
||||||
243
src/test/java/net/schmizz/sshj/util/gss/BogusGSSContext.java
Normal file
243
src/test/java/net/schmizz/sshj/util/gss/BogusGSSContext.java
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
package net.schmizz.sshj.util.gss;
|
||||||
|
|
||||||
|
import static net.schmizz.sshj.util.gss.BogusGSSManager.unavailable;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import org.ietf.jgss.ChannelBinding;
|
||||||
|
import org.ietf.jgss.GSSContext;
|
||||||
|
import org.ietf.jgss.GSSCredential;
|
||||||
|
import org.ietf.jgss.GSSException;
|
||||||
|
import org.ietf.jgss.GSSName;
|
||||||
|
import org.ietf.jgss.MessageProp;
|
||||||
|
import org.ietf.jgss.Oid;
|
||||||
|
|
||||||
|
public class BogusGSSContext
|
||||||
|
implements GSSContext {
|
||||||
|
|
||||||
|
private static final byte[] INIT_TOKEN = fromString("INIT");
|
||||||
|
private static final byte[] ACCEPT_TOKEN = fromString("ACCEPT");
|
||||||
|
private static final byte[] MIC = fromString("LGTM");
|
||||||
|
|
||||||
|
private static byte[] fromString(String s) {
|
||||||
|
return s.getBytes(Charset.forName("UTF-8"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean initialized = false;
|
||||||
|
private boolean accepted = false;
|
||||||
|
private boolean integState = false;
|
||||||
|
private boolean mutualAuthState = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] initSecContext(byte[] inputBuf, int offset, int len) throws GSSException {
|
||||||
|
initialized = true;
|
||||||
|
return INIT_TOKEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int initSecContext(InputStream inStream, OutputStream outStream) throws GSSException {
|
||||||
|
throw unavailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] acceptSecContext(byte[] inToken, int offset, int len) throws GSSException {
|
||||||
|
accepted = Arrays.equals(INIT_TOKEN, Arrays.copyOfRange(inToken, offset, offset + len));
|
||||||
|
return ACCEPT_TOKEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void acceptSecContext(InputStream inStream, OutputStream outStream) throws GSSException {
|
||||||
|
throw unavailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEstablished() {
|
||||||
|
return initialized || accepted;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() throws GSSException {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getWrapSizeLimit(int qop, boolean confReq, int maxTokenSize) throws GSSException {
|
||||||
|
throw unavailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] wrap(byte[] inBuf, int offset, int len, MessageProp msgProp) throws GSSException {
|
||||||
|
throw unavailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void wrap(InputStream inStream, OutputStream outStream, MessageProp msgProp) throws GSSException {
|
||||||
|
throw unavailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] unwrap(byte[] inBuf, int offset, int len, MessageProp msgProp) throws GSSException {
|
||||||
|
throw unavailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unwrap(InputStream inStream, OutputStream outStream, MessageProp msgProp) throws GSSException {
|
||||||
|
throw unavailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getMIC(byte[] inMsg, int offset, int len, MessageProp msgProp) throws GSSException {
|
||||||
|
return MIC;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void getMIC(InputStream inStream, OutputStream outStream, MessageProp msgProp) throws GSSException {
|
||||||
|
throw unavailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void verifyMIC(byte[] inToken, int tokOffset, int tokLen, byte[] inMsg, int msgOffset, int msgLen, MessageProp msgProp) throws GSSException {
|
||||||
|
if (!Arrays.equals(MIC, Arrays.copyOfRange(inToken, tokOffset, tokOffset + tokLen))) {
|
||||||
|
throw new GSSException(GSSException.BAD_MIC);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void verifyMIC(InputStream tokStream, InputStream msgStream, MessageProp msgProp) throws GSSException {
|
||||||
|
throw unavailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] export() throws GSSException {
|
||||||
|
throw unavailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void requestMutualAuth(boolean state) throws GSSException {
|
||||||
|
this.mutualAuthState = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void requestInteg(boolean state) throws GSSException {
|
||||||
|
this.integState = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void requestReplayDet(boolean state) throws GSSException {
|
||||||
|
throw unavailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void requestSequenceDet(boolean state) throws GSSException {
|
||||||
|
throw unavailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void requestCredDeleg(boolean state) throws GSSException {
|
||||||
|
throw unavailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void requestAnonymity(boolean state) throws GSSException {
|
||||||
|
throw unavailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void requestConf(boolean state) throws GSSException {
|
||||||
|
throw unavailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void requestLifetime(int lifetime) throws GSSException {
|
||||||
|
throw unavailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setChannelBinding(ChannelBinding cb) throws GSSException {
|
||||||
|
throw unavailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getMutualAuthState() {
|
||||||
|
return mutualAuthState;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getIntegState() {
|
||||||
|
return integState;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getCredDelegState() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getReplayDetState() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getSequenceDetState() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getAnonymityState() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTransferable() throws GSSException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isProtReady() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getConfState() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLifetime() {
|
||||||
|
return INDEFINITE_LIFETIME;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GSSName getSrcName() throws GSSException {
|
||||||
|
try {
|
||||||
|
String hostname = InetAddress.getLocalHost().getCanonicalHostName();
|
||||||
|
return new BogusGSSName("user@" + hostname, GSSName.NT_HOSTBASED_SERVICE);
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GSSName getTargName() throws GSSException {
|
||||||
|
throw unavailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Oid getMech() throws GSSException {
|
||||||
|
return BogusGSSManager.KRB5_MECH;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GSSCredential getDelegCred() throws GSSException {
|
||||||
|
throw unavailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInitiator() throws GSSException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package net.schmizz.sshj.util.gss;
|
||||||
|
|
||||||
|
import static net.schmizz.sshj.util.gss.BogusGSSManager.unavailable;
|
||||||
|
|
||||||
|
import org.ietf.jgss.GSSCredential;
|
||||||
|
import org.ietf.jgss.GSSException;
|
||||||
|
import org.ietf.jgss.GSSName;
|
||||||
|
import org.ietf.jgss.Oid;
|
||||||
|
|
||||||
|
public class BogusGSSCredential
|
||||||
|
implements GSSCredential {
|
||||||
|
|
||||||
|
private final GSSName name;
|
||||||
|
private final int usage;
|
||||||
|
|
||||||
|
public BogusGSSCredential(GSSName name, int usage) {
|
||||||
|
this.name = name;
|
||||||
|
this.usage = usage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() throws GSSException {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GSSName getName() throws GSSException {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GSSName getName(Oid mech) throws GSSException {
|
||||||
|
return name.canonicalize(mech);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRemainingLifetime() throws GSSException {
|
||||||
|
return INDEFINITE_LIFETIME;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRemainingInitLifetime(Oid mech) throws GSSException {
|
||||||
|
return INDEFINITE_LIFETIME;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRemainingAcceptLifetime(Oid mech) throws GSSException {
|
||||||
|
return INDEFINITE_LIFETIME;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getUsage() throws GSSException {
|
||||||
|
return usage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getUsage(Oid mech) throws GSSException {
|
||||||
|
return usage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Oid[] getMechs() throws GSSException {
|
||||||
|
return new Oid[] { BogusGSSManager.KRB5_MECH };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void add(GSSName name, int initLifetime, int acceptLifetime, Oid mech, int usage) throws GSSException {
|
||||||
|
throw unavailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Object clone() throws CloneNotSupportedException {
|
||||||
|
return super.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return (name == null ? 0 : name.hashCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (!(obj instanceof BogusGSSCredential)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
GSSName otherName = ((BogusGSSCredential) obj).name;
|
||||||
|
return name == null ? otherName == null : name.equals((Object) otherName);
|
||||||
|
}
|
||||||
|
}
|
||||||
106
src/test/java/net/schmizz/sshj/util/gss/BogusGSSManager.java
Normal file
106
src/test/java/net/schmizz/sshj/util/gss/BogusGSSManager.java
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
package net.schmizz.sshj.util.gss;
|
||||||
|
|
||||||
|
import java.security.Provider;
|
||||||
|
|
||||||
|
import org.apache.sshd.server.auth.gss.UserAuthGSS;
|
||||||
|
import org.ietf.jgss.GSSContext;
|
||||||
|
import org.ietf.jgss.GSSCredential;
|
||||||
|
import org.ietf.jgss.GSSException;
|
||||||
|
import org.ietf.jgss.GSSManager;
|
||||||
|
import org.ietf.jgss.GSSName;
|
||||||
|
import org.ietf.jgss.Oid;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements a fake Kerberos 5 mechanism. MINA only supports Kerberos 5 over
|
||||||
|
* GSS-API, so we can't implement a separate mechanism.
|
||||||
|
*/
|
||||||
|
public class BogusGSSManager
|
||||||
|
extends GSSManager {
|
||||||
|
|
||||||
|
public static final Oid KRB5_MECH = UserAuthGSS.KRB5_MECH;
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(BogusGSSManager.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Oid[] getMechs() {
|
||||||
|
return new Oid[] { KRB5_MECH };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Oid[] getNamesForMech(Oid mech) throws GSSException {
|
||||||
|
return new Oid[] { GSSName.NT_EXPORT_NAME, GSSName.NT_HOSTBASED_SERVICE };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Oid[] getMechsForName(Oid nameType) {
|
||||||
|
return new Oid[] { KRB5_MECH };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GSSName createName(String nameStr, Oid nameType) throws GSSException {
|
||||||
|
return new BogusGSSName(nameStr, nameType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GSSName createName(byte[] name, Oid nameType) throws GSSException {
|
||||||
|
throw unavailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GSSName createName(String nameStr, Oid nameType, Oid mech) throws GSSException {
|
||||||
|
return this.createName(nameStr, nameType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GSSName createName(byte[] name, Oid nameType, Oid mech) throws GSSException {
|
||||||
|
throw unavailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GSSCredential createCredential(int usage) throws GSSException {
|
||||||
|
return new BogusGSSCredential(null, usage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GSSCredential createCredential(GSSName name, int lifetime, Oid mech, int usage) throws GSSException {
|
||||||
|
return new BogusGSSCredential(name, usage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GSSCredential createCredential(GSSName name, int lifetime, Oid[] mechs, int usage) throws GSSException {
|
||||||
|
return new BogusGSSCredential(name, usage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GSSContext createContext(GSSName peer, Oid mech, GSSCredential myCred, int lifetime) throws GSSException {
|
||||||
|
return new BogusGSSContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GSSContext createContext(GSSCredential myCred) throws GSSException {
|
||||||
|
return new BogusGSSContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GSSContext createContext(byte[] interProcessToken) throws GSSException {
|
||||||
|
throw unavailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addProviderAtFront(Provider p, Oid mech) throws GSSException {
|
||||||
|
throw unavailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addProviderAtEnd(Provider p, Oid mech) throws GSSException {
|
||||||
|
throw unavailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
static GSSException unavailable() throws GSSException {
|
||||||
|
GSSException e = new GSSException(GSSException.UNAVAILABLE);
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
58
src/test/java/net/schmizz/sshj/util/gss/BogusGSSName.java
Normal file
58
src/test/java/net/schmizz/sshj/util/gss/BogusGSSName.java
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package net.schmizz.sshj.util.gss;
|
||||||
|
|
||||||
|
import static net.schmizz.sshj.util.gss.BogusGSSManager.unavailable;
|
||||||
|
|
||||||
|
import org.ietf.jgss.GSSException;
|
||||||
|
import org.ietf.jgss.GSSName;
|
||||||
|
import org.ietf.jgss.Oid;
|
||||||
|
|
||||||
|
public class BogusGSSName
|
||||||
|
implements GSSName {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private final Oid oid;
|
||||||
|
|
||||||
|
public BogusGSSName(String name, Oid oid) {
|
||||||
|
this.name = name;
|
||||||
|
this.oid = oid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(GSSName another) throws GSSException {
|
||||||
|
if (!(another instanceof BogusGSSName)) {
|
||||||
|
throw new GSSException(GSSException.BAD_NAMETYPE);
|
||||||
|
}
|
||||||
|
BogusGSSName otherName = (BogusGSSName) another;
|
||||||
|
return name.equals(otherName.name) && oid.equals(otherName.oid);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GSSName canonicalize(Oid mech) throws GSSException {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] export() throws GSSException {
|
||||||
|
throw unavailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Oid getStringNameType() throws GSSException {
|
||||||
|
return oid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAnonymous() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isMN() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user