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