Decrypt kerberos ticket using Spnego
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 conta开发者_如何学JAVAint 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.
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.
精彩评论