3
votes

I'm currently trying to implement a Java client to an SPNEGO protected web service using the SPNEGO library from SourceForge (the server is using the same library). I can not get it to authenticate successfully, my requests always end up as

HTTP/1.1 500 Failure unspecified at GSS-API level (Mechanism level: Checksum failed)

This is similar to the symptoms that I get when accessing the web service from a browser with an inappropriate hostname, and indeed some debugging in Wireshark reveals that the client sends a wrong SPN with the request - I send to service-test.client.com, which is registered as an SPN and has an A record in DNS, but is registered in the Windows domain as server-1234.client.corp. Even though I send my request to http://service-test.client.com (see matching Host header), the SPN that Java requests a ticket for is the "internal" Windows name:

Wireshark decode of the HTTP request

The same sent from Chrome or IE has matching Host headers and SPNs:

enter image description here

Since there is none of this translation occurs in my code or the SPNEGO library, I presume it must be happening somehwere in the JRE. I've been looking into the JGSS source, but it's a bit hard to understand. Can anyone tell me how to skip this translation and get tickets for the correct SPN?

Client code:

SpnegoHttpURLConnection con = new SpnegoHttpURLConnection("spnego-client", user, password);
con.connect(new URL("http://service-test.client.com:8083/service"));
int rc = con.getResponseCode();
String msg = con.getResponseMessage();
1
Recheck your DNS. Do a reverse lookup. Most problems occur from incorrect reverse DNS entries. What is the name of your realm? Page 85 in this RFC might help you. Windows uses SSPI which does actually the same thing as GSS-API but in a diffrent fashion. Just another hint: You need to provide information about the KDC serving the CLIENT.COM realm otherwise Java won't be able to obtain a ticket. We have a similar constellation at work. This is deemed to fail because the realm CLIENT.COM is not known to Kerberos.Michael-O
You can sniff the KDC traffic with Wireshark too. Filter for kerberos.Michael-O
@Michael-O: thanks, great comments. If I read that RFC correctly, it shouldn't do any reverse-lookup shenanigans, though... I'll verify on Monday what DNS/Kerberos traffic happens...themel
Actually not, it says "When a reference to a name of this type is resolved, the "hostname" may (as an example implementation strategy) be canonicalized by attempting a DNS lookup and using the fully-qualified domain name which is returned, or by using the "hostname" as provided if the DNS lookup fails." So it depends. It's perfectly natural that a reverse lookup is done. This is how Kerberos verifies the hostname. This is actually crucial if you are running DNS round-robin. Without that it won't never be able to construct the real SPN. Tell me your results on monday. I am excited.Michael-O
Thanks - that's all very plausible, but I don't get why IE would behave differently from Java here. I always assumed a round-robin setup would have to run with a shared SPN. If it works the way you describe, I'm in trouble because we use multiple SPNs bound to different services on the same machine/IP address - "Kerberos virtual hosting" if you will. So far, with just browser clients, it worked, but I might really just have got lucky.themel

1 Answers

4
votes

Summary from the comments above:

Recheck your DNS. Do a reverse lookup. Most problems occur from incorrect reverse DNS entries.

Page 85 in RFC2713 might help you and check RFC4120 and search for "canon".

When a SPN for a host-based service is constructed with GSS-API you have to canonicalize that name with the target mechanism. The RFC says

When a reference to a name of this type is resolved, the "hostname" may (as an example implementation strategy) be canonicalized by attempting a DNS lookup and using the fully-qualified domain name which is returned, or by using the "hostname" as provided if the DNS lookup fails. The canonicalization operation also maps the host's name into lower-case characters.

Where the Kerberos 5 RFC says:

server and when transmitted. Thus, for example, one should not rely on an unprotected DNS record to map a host alias to the primary name of a server, accepting the primary name as the party that one intends to contact, since an attacker can modify the mapping and impersonate the party.

Implementations of Kerberos and protocols based on Kerberos MUST NOT use insecure DNS queries to canonicalize the hostname components of the service principal names (i.e., they MUST NOT use insecure DNS queries to map one name to another to determine the host part of the principal name with which one is to communicate). In an environment without secure name service, application authors MAY append a statically configured domain name to unqualified hostnames before passing the name to the security mechanisms, but they should do no more than that. Secure name service facilities, if available, might be trusted for hostname canonicalization, but such canonicalization by the client SHOULD NOT be required by KDC implementations.

Implementation note: Many current implementations do some degree of canonicalization of the provided service name, often using DNS even though it creates security problems. However, there is no consistency among implementations as to whether the service name is case folded to lowercase or whether reverse resolution is used. To maximize interoperability and security, applications SHOULD provide security mechanisms with names that result from folding the user- entered name to lowercase without performing any other modifications or canonicalization.

It seems like GSS-API impls may canonicalize but Kerberos should not do so if the DNS is untrusted. So it depends. It's perfectly natural that a reverse lookup is done. This is how Kerberos verifies the hostname. This is actually crucial if you are running DNS round-robin. Without that it won't never be able to construct the real SPN.

Though I would really as this on a Kerberos mailing list. This is a very interesting point.

I have checked the MIT Kerberos implementation and there is the method krb5_sname_to_principal which actually does this if you check the source code in sn2princ.c:

if (type == KRB5_NT_SRV_HST) {
        struct addrinfo *ai = NULL, hints;
        int err;
        char hnamebuf[NI_MAXHOST];

        /* Note that the old code would accept numeric addresses,
           and if the gethostbyaddr step could convert them to
           real hostnames, you could actually get reasonable
           results.  If the mapping failed, you'd get dotted
           triples as realm names.  *sigh*

           The latter has been fixed in hst_realm.c, but we should
           keep supporting numeric addresses if they do have
           hostnames associated.  */

        memset(&hints, 0, sizeof(hints));
        hints.ai_flags = AI_CANONNAME;
        err = getaddrinfo(hostname, 0, &hints, &ai);
        if (err) {
#ifdef DEBUG_REFERRALS
            printf("sname_to_princ: failed to canonicalize %s; using as-is", hostname);
#endif
        }
        remote_host = strdup((ai && ai->ai_canonname) ? ai->ai_canonname : hostname);
        if (!remote_host) {
            if(ai)
                freeaddrinfo(ai);
            return ENOMEM;
        }

        if ((!err) && maybe_use_reverse_dns(context, DEFAULT_RDNS_LOOKUP)) {
            /*
             * Do a reverse resolution to get the full name, just in
             * case there's some funny business going on.  If there
             * isn't an in-addr record, give up.
             */
            /* XXX: This is *so* bogus.  There are several cases where
               this won't get us the canonical name of the host, but
               this is what we've trained people to expect.  We'll
               probably fix it at some point, but let's try to
               preserve the current behavior and only shake things up
               once when it comes time to fix this lossage.  */
            err = getnameinfo(ai->ai_addr, ai->ai_addrlen,
                              hnamebuf, sizeof(hnamebuf), 0, 0, NI_NAMEREQD);
            freeaddrinfo(ai);
            if (err == 0) {
                free(remote_host);
                remote_host = strdup(hnamebuf);
                if (!remote_host)
                    return ENOMEM;
            }
        } else
            freeaddrinfo(ai);
    }

So, I guess we have to ask with the mailing list.