2

Major edit: 2015-05-27: After some degree of success updated on where I'm currently stuck rather than leaving a rambling post....could really do with some pointers on this one - a little bogged down....

I'm running some code on a Linux app server (WebSphere) that needs to authenticate to an IIS web service which is configured for "Integrated Authentication", but I'm having some problems forming the Authorization: Negotiate token. I should also say that I need to put this token into the HTTP header for a JAX-WS SOAP request that I will subsequently build. I know my SOAP request itself works because we were using WS-Security Username token profile previously and it worked fine - trying to swap to kerberos is proving difficult...

My problem is with initSecContext I think. It appears that on the first call the context is configured in "some" way and there is some returned token data, but .isEstablished is false. The problem I'm having is putting the initSecContext call into a loop - it seems IIS just closes the connection when I do this. Can anyone give me some pointers - I seem to be taking the approach used by other posters and the Oracle samples (although the IBM/WebSphere sample only makes a single initSecContext call and doesn't check .isEstablished which seems odd to me based on the Oracle documentation).

Anyway, the error I get is below (note the Ready: property seems to clearly say initSecContext needs to loop - to me at least);

[5/27/15 6:51:11:605 UTC] 0000004f SystemOut     O INFO: com.mycorp.kerberosKerberosTokenGenerator/getKerberosToken/run: After initSecContext:
--- GSSContext ---
        Owner:          domainuser@MYDOMAIN.COM
        Peer:           HTTP/iishost.mycorp.com
        State:          initialized
        Lifetime:       indefinite
        Ready:          no
        Flags:
                Confidentiality         off
                Delegation              on
                Integrity               off
                MutualAuthn             on
                ReplayDetection         off
                SequenceDetection       off
                DelegatedCred:          unknown
--- End of GSSContext ---

[5/27/15 6:51:11:605 UTC] 0000004f SystemOut     O INFO: com.mycorp.kerberosKerberosTokenGenerator/getKerberosToken/run: Context is not established, trying again
[5/27/15 6:51:11:606 UTC] 0000004f SystemOut     O ERROR: com.mycorp.kerberosKerberosTokenGenerator/getKerberosToken/run: IOException during context establishment: Connection reset

My code is below;

            LoginContext lc = getLoginContext(contextName);
            final Subject subject = lc.getSubject();

            String b64Token = (String) Subject.doAs(subject, new PrivilegedExceptionAction() {
                @Override
                public Object run() throws PrivilegedActionException, GSSException {
                    // Create socket to server
                    Socket socket;
                    DataInputStream inStream = null;
                    DataOutputStream outStream = null;
                    try {
                        socket = new Socket("iishost.mycorp.com", 443);
                        inStream = new DataInputStream(socket.getInputStream());
                        outStream = new DataOutputStream(socket.getOutputStream());
                    } catch (IOException ex) {
                        System.out.println("Exception setting up server sockets: " + ex.getMessage());
                    }
                    GSSName gssName = manager.createName(userName, GSSName.NT_USER_NAME, KRB5_MECH_OID);
                    GSSCredential gssCred = manager.createCredential(gssName.canonicalize(KRB5_MECH_OID),
                            GSSCredential.DEFAULT_LIFETIME,
                            KRB5_MECH_OID,
                            GSSCredential.INITIATE_ONLY);

                    gssCred.add(gssName, GSSCredential.INDEFINITE_LIFETIME,
                            GSSCredential.INDEFINITE_LIFETIME,
                            SPNEGO_MECH_OID,
                            GSSCredential.INITIATE_ONLY);
                    GSSName gssServerName = manager.createName(servicePrincipal, KERBEROS_V5_PRINCIPAL_NAME);
                    GSSContext clientContext = manager.createContext(gssServerName.canonicalize(SPNEGO_MECH_OID),
                            SPNEGO_MECH_OID,
                            gssCred,
                            GSSContext.DEFAULT_LIFETIME);
                    clientContext.requestCredDeleg(true);
                    clientContext.requestMutualAuth(true);
                    byte[] token = new byte[0];

                    while (!clientContext.isEstablished()) {
                        try {
                            token = clientContext.initSecContext(token, 0, token.length);
// IF I LOOK AT token HERE THERE IS CERTAINLY TOKEN DATA THERE - .isEstablished IS STILL FALSE                          
                            outStream.writeInt(token.length);
                            outStream.write(token);
                            outStream.flush();

                            // Check if we're done
                            if (!clientContext.isEstablished()) {
                                token = new byte[inStream.readInt()];
                                inStream.readFully(token);
                            }
                        } catch (IOException ex) {

// THIS EXCEPTION IS THROWN ON SECOND ITERATION - LOOKS LIKE IIS CLOSES THE CONNECTION                      
                            System.out.println("IOException during context establishment: " + ex.getMessage());
                        }
                    }
                    String b64Token = Base64.encode(token);
                    clientContext.dispose();        // I'm assuming this won't invalidate the token in some way as I need to use it later
                    return b64Token;                
                }
            });

