mirror of
https://github.com/hierynomus/sshj.git
synced 2025-12-06 07:10:53 +03:00
Add simple test for AuthGssApiWithMic
Mock enough of the JGSS API to avoid needing a real Kerberos environment. I'm not sure how accurate this is, but it should test that the client is sending the correct packets in the corect order.
This commit is contained in:
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.transport.TransportException;
|
||||
import net.schmizz.sshj.userauth.UserAuthException;
|
||||
|
||||
import org.apache.sshd.SshServer;
|
||||
import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
|
||||
import org.apache.sshd.server.PasswordAuthenticator;
|
||||
import org.apache.sshd.server.auth.gss.GSSAuthenticator;
|
||||
import org.apache.sshd.server.session.ServerSession;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -50,6 +52,8 @@ public class BasicFixture {
|
||||
public static final String hostname = "localhost";
|
||||
public final int port = gimmeAPort();
|
||||
|
||||
private GSSAuthenticator gssAuthenticator;
|
||||
|
||||
private SSHClient client;
|
||||
private SshServer server;
|
||||
|
||||
@@ -99,6 +103,7 @@ public class BasicFixture {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
server.setGSSAuthenticator(gssAuthenticator);
|
||||
server.start();
|
||||
serverRunning = true;
|
||||
}
|
||||
@@ -137,6 +142,10 @@ public class BasicFixture {
|
||||
return client;
|
||||
}
|
||||
|
||||
public void setGssAuthenticator(GSSAuthenticator gssAuthenticator) {
|
||||
this.gssAuthenticator = gssAuthenticator;
|
||||
}
|
||||
|
||||
public void dummyAuth()
|
||||
throws UserAuthException, TransportException {
|
||||
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