1

I am using a Java Spring Boot application with Waffle SSO library waffle-spring-security4 2.0.0 and have installed and configured MIT Kerberos. The ticket cache is working fine, and running "kinit" creates Kerberos tickets in the ticket cache (C:\Users\XYZ\krb5cc_XYZ). I have verified this by running "klist".

I have implemented JAAS Keytab authentication, which generates the GSSCredential and GSSContext. However, I am unsure about how to extract the Kerberos KT ticket from JAAS or Waffle SSO and authenticate JSch using the extracted ticket.

The following code generates a Kerberos service ticket and successfully authenticates JSch, enabling me to run a command on the remote machine. However, when I run the code for another user, the previous user's TGT is being used. I have disposed of the GSSCredential and GSSContext, but it does not solve the issue.

JSch itself performs all the GSS Authentication and creates the GSSCredential and GSSContext, but I don't have the control to modify the internal behavior and configuration. Therefore, I am performing JAAS authentication and creating GSSCredential and GSSContext.

What suggestions do you have for what I am missing?

public static ArrayList<String> simpleKerberos(String command)
            throws LoginException, GSSException, JSchException, IOException, PrivilegedActionException {
    SunJaasKerberosClient client = new SunJaasKerberosClient();
    client.setDebug(true); // enable debug logs
    ArrayList<String> results = new ArrayList<String>();

    SecurityContext sec = SecurityContextHolder.getContext();
    Authentication authentication = sec.getAuthentication();
    WindowsPrincipal principal = (WindowsPrincipal) authentication.getPrincipal();
    String userNtId = principal.getName();
    String[] userInfo = userNtId.split("\\\\");
    String username = userInfo[1].toLowerCase() + "@" + userInfo[0] + ".DOMAIN.COM";
    String userId = userInfo[1];
    String keyTabFilePath = "C:\\Users\\" + userId + "\\" + userId + ".keytab";

    String ticketCache = "FILE:C:\\Users\\" + userId + "\\krb5cc_" + userId + "";

    String loginContextName = "Krb5Login";

    System.setProperty("java.security.krb5.conf", "C:\\ProgramData\\MIT\\Kerberos5\\krb5.ini");
    System.setProperty("java.security.auth.login.config", "C:\\ProgramData\\MIT\\Kerberos5\\jaas.config");
    //System.setProperty("sun.security.jgss.native", "true");
    System.setProperty("javax.security.auth.useSubjectCredsOnly", "false");
    System.setProperty("sun.security.krb5.debug", "true");
    System.setProperty("java.security.debug", "gssloginconfig,configfile,configparser,logincontext");
    System.setProperty("KRB5CCNAME", ticketCache);
    System.setProperty("javax.security.auth.kerberos.ticket.cache", "FILE:" + ticketCache);
    System.setProperty("KRB5_KTNAME", "C:\\Users\\" + userId + "\\" + userId + ".keytab");
    System.setProperty("user.home", "C:\\Users\\" + userId + "");
    System.setProperty("user.name", userId);
    System.setProperty("principal", username);

    String host = "REMOTE.MACHINE.FQDN.COM";
    String realm = "SUBDOMAIN.DOMAIN.COM";

    Configuration loginConfig = new Configuration() {
        @Override
        public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
            if (name.equals(loginContextName)) {
                Map<String, Object> options = new HashMap<>();
                options.put("useKeyTab", "true");
                options.put("useTicketCache", "true");
                options.put("debug", "true");
                options.put("principal", username);
                options.put("ticketCache", ticketCache);

                // Create a Subject that represents the current user's security context
                Subject subject = new Subject();
                Principal principal = new KerberosPrincipal(username);
                subject.getPrincipals().add(principal);

                options.put("javax.security.auth.subject", subject);
                // options.put("runAs", "authenticated");
                // options.put("doNotPrompt", "true");

                return new AppConfigurationEntry[] {
                        new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule",
                                AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options) };
            }
            return null;
        }
    };

    LoginContext loginContext = new LoginContext(loginContextName, null,
            new KerberosKeytabCallbackHandler(username, keyTabFilePath), loginConfig);
    loginContext.login();

    GSSManager gssManager = GSSManager.getInstance();
    GSSName clientName = gssManager.createName(username, GSSName.NT_USER_NAME);

    GSSCredential credential = null;
    Subject subject = null;
    try {

        subject = loginContext.getSubject();
        credential = Subject.doAs(subject, (PrivilegedExceptionAction<GSSCredential>) () -> {
            return gssManager.createCredential(clientName, GSSCredential.DEFAULT_LIFETIME,
                    new Oid("1.2.840.113554.1.2.2"), GSSCredential.ACCEPT_ONLY); ---Not sure, If I should've used INITIATE_ONLY
        });

    } catch (PrivilegedActionException e) {
        e.printStackTrace();
    }
    
    Set<GSSCredential> gssCredentials = subject.getPrivateCredentials(GSSCredential.class);
    gssCredentials.add(credential);

    if (credential == null) {
        throw new RuntimeException("Could not obtain Kerberos credentials for " + username);
    }

    GSSContext gssContext = gssManager.createContext(clientName, new Oid("1.2.840.113554.1.2.2"), credential,
            GSSContext.DEFAULT_LIFETIME);
    
    gssContext.requestCredDeleg(true);
    gssContext.requestMutualAuth(true);
    gssContext.requestConf(true);
    gssContext.requestInteg(true);
    gssContext.requestReplayDet(true);
    gssContext.requestSequenceDet(true);
    gssContext.requestAnonymity(false);
    gssContext.requestLifetime(GSSContext.DEFAULT_LIFETIME);
    gssContext.setChannelBinding(null);

    JSch jsch = new JSch();
    JSch.setConfig("PreferredAuthentications", "gssapi-with-mic");
    Session session = jsch.getSession(username, host, 22);
    session.setConfig("StrictHostKeyChecking", "no");
    UserInfo user = new KerberosUserInfo(credential, gssContext);
    session.setUserInfo(user);
    session.setConfig("userauth.gssapi-with-mic", "com.jcraft.jsch.UserAuthGSSAPIWithMIC");
    session.connect();
    Channel channel = session.openChannel("exec");
    ((ChannelExec) channel).setCommand(command);

    channel.setInputStream(null);
    ((ChannelExec) channel).setErrStream(System.err);

    InputStream in = channel.getInputStream();
    channel.connect();
    BufferedReader reader = new BufferedReader(new InputStreamReader(in));
    String line = null;
    while ((line = reader.readLine()) != null) {
        results.add(line);
    }
    jsch.removeAllIdentity();
    channel.disconnect();
    session.disconnect();
    loginContext.logout();
    loginConfig.refresh();
    gssContext.dispose();
    credential.dispose();

    return results;
}

