Decrypt kerberos ticket using Spnego
Asked Answered
B

5

14

I'm using spnego ( http://spnego.sourceforge.net ) for kerberos authentication under JBoss.

I need to decrypt kerberos ticket to access the authorization-data which will containt PAC data. The PAC data is needed to decide which roles are to be granted to user.

How to access and decrypt kerberos ticket? I've searched net for examples, but without effort.

Brewington answered 22/12, 2010 at 11:11 Comment(4)
What do you mean by this, "I'm actually testing the solution to check, if it is not adapted to single specific format of token only." What is single specific format?Alphabetize
Well, the code from JaasLounge, and other samples from internet didnt' worked. They do not recognized the structure of the ticket. When using JaasLounge, I got messages such as 'DERSequence not expected here'.Brewington
What are we looking at here? Is this an indication of a solution to your own question? Or is it more questions when trying to solve?Paleozoology
@Paleozoology I've answered my own question in the question. This was on my beginning of my adventure with SO, now I correct this antipattern - I've posted my solution as separate answer.Brewington
A
8

These guys have a full PAC decoding implementation:

http://jaaslounge.sourceforge.net/

You can use the token parser like this:

HttpServletRequest request = (HttpServletRequest) req;
String header = request.getHeader("Authorization");
byte[] base64Token = header.substring(10).getBytes("UTF-8");
byte[] spnegoHeader = Base64.decode(base64Token);

SpnegoInitToken spnegoToken = new SpnegoInitToken(spnegoHeader);

You're going to need to jump though some hoops if you want to decrypt the underlying Kerberos ticket. Not sure if you need that.

Grant

Alphabetize answered 7/1, 2011 at 18:26 Comment(3)
I've downloaded this jaaslounge with sources, but have found no way to extract kerberos ticket from this SpnegoInitToken.Brewington
"You're going to need to jump though some hoops if you want to decrypt the underlying Kerberos ticket. Not sure if you need that." -- This is why I said this. It requires extra code to get the Kerberos ticket. I'll provide a follow up response.Alphabetize
Well, yes you were rigth, they have full PAC decoding implementation. However, their code for parsing whole kerberos token didn't worked and I had to write it myself. After I got encrypted part and decrypted it, I've passed it to KerberosEncData, which does the rest of the job.Brewington
P
8

I have successfully used the servlet filter from http://spnego.sourceforge.net in combination with the PAC parser from http://jaaslounge.sourceforge.net/ without the need to do something explicitly with DER/ASN.1 parsers :

/** 
 * Retrieve LogonInfo (for example, Group SID) from the PAC Authorization Data
 * from a Kerberos Ticket that was issued by Active Directory.
 */  
byte[] kerberosTokenData = gssapiData;
try {
    SpnegoToken token = SpnegoToken.parse(gssapiData);
    kerberosTokenData = token.getMechanismToken();
} catch (DecodingException dex) {
    // Chromium bug: sends a Kerberos response instead of an spnego response 
    // with a Kerberos mechanism
} catch (Exception ex) {
    log.error("", ex);
}   

try {
    Object[] keyObjs = IteratorUtils.toArray(loginContext.getSubject()
                         .getPrivateCredentials(KerberosKey.class).iterator());
    KerberosKey[] keys = new KerberosKey[keyObjs.length];
    System.arraycopy(keyObjs, 0, keys, 0, keyObjs.length);

    KerberosToken token = new KerberosToken(kerberosTokenData, keys);
    log.info("Authorizations: "); 
    for (KerberosAuthData authData : token.getTicket().getEncData()
                                             .getUserAuthorizations()) {
        if (authData instanceof KerberosPacAuthData) {
            PacSid[] groupSIDs = ((KerberosPacAuthData) authData)
                                      .getPac().getLogonInfo().getGroupSids();
            log.info("GroupSids: " + Arrays.toString(groupSIDs));
            response.getWriter().println("Found group SIDs: " + 
                Arrays.toString(groupSIDs));
        } else {
            log.info("AuthData without PAC: " + authData.toString());
        }   
    }   
} catch (Exception ex) {
    log.error("", ex);
}   

I've also written a new HttpFilter (forked from spnego.sf.net): spnego-pac, that discloses the LogonInfo through the getUserPrincipal().

An example project demonstrating the above code in full can be found here:

https://github.com/EleotleCram/jetty-spnego-demo

The spnego-pac filter (used in the above example) can be found here:

https://github.com/EleotleCram/spnego.sf.net-fork

Hope this is helpful to anyone.

__
Marcel

Paleozoology answered 10/4, 2012 at 15:22 Comment(0)
A
5

If you get the mechanism token from the spnegoToken like this:

byte[] mechanismToken = spnegoToken.getMechanismToken(); 

The mechanism token is usually a KerberosApRequest. There is a KerberosToken constructor which takes a KerberosApRequest. Simply pass in the mechanismToken byte array along with the key to decrypt the contents.

Alphabetize answered 17/1, 2011 at 22:40 Comment(4)
You mean class sun.security.jgss.spnego.SpNegoToken? Unfortunatelly this class is not used in the project I'm working on, It is based on org.ietf.jgss.GSSCredential interface. I'm not using it manually, but using utility net.sourceforge.spnego.SpnegoAuthenticator.Brewington
No, I'm talking about the SpnegoInitToken spnegoToken = new SpnegoInitToken(spnegoHeader); in the code I put in the previous answer. This - org.jaaslounge.decoding.spnego.SpnegoInitToken and org.jaaslounge.decoding.kerberos.KerberosApRequestAlphabetize
I've tested it and still doesn't work. Construction of KerberosAsRequest fails, because it is expection DERSequence, and the content of mechanismToken is DERApplicationSpecific.Brewington
What version of bouncycastle are you using? I think there is a bug in some versions of it. You need to use the latest (1.45): repo2.maven.org/maven2/org/bouncycastle <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk16</artifactId> <version>1.45</version>Alphabetize
B
5

