Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kerberos PoC #66

Open
wants to merge 2 commits into
base: support/1_14-mule
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.ning.http.client.spnego;

import java.security.PrivilegedExceptionAction;

import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSManager;
import org.ietf.jgss.Oid;

class CreateGssCredentialAction implements PrivilegedExceptionAction<GSSCredential> {

private final GSSManager gssManager;
private final Oid negotiationOid;

CreateGssCredentialAction(GSSManager gssManager, Oid negotiationOid) {
this.gssManager = gssManager;
this.negotiationOid = negotiationOid;
}

@Override
public GSSCredential run() throws Exception {
return gssManager
.createCredential(null, GSSCredential.DEFAULT_LIFETIME, negotiationOid, GSSCredential.INITIATE_AND_ACCEPT);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.ning.http.client.spnego;

import java.util.HashMap;
import java.util.Map;

import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.Configuration;

/**
* Kerberos' configuration that uses a keytab file.
*/
class KeytabKerberosConfiguration extends Configuration {

private final String principal;
private final String keytabFileName;

public KeytabKerberosConfiguration(String principal, String keytabFileName) {
this.principal = principal;
this.keytabFileName = keytabFileName;
}

@Override
public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
Map<String, String> options = new HashMap<>();
options.put("keyTab", keytabFileName);
options.put("principal", principal);
options.put("useKeyTab", "true");
options.put("storeKey", "true");
options.put("doNotPrompt", "true");
options.put("useTicketCache", "true");
options.put("renewTGT", "true");
options.put("refreshKrb5Config", "true");
options.put("isInitiator", "true");
String ticketCache = System.getenv("KRB5CCNAME");
if (ticketCache != null) {
options.put("ticketCache", ticketCache);
}
options.put("debug", "true");

return new AppConfigurationEntry[]{
new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule",
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
options),};
}
}
56 changes: 45 additions & 11 deletions src/main/java/com/ning/http/client/spnego/SpnegoEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,28 @@

package com.ning.http.client.spnego;

import static javax.security.auth.Subject.doAs;
import static org.slf4j.LoggerFactory.getLogger;

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;

import com.ning.http.util.Base64;

import java.io.IOException;
import java.security.Principal;
import java.security.PrivilegedActionException;
import java.util.HashSet;
import java.util.Set;

import javax.security.auth.Subject;
import javax.security.auth.kerberos.KerberosPrincipal;
import javax.security.auth.login.LoginContext;