Kerberos UserInfo

class KerberosUserInfo implements UserInfo {

    private GSSCredential credential;
    private GSSContext context;
    
    public KerberosUserInfo(GSSCredential credential) {
        this.credential = credential;
    }

    public KerberosUserInfo(GSSCredential credential, GSSContext context) {
        this.credential = credential;
        this.context = context;
    }
    
    @Override
    public String getPassphrase() {
        return null;
    }
     @Override
    public String getPassword() {
        return null;
    }
     @Override
    public boolean promptPassphrase(String message) {
        return false;
    }
     @Override
    public boolean promptPassword(String message) {
        return false;
    }

    public boolean promptYesNo(String message) {
        return true;
    }
     @Override
    public void showMessage(String message) {
        System.out.println(message);
    }

    /*
     * public GSSCredential getGSSCredential() { return credential; }
     */
    public GSSCredential getGSSCredential() {
        return credential;
    }

    public GSSContext getGSSContext() {
        return context;
    }

}

KerberosKeytabCallbackHandler

public class KerberosKeytabCallbackHandler implements CallbackHandler {

    private final String principal;
    private final String keytab;

    public KerberosKeytabCallbackHandler(String principal, String keytab) {
        this.principal = Objects.requireNonNull(principal);
        this.keytab = Objects.requireNonNull(keytab);
    }

    @Override
    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
        for (Callback callback : callbacks) {
            if (callback instanceof NameCallback) {
                ((NameCallback) callback).setName(principal);
            } else if (callback instanceof PasswordCallback) {
                ((PasswordCallback) callback).setPassword(keytab.toCharArray());
            } else {
                throw new UnsupportedCallbackException(callback);
            }
        }
    }
}
Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
Chandru
  • 81
  • 1
  • 9
  • 1
    Waffle and MIT Kerberos does not make sense. Waffle uses SSPI. – Michael-O Mar 16 '23 at 08:36
  • Yes. That's why I am using JAAS with KeyTab to create Kerberos ticket & authenticate the user. It successfully creates the TGT & Kerberos Service Ticket. But I am unware of setting the custom created GSSCredential & GSSContext to JSch configuration. Using Jsch 0.1.55. JSch does exactly what I am doing with JAAS, it creates GSSCredential & GSSContext. But it uses the last authenticated users TGT for the current users. So authentication getting failed. It would be great, if you provide some pointers or suggestions. – Chandru Mar 21 '23 at 05:27
  • You need the `Subject#doAs()` method with the `LoginContext`. – Michael-O Mar 21 '23 at 08:02
  • I am not certain of the usage of Subject#doAs() in this context. Nonetheless, I have managed to create GSSCredentials and GSSContext using JAAS. However, I am uncertain of how to make JSch utilize these credentials and context. Internally, JSch performs authentication using Keytab and creates new GSSCredentials and GSSContext, causing failure. Although this works for the first user, the second user utilizes the first user's TGT, resulting in JSch authentication failure. Despite clearing GSSCredentials, GSSContext, JSch Session, and channel, JSch still employs the previous user's TGT. – Chandru Mar 25 '23 at 13:39

0 Answers0