1

So I'm trying to implement a SSO/Integrated security system for an AIX server (so IBM JRE). It uses Kerberos to authenticate against AD.

Keep in mind the data below is sanitized.

Command my AD admin used to create the keytab file on the AD server (notice /kvno 2).

ktpass /princ HTTP/local.domain.com@LOCALDOMAIN.NET /mapuser PSLDAP@LOCALDOMAIN.NET /pass <PASSWORD> /crypto ALL /ptype KRB5_NT_PRINCIPAL /kvno 2 /out krb5.keytab

My krb5.conf file:

[libdefaults]
    default_realm = LOCALDOMAIN.NET
    default_keytab_name = FILE:/keytabs/krb.keytab
    default_tkt_enctypes = rc4-hmac
    default_tgs_enctypes = rc4-hmac
    dns_lookup_kdc = true
    dns_lookup_realm = true
[realms]
    LOCALDOMAIN.NET = {
            kdc = localdc08.localdomain.net:88
            kdc = otherdc08.localdomain.net:88
            admin_server = localdc08.localdomain.net:749
            master_kdc = localdc08.localdomain.net
            default_domain = LOCALDOMAIN.NET
    }
[domain_realm]
    .LOCALDOMAIN.NET = LOCALDOMAIN.NET
    LOCALDOMAIN.NET = LOCALDOMAIN.NET
    localdc08.localdomain.net = LOCALDOMAIN.NET
    localdc08.localdomain.net = LOCALDOMAIN.NET
    localdomain.net = LOCALDOMAIN.NET
    .localdomain.net = LOCALDOMAIN.NET
[logging]
    kdc = FILE:/var/krb5/log/krb5kdc.log
    admin_server = FILE:/var/krb5/log/kadmin.log
    kadmin_local = FILE:/var/krb5/log/kadmin_local.log
    default = FILE:/var/krb5/log/krb5lib.log

Here's my krb5Login.conf for the LoginModule:

krbServer {
    com.ibm.security.auth.module.Krb5LoginModule required 
    credsType=acceptor 
    refreshKrb5Config=true 
    principal="HTTP/local.domain.com" 
    useKeytab="/keytabs/krb5.keytab" 
    debug=true;
};