This doc tells me I don't need to loop on initSecContext, but .isEstablished returns false for me: http://www-01.ibm.com/support/knowledgecenter/SS7K4U_8.5.5/com.ibm.websphere.zseries.doc/ae/tsec_SPNEGO_token.html?cp=SS7K4U_8.5.5%2F1-3-0-20-4-0&lang=en

The Oracle docs tell me I should: https://docs.oracle.com/javase/7/docs/api/org/ietf/jgss/GSSContext.html

My only hesitation is that from the Oracle docs it seems like I'm starting the application conversation, but what I'm trying to do it obtain the token only & it's later on in my code when I will use JAX-WS to post my actual web service call (including the spnego/kerberos token in the http header) - is this the cause of my issue?

KarlP
  • 309
  • 3
  • 15
  • What about asking IBM since you have paid for that product (WebSphere)? – Michael-O May 27 '15 at 08:11
  • If that was so easy I'd have done it weeks ago. Sadly raising pmr's in a corp is painfully slow just to get approval to be able to raise one - honestly I wish I was joking. But really I don't think this is a WAS issue anyway. I'm not using WAS policy sets etc - I'm doing my own jaas login to IIS. I would get the same results if I ran this from a command line on Linux. WAS just happens to be the server, running an app that my extension plugs into. – KarlP May 27 '15 at 09:43
  • First of all, you should test your code in a clean room app, not fully embedded. Second, why do you use sockets manually where you want to use SOAP over HTTP. Why do you create a GSS name for Kerberos if you add SPNEGO. Request SPNEGO right away. Who is the server? Self-written? I would highly recommend to test with `gss-client` and `gss-server` supplied with MIT Kerberos first. If that works, go ahead with Java. You should also check your comm with Wireshark. It will parse your ticket and display it nicely. – Michael-O May 27 '15 at 11:13
  • Sockets, and the initSecContext loop which reads/writes bytes to the server is the part I'm confused over. I read the Oracle docs which seem to say I need to do this using a socket to negotiate the token with the server - otherwise what else would I be doing in that loop? IIS Server is running an app written by another company. Wireshark - rock and hard place. Firewalls prevent gui use and Linux permissions (at the minute) prevent local linux wireshark install for command line...working on that though. GSSNaming - will take a look, most likely in error... Thanks for the pointers – KarlP May 27 '15 at 12:56
  • IIS is not a plain socket communication server. So you have to use HTTP. If you use some JAX-WS implementation, it will use probably `HttpURLConnection` which supports SPNEGO out of the box. – Michael-O May 27 '15 at 13:11
  • I'm still not getting my head around something here though which is holding me back. It's the call to initSecContext() & I read that as if I need to call it once, get it's response token and fire that to the server and if isEstablished is false, then I loop until isEstablished is true? If I'm using HttpURLConnection - am I sending a body or just populating the header only & trying to send that (sounds odd)? Or, am I supposed to be building my SOAP message first & then in the isEstablished loop keep updating the header token and sending the whole SOAP message from within a doAs call? – KarlP May 27 '15 at 14:04
  • Provide a complete example. – Michael-O May 27 '15 at 18:46

1 Answers1

1

Just an update. I have this working now - my previous code was largely ok - it was just my understanding of how the Kerberos token would be added to the JAX-WS request. Turns out it's just a matter of attaching a Handler to the bindingProvider. The handler then obtains the Kerberos token and adds it to the header of the request - nice and easy.

Below is my working Handler which is added to the Handler chain obtained from a call to bindingProvider.getBinding().getHandlerChain()

public class HTTPKerberosHandler implements SOAPHandler<SOAPMessageContext> {

    private final String contextName;
    private final String servicePrincipal;
    private static Oid KRB5_MECH_OID = null;
    private static Oid SPNEGO_MECH_OID = null;
    private static Oid KERBEROS_V5_PRINCIPAL_NAME = null;
    final String className = this.getClass().getName();

