Decrypt kerberos ticket using Spnego

非 Y 不嫁゛ 提交于 2019-12-02 23:07:18

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

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

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.

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];
}

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!