/**
* SPNEGO (Simple and Protected GSSAPI Negotiation Mechanism) authentication
Expand All @@ -60,7 +71,7 @@ public class SpnegoEngine {
private static final String SPNEGO_OID = "1.3.6.1.5.5.2";
private static final String KERBEROS_OID = "1.2.840.113554.1.2.2";

private final Logger log = LoggerFactory.getLogger(getClass());
private static final Logger LOGGER = getLogger(SpnegoEngine.class);

private final SpnegoTokenGenerator spnegoGenerator;

Expand All @@ -74,10 +85,10 @@ public SpnegoEngine() {
this(null);
}

public String generateToken(String server) throws Throwable {
public String generateToken(String server, String clientPrincipal, String keytabFilename) throws Throwable {

try {
log.debug("init {}", server);
LOGGER.debug("init {}", server);
/* Using the SPNEGO OID is the correct method.
* Kerberos v5 works for IIS but not JBoss. Unwrapping
* the initial token when using SPNEGO OID looks like what is
Expand All @@ -102,18 +113,19 @@ public String generateToken(String server) throws Throwable {
boolean tryKerberos = false;
try {
GSSManager manager = GSSManager.getInstance();
GSSCredential gssCredential = getGssCredential(clientPrincipal, keytabFilename, manager, negotiationOid);
GSSName serverName = manager.createName("HTTP@" + server, GSSName.NT_HOSTBASED_SERVICE);
gssContext = manager.createContext(
serverName.canonicalize(negotiationOid), negotiationOid, null,
serverName.canonicalize(negotiationOid), negotiationOid, gssCredential,
GSSContext.DEFAULT_LIFETIME);
gssContext.requestMutualAuth(true);
gssContext.requestCredDeleg(true);
} catch (GSSException ex) {
log.error("generateToken", ex);
LOGGER.error("generateToken", ex);
// BAD MECH means we are likely to be using 1.5, fall back to Kerberos MECH.
// Rethrow any other exception.
if (ex.getMajor() == GSSException.BAD_MECH) {
log.debug("GSSException BAD_MECH, retry with Kerberos MECH");
LOGGER.debug("GSSException BAD_MECH, retry with Kerberos MECH");
tryKerberos = true;
} else {
throw ex;
Expand All @@ -122,12 +134,13 @@ public String generateToken(String server) throws Throwable {
}
if (tryKerberos) {
/* Kerberos v5 GSS-API mechanism defined in RFC 1964.*/
log.debug("Using Kerberos MECH {}", KERBEROS_OID);
LOGGER.debug("Using Kerberos MECH {}", KERBEROS_OID);
negotiationOid = new Oid(KERBEROS_OID);
GSSManager manager = GSSManager.getInstance();
GSSName serverName = manager.createName("HTTP@" + server, GSSName.NT_HOSTBASED_SERVICE);
GSSCredential gssCredential = getGssCredential(clientPrincipal, keytabFilename, manager, negotiationOid);
gssContext = manager.createContext(
serverName.canonicalize(negotiationOid), negotiationOid, null,
serverName.canonicalize(negotiationOid), negotiationOid, gssCredential,
GSSContext.DEFAULT_LIFETIME);
gssContext.requestMutualAuth(true);
gssContext.requestCredDeleg(true);
Expand All @@ -154,11 +167,11 @@ public String generateToken(String server) throws Throwable {
gssContext.dispose();

String tokenstr = new String(Base64.encode(token));
log.debug("Sending response '{}' back to the server", tokenstr);
LOGGER.debug("Sending response '{}' back to the server", tokenstr);

return tokenstr;
} catch (GSSException gsse) {
log.error("generateToken", gsse);
LOGGER.error("generateToken", gsse);
if (gsse.getMajor() == GSSException.DEFECTIVE_CREDENTIAL
|| gsse.getMajor() == GSSException.CREDENTIALS_EXPIRED)
throw new Exception(gsse.getMessage(), gsse);
Expand All @@ -174,4 +187,25 @@ public String generateToken(String server) throws Throwable {
throw new Exception(ex.getMessage());
}
}

private static GSSCredential getGssCredential(String clientPrincipal,
String keytabFilename,
GSSManager manager,
final Oid negotiationOid) throws Exception {
LoginContext loginContext = null;
try {
Set<Principal> principals = new HashSet<>();
principals.add(new KerberosPrincipal(clientPrincipal));
Subject subject = new Subject(false, principals, new HashSet<>(), new HashSet<>());
loginContext = new LoginContext("", subject, null, new KeytabKerberosConfiguration(clientPrincipal, keytabFilename));
loginContext.login();
return doAs(subject, new CreateGssCredentialAction(manager, negotiationOid));
} catch (PrivilegedActionException ex) {
throw ex.getException();
} finally {
if (loginContext != null) {
loginContext.logout();
}
}
}
}
19 changes: 15 additions & 4 deletions src/main/java/com/ning/http/util/AuthenticatorUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,26 @@ public static String perConnectionAuthorizationHeader(Request request,
case KERBEROS:
case SPNEGO:
String host;
if (proxyServer != null)
String principal;
String keytabName;
if (proxyServer != null) {
host = proxyServer.getHost();
else if (request.getVirtualHost() != null)
principal = proxyServer.getPrincipal();
keytabName = proxyServer.getPassword();
} else if (request.getVirtualHost() != null) {
host = request.getVirtualHost();
else
// TODO: ??
principal = request.getRealm().getPrincipal();
keytabName = request.getRealm().getPassword();
} else {
host = uri.getHost();
// TODO: ??
principal = request.getRealm().getPrincipal();
keytabName = request.getRealm().getPassword();
}

try {
authorizationHeader = "Negotiate " + SpnegoEngine.INSTANCE.generateToken(host);
authorizationHeader = "Negotiate " + SpnegoEngine.INSTANCE.generateToken(host, principal, keytabName);
} catch (Throwable e) {
throw new IOException(e);
}
Expand Down