mirror of
https://github.com/hierynomus/sshj.git
synced 2025-12-06 23:30:55 +03:00
Added AuthGssApiWithMic for Kerberos auth
This commit is contained in:
@@ -15,6 +15,14 @@
|
||||
*/
|
||||
package net.schmizz.sshj;
|
||||
|
||||
import javax.security.auth.login.LoginContext;
|
||||
import javax.security.auth.login.LoginException;
|
||||
|
||||
import org.ietf.jgss.GSSException;
|
||||
import org.ietf.jgss.Oid;
|
||||
|
||||
import com.sun.security.auth.callback.DialogCallbackHandler;
|
||||
|
||||
import net.schmizz.sshj.common.Factory;
|
||||
import net.schmizz.sshj.common.SSHException;
|
||||
import net.schmizz.sshj.common.SecurityUtils;
|
||||
@@ -49,6 +57,7 @@ import net.schmizz.sshj.userauth.keyprovider.KeyFormat;
|
||||
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;
|
||||
@@ -63,11 +72,15 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.net.ServerSocket;
|
||||
import java.security.KeyPair;
|
||||
import java.security.PublicKey;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@@ -365,6 +378,30 @@ public class SSHClient
|
||||
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 authenication 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
|
||||
* to attempt connection after this method has been called.
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
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 GSSContext secContext;
|
||||
|
||||
public AuthGssApiWithMic(LoginContext loginContext, List<Oid> mechanismOids) {
|
||||
super("gssapi-with-mic");
|
||||
this.loginContext = loginContext;
|
||||
this.mechanismOids = mechanismOids;
|
||||
|
||||
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 {
|
||||
GSSManager manager = GSSManager.getInstance();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user