I provide my own solution to the problem:

I've based my solution on BouncyCastle library (for parsing parts of token) and JaasLounge (for decrypting encrypted part of token). Unfortunatelly, the code for decoding whole spnego token from JaasLounge failed for my requirements. I had to write it myself.

I've decoded ticket part by part, firstly constructing DERObjects from byte[] array:

private DERObject[] readDERObjects(byte[] bytes) throws IOException {
    ASN1InputStream stream = new ASN1InputStream(new ByteArrayInputStream(
        bytes));
    List<DERObject> objects = new ArrayList<DERObject>();
    DERObject curObj;
    while ((curObj = stream.readObject()) != null) {
        objects.add(untag(curObj));
    }
    return objects.toArray(new DERObject[0]);
}

The untag() is my helper function, to remove DERTaggedObject wrapping

private DERObject untag(DERObject src) {
    if (src instanceof DERTaggedObject) {
        return ((DERTaggedObject) src).getObject();
    }
    return src;
}

For extracting sequence of DERObject from given DERObject I've written another helper function:

private DERObject[] readDERObjects(DERObject container) throws IOException {
// do operation varying from the type of container
if (container instanceof DERSequence) {
    // decode using enumerator
    List<DERObject> objects = new ArrayList<DERObject>();
    DERSequence seq = (DERSequence) container;
    Enumeration enumer = seq.getObjects();
    while (enumer.hasMoreElements()) {
    DERObject curObj = (DERObject) enumer.nextElement();
    objects.add(untag(curObj));
    }
    return objects.toArray(new DERObject[0]);
}
if (container instanceof DERApplicationSpecific) {
    DERApplicationSpecific aps = (DERApplicationSpecific) container;
    byte[] bytes = aps.getContents();
    return readDERObjects(bytes);
}
if (container instanceof DEROctetString) {
    DEROctetString octets = (DEROctetString) container;
    byte[] bytes = octets.getOctets();
    return readDERObjects(bytes);
}
throw new IllegalArgumentException("Unable to decode sequence from "+container);
}

At the end, when I've got DEROctetStream, that contained encrypted part, I've just used KerberosEncData:

KerberosEncData encData = new KerberosEncData(decrypted, matchingKey);

The byte sequence we receive from client browser will be parsed into single DERApplicationSpecific which is ticket root - level 0.
The root contains:

  • DERObjectIdentifier - SPNEGO OID
  • DERSequence - level 1

Level 1 contains:

  • SEQUENCE of DERObjectIdentifier - mech types
  • DEROctetString - wrapped DERApplicationSepecific - level 2

Level 2 contains:

  • DERObjectIndentifier - Kerberos OID
  • KRB5_AP_REQ tag 0x01 0x00, parsed as boolean (false)
  • DERApplicationSpecific - container of DERSequence - level 3

Level 3 contains:

  • version number - should be 5
  • message type - 14 (AP_REQ)
  • AP options (DERBITString)
  • DERApplicationSpecific - wrapped DERSequence with ticket part
  • DERSeqeuence with additional ticket part - not processed

Ticket part - level 4 contains:

  • Ticket version - should be 5
  • Ticket realm - the name of the realm in which user is authenticated
  • DERSequence of server names. Each server name is DERSequence of 2 strings: server name and instance name
  • DERSequence with encrypted part

Encrypted part sequence (level 5) contains:

  • Used algorithm number
    • 1, 3 - DES
    • 16 - des3-cbc-sha1-kd
    • 17 - ETYPE-AES128-CTS-HMAC-SHA1-96
    • 18 - ETYPE-AES256-CTS-HMAC-SHA1-96
    • 23 - RC4-HMAC
    • 24 - RC4-HMAC-EXP
  • Key version number
  • Encrypted part (DEROctetStream)

The problem was with DERBoolean constructor, that throw ArrayIndexOutOfBoundException, when sequence 0x01 0x00 was found. I had to change that constructor:

public DERBoolean(
    byte[]       value)
{
// 2011-01-24 llech make it byte[0] proof, sequence 01 00 is KRB5_AP_REQ
if (value.length == 0)
    this.value = 0;
else
    this.value = value[0];
}
Brewington answered 23/8, 2013 at 16:26 Comment(2)
KerberosEncData (at least the version I know) seems to throw a GeneralSecurityException("Unsupported encryption type.") when the algorithm number isn't 3 or 23. How do you handle the other algorithms? Or havn't you run into this issue yet?Aurelia
I was fighting long with this code, using reverse engineering with WireShark, until it has worked for the specific domain I was using. I haven't run into such problems, and the code was written long ago. But originally I've put the answer into the question, which was wrong and I'm correcting it now. The answer is therefore not new.Brewington
A
2

Wow been a while since I've used spnego (nearly a year) ... You're asking a very cool question.

I did a little digging and was going to try and run up some code I had from a while back that was working with MS-AD but just not feeling it today :-/

Anyway, I found this link through google: http://www.google.com/url?sa=t&source=web&cd=1&sqi=2&ved=0CBMQFjAA&url=http%3A%2F%2Fbofriis.dk%2Ffiles%2Fms_kerberos_pac.pdf&rct=j&q=java%20kerberos%20privilege%20attribute%20certificate&ei=2FASTbaLGcP38Abk07iQDg&usg=AFQjCNHcIfQRUTxkQUvLRcgOaQksCALTHA&sig2=g8yn7ie1PbzSkE2Mfv41Bw&cad=rja

Hopefully that can give you some insight.

Advise answered 22/12, 2010 at 19:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.