Here's the java I'm running (can't disclose the whole thing because IP)

        context = new LoginContext("krbServer");
        context.login();

        // Get server credentials
        Subject sub = context.getSubject();
        serverCred = Subject.doAs(sub, new PrivilegedExceptionAction<GSSCredential>() {
            public GSSCredential run() throws GSSException {
                // mechanism OID for SPNEGO authentication
                Oid spnegoOid = new Oid("1.3.6.1.5.5.2");

                // null name defaults to currently logged in name
                GSSCredential cred = authManager.createCredential(null,
                        GSSCredential.INDEFINITE_LIFETIME,
                        spnegoOid,
                        GSSCredential.ACCEPT_ONLY);
                return cred;
            }
        });
        context.logout();

When I call the above code, I get the following debug output:

Constructor With Arg: krbServer Version: 1.7.0 Home: /dev/jre
LoginContext Constructed
[JGSS_DBG_CRED]  Thread-2 JAAS config: debug=true
[JGSS_DBG_CRED]  Thread-2 JAAS config: principal=HTTP/local.domain.com
[JGSS_DBG_CRED]  Thread-2 JAAS config: credsType=accept only
[JGSS_DBG_CRED]  Thread-2 config: useDefaultCcache=false (default)
[JGSS_DBG_CRED]  Thread-2 config: useCcache=null
[JGSS_DBG_CRED]  Thread-2 config: useDefaultKeytab=false
[JGSS_DBG_CRED]  Thread-2 config: useKeytab=/keytabs/krb5.keytab
[KRB_DBG_CFG] Config:Thread-2:   ConfigFile: /etc/krb5/krb5.conf
[JGSS_DBG_CRED]  Thread-2 JAAS config: forwardable=false (default)
[JGSS_DBG_CRED]  Thread-2 JAAS config: renewable=false (default)
[JGSS_DBG_CRED]  Thread-2 JAAS config: proxiable=false (default)
[JGSS_DBG_CRED]  Thread-2 JAAS config: tryFirstPass=false (default)
[JGSS_DBG_CRED]  Thread-2 JAAS config: useFirstPass=false (default)
[JGSS_DBG_CRED]  Thread-2 JAAS config: moduleBanner=false (default)
[JGSS_DBG_CRED]  Thread-2 JAAS config: interactive login? no
[JGSS_DBG_CRED]  Thread-2 JAAS config: refreshKrb5Config = true
[KRB_DBG_CFG] Config:Thread-2:   ConfigFile: /etc/krb5/krb5.conf
[KRB_DBG_KDC] KdcComm:Thread-2:   >>> KdcAccessibility: reset
[KRB_DBG_KDC] KdcComm:Thread-2:   >>> KdcAccessibility: reset
[JGSS_DBG_CRED]  Thread-2 Try keytab for principal=HTTP/local.domain.com
[KRB_DBG_KTAB] KeyTab:Thread-2Loading the keytab file ...   >>> KeyTab: load() entry length: 73
[KRB_DBG_KTAB] KeyTableInputStream:Thread-2:   >>> KeyTabInputStream, readName(): LOCALDOMAIN.NET
[KRB_DBG_KTAB] KeyTableInputStream:Thread-2:   >>> KeyTabInputStream, readName(): HTTP
[KRB_DBG_KTAB] KeyTableInputStream:Thread-2:   >>> KeyTabInputStream, readName(): local.domain.com
[KRB_DBG_KDC] EncryptionKey:Thread-2:   >>> EncryptionKey: config default key type is rc4-hmac
[KRB_DBG_KTAB] KeyTab:Thread-2:   Added key: 23  version: 2
[KRB_DBG_KTAB] KeyTab:Thread-2:   Ordering keys wrt default_tkt_enctypes list
[JGSS_DBG_CRED]  Thread-2 No Kerberos creds in keytab for principal HTTP/local.domain.com
[JGSS_DBG_CRED]  Thread-2 Login successful
[JGSS_DBG_CRED]  Thread-2 kprincipal : HTTP/local.domain.com@LOCALDOMAIN.NET
[JGSS_DBG_CRED]  Thread-2 HTTP/local.domain.com@LOCALDOMAIN.NET added to Subject
[JGSS_DBG_CRED]  Thread-2 Attempting to add KeyTab to Subject for HTTP/local.domain.com@LOCALDOMAIN.NET
[JGSS_DBG_CRED]  Thread-2 find keys for HTTP/local.domain.com@LOCALDOMAIN.NET
[KRB_DBG_KTAB] KeyTab:Thread-2:   Added key: 23  version: 2
[KRB_DBG_KTAB] KeyTab:Thread-2:   Ordering keys wrt default_tkt_enctypes list
[JGSS_DBG_CRED]  Thread-2 No keys to add to Subject for HTTP/local.domain.com@LOCALDOMAIN.NET
LoginContext login() method executed
LoginContext getSubject() method executed
Subject doAs() method executed, serverCred Name: default Lifetime: 2147483647
[JGSS_DBG_CRED]  Thread-2 KeyTab is removed from subject
[JGSS_DBG_CRED]  Thread-2 KerberosKey Kerberos Principal HTTP/local.domain.com@LOCALDOMAIN.NETKey Version 2key EncryptionKey: keyType=23 keyBytes (hex dump)=
0000: <MASKED>

When I then call

        public String validate(String encToken) {
        byte[] token = Base64.decode(encToken);

        GSSContext authContext;
        try {
            authContext = authManager.createContext(serverCred);
            authContext.acceptSecContext(token, 0, token.length);
            if (authContext.isEstablished()) {
                return authContext.getSrcName().toString();
            }
        } catch (GSSException e) {
            // fall through to the return
        }

        return null;
    }
}

I discover that the "acceptSecContext" command being called on my token returns a value. I've been under the impression that acceptSecContext only returns a value that needs to be passed back to the initiator. However, the initiator does not expect a response back. Additionally (and more importantly), the .isEstablished() method returns false.

So my questions are

1) Is there anything wrong with the above setup?
2) Why does this happen when I call the login() method for the context object?

[JGSS_DBG_CRED]  Thread-2 Attempting to add KeyTab to Subject for HTTP/local.domain.com@LOCALDOMAIN.NET
    [JGSS_DBG_CRED]  Thread-2 find keys for HTTP/local.domain.com@LOCALDOMAIN.NET
    [KRB_DBG_KTAB] KeyTab:Thread-2:   Added key: 23  version: 2
    [KRB_DBG_KTAB] KeyTab:Thread-2:   Ordering keys wrt default_tkt_enctypes list
    [JGSS_DBG_CRED]  Thread-2 No keys to add to Subject for HTTP/local.domain.com@LOCALDOMAIN.NET

If it found key 23 version 2, why does it then say "No keys to add to Subject for principal@domain? Why didn't it add the key that it found? Does it have a problem with kvno=2?

