diff --git a/src/main/java/net/schmizz/sshj/SSHClient.java b/src/main/java/net/schmizz/sshj/SSHClient.java index a384d08e5..a59eaa431 100644 --- a/src/main/java/net/schmizz/sshj/SSHClient.java +++ b/src/main/java/net/schmizz/sshj/SSHClient.java @@ -49,6 +49,7 @@ import net.schmizz.sshj.userauth.keyprovider.KeyPairWrapper; import net.schmizz.sshj.userauth.keyprovider.KeyProvider; 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.AuthMethod; import net.schmizz.sshj.userauth.method.AuthPassword; @@ -58,15 +59,19 @@ import net.schmizz.sshj.userauth.password.PasswordUtils; import net.schmizz.sshj.userauth.password.Resource; import net.schmizz.sshj.xfer.scp.SCPFileTransfer; +import org.ietf.jgss.Oid; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.security.auth.login.LoginContext; + import java.io.Closeable; import java.io.File; import java.io.IOException; import java.net.ServerSocket; import java.security.KeyPair; import java.security.PublicKey; +import java.util.ArrayList; import java.util.Arrays; import java.util.Deque; import java.util.LinkedList; @@ -365,6 +370,30 @@ public void authPublickey(String username, String... locations) 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. + *

+ * 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 oids = new ArrayList(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 * to attempt connection after this method has been called. diff --git a/src/main/java/net/schmizz/sshj/common/Message.java b/src/main/java/net/schmizz/sshj/common/Message.java index 01aba1578..d019be0ef 100644 --- a/src/main/java/net/schmizz/sshj/common/Message.java +++ b/src/main/java/net/schmizz/sshj/common/Message.java @@ -46,6 +46,9 @@ public enum Message { USERAUTH_60(60), USERAUTH_INFO_RESPONSE(61), + USERAUTH_GSSAPI_EXCHANGE_COMPLETE(63), + USERAUTH_GSSAPI_MIC(66), + GLOBAL_REQUEST(80), REQUEST_SUCCESS(81), REQUEST_FAILURE(82), diff --git a/src/main/java/net/schmizz/sshj/userauth/method/AuthGssApiWithMic.java b/src/main/java/net/schmizz/sshj/userauth/method/AuthGssApiWithMic.java new file mode 100644 index 000000000..eb800a13b --- /dev/null +++ b/src/main/java/net/schmizz/sshj/userauth/method/AuthGssApiWithMic.java @@ -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 mechanismOids; + private final GSSManager manager; + + private GSSContext secContext; + + public AuthGssApiWithMic(LoginContext loginContext, List mechanismOids) { + this(loginContext, mechanismOids, GSSManager.getInstance()); + } + + public AuthGssApiWithMic(LoginContext loginContext, List 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 { + + 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); + } + } +} diff --git a/src/test/java/net/schmizz/sshj/userauth/GssApiTest.java b/src/test/java/net/schmizz/sshj/userauth/GssApiTest.java new file mode 100644 index 000000000..27e744a38 --- /dev/null +++ b/src/test/java/net/schmizz/sshj/userauth/GssApiTest.java @@ -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. 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()); + } + +} diff --git a/src/test/java/net/schmizz/sshj/util/BasicFixture.java b/src/test/java/net/schmizz/sshj/util/BasicFixture.java index 11f781841..910697d93 100644 --- a/src/test/java/net/schmizz/sshj/util/BasicFixture.java +++ b/src/test/java/net/schmizz/sshj/util/BasicFixture.java @@ -33,9 +33,11 @@ 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 boolean authenticate(String u, String p, ServerSession s) { return false; } }); + server.setGSSAuthenticator(gssAuthenticator); server.start(); serverRunning = true; } @@ -137,6 +142,10 @@ public SSHClient getClient() { return client; } + public void setGssAuthenticator(GSSAuthenticator gssAuthenticator) { + this.gssAuthenticator = gssAuthenticator; + } + public void dummyAuth() throws UserAuthException, TransportException { server.setPasswordAuthenticator(new BogusPasswordAuthenticator()); diff --git a/src/test/java/net/schmizz/sshj/util/gss/BogusGSSAuthenticator.java b/src/test/java/net/schmizz/sshj/util/gss/BogusGSSAuthenticator.java new file mode 100644 index 000000000..9528b390a --- /dev/null +++ b/src/test/java/net/schmizz/sshj/util/gss/BogusGSSAuthenticator.java @@ -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); + } +} diff --git a/src/test/java/net/schmizz/sshj/util/gss/BogusGSSContext.java b/src/test/java/net/schmizz/sshj/util/gss/BogusGSSContext.java new file mode 100644 index 000000000..597768d59 --- /dev/null +++ b/src/test/java/net/schmizz/sshj/util/gss/BogusGSSContext.java @@ -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; + } + +} diff --git a/src/test/java/net/schmizz/sshj/util/gss/BogusGSSCredential.java b/src/test/java/net/schmizz/sshj/util/gss/BogusGSSCredential.java new file mode 100644 index 000000000..c1d9803b7 --- /dev/null +++ b/src/test/java/net/schmizz/sshj/util/gss/BogusGSSCredential.java @@ -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); + } +} diff --git a/src/test/java/net/schmizz/sshj/util/gss/BogusGSSManager.java b/src/test/java/net/schmizz/sshj/util/gss/BogusGSSManager.java new file mode 100644 index 000000000..c083ecfe3 --- /dev/null +++ b/src/test/java/net/schmizz/sshj/util/gss/BogusGSSManager.java @@ -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; + } +} \ No newline at end of file diff --git a/src/test/java/net/schmizz/sshj/util/gss/BogusGSSName.java b/src/test/java/net/schmizz/sshj/util/gss/BogusGSSName.java new file mode 100644 index 000000000..8b39864a7 --- /dev/null +++ b/src/test/java/net/schmizz/sshj/util/gss/BogusGSSName.java @@ -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; + } +}