    static {
        try {
            KERBEROS_V5_PRINCIPAL_NAME = new Oid("1.2.840.113554.1.2.2.1");
            KRB5_MECH_OID = new Oid("1.2.840.113554.1.2.2");
            SPNEGO_MECH_OID = new Oid("1.3.6.1.5.5.2");
        } catch (final GSSException ex) {
            System.out.println("Exception creating mechOid's: " + ex.getMessage());
            ex.printStackTrace();
        }
    }

    public HTTPKerberosHandler(final String contextName, final String servicePrincipal) {
        this.contextName = contextName;
        this.servicePrincipal = servicePrincipal;
    }

    @Override
    public Set<QName> getHeaders() {
        return null;
    }

    @Override
    public boolean handleFault(SOAPMessageContext context) {
        return false;
    }

    @Override
    public void close(MessageContext context) {
        // No action
    }

    @Override
    public boolean handleMessage(SOAPMessageContext context) {
        if (((Boolean) context.get(SOAPMessageContext.MESSAGE_OUTBOUND_PROPERTY))) {
            return handleRequest(context);
        } else {
            return handleResponse(context);
        }
    }

    private boolean handleRequest(SOAPMessageContext context) {
        byte[] token = getKerberosToken(contextName, servicePrincipal);

        HashMap<String, String> sendTransportHeaders = new HashMap<String, String>();
        sendTransportHeaders.put("Authorization", "Negotiate " + Base64.encode(token));
        context.put(com.ibm.websphere.webservices.Constants.REQUEST_TRANSPORT_PROPERTIES, sendTransportHeaders);

        return true;
    }

    private boolean handleResponse(SOAPMessageContext context) {
        logger.logInformation(className, "handleResponse", "Inbound response detected");
        return true;
    }

    public byte[] getKerberosToken(final String contextName, final String servicePrincipal) {

        try {
            LoginContext lc = getLoginContext(contextName);

            final Subject subject = lc.getSubject();

            byte[] token = (byte[]) Subject.doAs(subject, new PrivilegedExceptionAction() {
                @Override
                public Object run() throws PrivilegedActionException, GSSException {
                    final String methodName = "getKerberosToken/run";
                    final GSSManager manager = GSSManager.getInstance();

                    Set<Principal> principals = subject.getPrincipals();
                    Iterator it = principals.iterator();
                    String principalName = ((Principal) it.next()).getName();
                    logger.logInformation(className, methodName, "Using principal: [" + principalName + "]");

                    GSSName gssName = manager.createName(principalName, GSSName.NT_USER_NAME, KRB5_MECH_OID);
                    GSSCredential gssCred = manager.createCredential(gssName.canonicalize(KRB5_MECH_OID),
                            GSSCredential.DEFAULT_LIFETIME,
                            KRB5_MECH_OID,
                            GSSCredential.INITIATE_ONLY);

                    gssCred.add(gssName, GSSCredential.INDEFINITE_LIFETIME,
                            GSSCredential.INDEFINITE_LIFETIME,
                            SPNEGO_MECH_OID,
                            GSSCredential.INITIATE_ONLY);
                    logger.logInformation(className, methodName, "Client TGT obtained: " + gssCred.toString());

                    GSSName gssServerName = manager.createName(servicePrincipal, GSSName.NT_USER_NAME);

                    GSSContext clientContext = manager.createContext(gssServerName.canonicalize(SPNEGO_MECH_OID),
                            SPNEGO_MECH_OID,
                            gssCred,
                            GSSContext.DEFAULT_LIFETIME);

                    logger.logInformation(className, methodName, "Service ticket obtained: " + clientContext.toString());

                    byte[] token = new byte[0];
                    token = clientContext.initSecContext(token, 0, token.length);
                    clientContext.dispose();
                    return token;
                }
            });

            return token;
        } catch (PrivilegedActionException ex) {
            logger.logError(HTTPKerberosHandler.class.getName(), methodName, "PrivilegedActionException: " + ex.getMessage());
        } catch (Exception ex) {
            logger.logError(HTTPKerberosHandler.class.getName(), methodName, "Exception: " + ex.getMessage());
        }

        return null;
    }

    private LoginContext getLoginContext(String contextName) {

        LoginContext lc = null;
        try {
            lc = new LoginContext(contextName);

            lc.login();
        } catch (LoginException le) {
            logger.logError(HTTPKerberosHandler.class.getName(), methodName, "Login exception: [" + le.getMessage() + "]");
            le.printStackTrace();
        }

        return lc;
    }

}
KarlP
  • 309
  • 3
  • 15