3) I've searched pretty exhaustively and I can't determine how to parse the output from acceptSecContext to find out what the return value is. The return value I'm receiving (base-64 encoded) is oQcwBaADCgEC.

Edit: Update. The return value from acceptSecContext hex values are: 0xA1 0x07 0x30 0x05 0xA0 0x03 0x0A 0x01 0x02

It APPPEARS from the following site (https://msdn.microsoft.com/en-us/library/ms995330.aspx#http-sso-2_topic2) that the first hex value (A1) corresponds to a NegTokenTarg. That makes sense.

The next octet should be the length (with uppermost bit 1 if the length needs more octets). Since the uppermost bit is 0, the length is 7 octets. Checks out.

The next octet (0x30) denotes a Constructed SEQUENCE, with the next octet being the SEQUENCE length (0x05); 5 octets, checks out.

Then we have 0xA0, 0x03, 0x0A, 0x01 which denotes a Sequence Element 0 (negResult).

The final octet (0x02) is the ENUMERATED value, which is "rejected".

So my token is being rejected. How do I figure out "why"? I guess I'll need to engage the AD team to find out exactly what is happening on their end.

zeusalmighty
  • 1,374
  • 2
  • 8
  • 17

2 Answers2

0

Have you tried to manually test the keytab given with kinit and SPN? Also in your jaas.conf you would probably use useKeyTab=true and keyTab="keytab_filename". But that maybe something specific to your IBM JDK.

Philipp Grigoryev
  • 1,985
  • 3
  • 17
  • 23
  • I'm nor familiar with that Microsoft **ktpass.exe** utility -- what is the actual "principal" used in the keytab, `HTTP/local.domain.com@LOCALDOMAIN.NET` or `PSLDAP@LOCALDOMAIN.NET`?? Try Unix command `klist -k /keytabs/krb5.keytab -e` to display the principal and its keys (only "rc4-hmac" encryption is useful anyway, based on your config). – Samson Scharfrichter Jan 17 '16 at 00:49
  • @samson principal is the one after /princ – Philipp Grigoryev Jan 17 '16 at 03:47
  • the useKeyTab=true and keyTab="keytab_filename" are sun specific. IBM required useKeytab="keytab_filename". That was a doozy figuring out too. – zeusalmighty Jan 18 '16 at 14:00
  • Here's the output from klist, as mentioned above: `Number of entries: 1 [1] principal: HTTP/local.domain.com@LOCALDOMAIN.NET KVNO: 2` – zeusalmighty Jan 18 '16 at 18:34
  • And at least one key using "rc4-hmac" so as to comply with your `[libdefaults]` conf? – Samson Scharfrichter Jan 19 '16 at 07:19
  • Yes, I forgot the -e in my command: `Key table: /keytabs/krb5.keytab Number of entries: 1 [1] principal: HTTP/local.domain.com@LOCALDOMAIN.NET KVNO: 2 Encryption type: RC4 with HMAC` – zeusalmighty Jan 19 '16 at 16:32
  • Yes, kinit works properly. `Done! New ticket is stored in cache file /home/krb5cc_user` – zeusalmighty Jan 19 '16 at 17:52
0

I've discovered the issue for anyone else who is having a similar problem.

It turns out that the OID wasn't working.

Making the following change to my java code for retrieving the server credentials fixed the problem:

    serverCred = Subject.doAs(sub, new PrivilegedExceptionAction<GSSCredential>() {
        public GSSCredential run() throws GSSException {
            // mechanism OID for SPNEGO authentication
            Oid spnegoOid = new Oid("1.3.6.1.5.5.2");

            // null name defaults to currently logged in name
            GSSCredential cred = authManager.createCredential(null,
                    GSSCredential.INDEFINITE_LIFETIME,
                    spnegoOid,
                    GSSCredential.ACCEPT_ONLY);
            cred.add(null, 
                    GSSCredential.INDEFINITE_LIFETIME,
                    GSSCredential.INDEFINITE_LIFETIME,
                    new Oid("1.2.840.113554.1.2.2"),
                    GSSCredential.ACCEPT_ONLY);
            return cred;
        }

I assume it has to do with the fact that I'm not negotiating anything back and forth (initiator and acceptor) so SPNEGO never really has a chance to tell the initiator what mechanism it prefers and what is available, but I was under the impression that the SPNEGO Oid would accommodate many different mechanisms, so the reason this works is a little unclear to me, but it worked.

Addendum: After further research, I was able to find a vague reference to this problem and the cause was "Some functionality deep in the GSSCredential implementation in AIX". So there you have it.

zeusalmighty
  • 1,374
  • 2
  • 8
  • 17