The previous entry of the blog deployed the SPNEGO filter in a glassfish v3 container performing SPNEGO/Kerberos transparent login in a Samba 4 Windows domain (it is really interesting how difficult is to replaced a common pair, Windows AD DC and .NET IIS application, outside the Microsoft world). But it worked, you could see how a browser properly configured in a Windows machine member silently logged in. But I also commented that an important part was missing: AD groups. Without them authorization in JavaEE (groups should be mapped to application roles) was impossible. I was investigating the issue and this new entry is the result.
The first thing I found was an interesting article signed by Jens Bo Friis which explains how Windows Kerberos implementation deals with group membership problem (take into account it is from 2005 which gives an idea of the little progress in this subject). It seems that Microsoft added a extension to the Kerberos token called Privilege Access Certificate (PAC) which, among other information, contains the group SIDs (Group Security IDentifier) for the logged user. So group membership information is already there, in the token itself (remember the SPNEGO negotiation explained in the previous entry). I prefer this solution to accessing to the AD directly, which should be the easy integration (after retrieving the user using Kerberos the filter would ask the LDAP/AD for getting the user groups -memberOf attribute-).
Following the PAC solution the problem now is retrieving that information from the token. The Kerberos token seems to be ASN.1 encoded and the part that contains the PAC is also encrypted. Searching quite deeply, I found that some classes in Spring project decoded the token for obtaining the SIDs. The Spring class uses another project called jaaslounge, which is another try for Windows/Kerberos/AD SSO integration. Looking the Spring class only the decoding package was needed for parsing, not the whole project. The jaaslounge library is very confused to download so I finally decided to get the source from SVN directly and only compile the needed package (org.jaaslounge.decoding). Finally the decoding classes import the ASN.1 parsing framework from Bouncy Castle.
In summary the SPNEGO filter (the project I used in the previous entry) is going to retrieved the SIDs from the token using jaaslounge decoding, which in turn uses the Bouncy Castle ASN1 implementation. Besides, to make matters worse, the jaaslounge parsing classes did not work (I suppose that there is some problem with recent versions of Bouncy Castle). Therefore I created a frankenjar Netbeans project which contains the spnego-r7 filter implementation with the new code, the modified jaaslounge decoding package and a utility class from Spring (PacUtility.java which decodes the SID from binary to text). It is really remarkable that, when I do odd things, there are very few like me . With that mess the PAC section can be parsed with a code similar to the following (it was added to the SpnegoAuthenticator.java class):
LOGGER.finer("Let's parse the kerberos token...");
sids = new ArrayList();
// create the parser SPNEGO token
SpnegoInitToken spnegoToken = new SpnegoInitToken(gss);
// retrieve the mech (Kerberos) token
byte[] mechanismToken = spnegoToken.getMechanismToken();
// get the server credentials from the service account subject (keytab)
Set creds = this.loginContext.getSubject()
.getPrivateCredentials(KerberosKey.class);
KerberosKey[] keys = creds.toArray(new KerberosKey[creds.size()]);
// parse the kerberos token
KerberosToken kerberosToken = new KerberosToken(mechanismToken, keys);
// get all the autorizations
List authorizations = kerberosToken.getTicket()
.getEncData().getUserAuthorizations();
for (KerberosAuthData authorization : authorizations) {
// get the PAC info
PacLogonInfo logonInfo = ((KerberosPacAuthData) authorization)
.getPac().getLogonInfo();
if (logonInfo != null) {
if (logonInfo.getGroupSid() != null) {
String sid = PacUtility.binarySidToStringSid(
logonInfo.getGroupSid().toString());
LOGGER.log(Level.FINER, "Adding primary group SID: {0}", sid);
sids.add(sid);
}
if (logonInfo.getGroupSids() != null) {
for (PacSid pacSid : logonInfo.getGroupSids()) {
String sid = PacUtility.binarySidToStringSid(pacSid.toString());
LOGGER.log(Level.FINER, "Adding secondary group SID: {0}", sid);
sids.add(sid);
}
}
if (logonInfo.getExtraSids() != null) {
for (PacSid pacSid : logonInfo.getExtraSids()) {
String sid = PacUtility.binarySidToStringSid(pacSid.toString());
LOGGER.log(Level.FINER, "Adding extra group SID: {0}", sid);
sids.add(sid);
}
}
if (logonInfo.getResourceGroupSids() != null) {
for (PacSid pacSid : logonInfo.getResourceGroupSids()) {
String sid = PacUtility.binarySidToStringSid(pacSid.toString());
LOGGER.log(Level.FINER, "Adding resource group SID: {0}", sid);
sids.add(sid);
}
}
}
}
The parsing is only executed once, the first user access, in the following requests the SIDs are recovered from the Java session. The SIDs have been added to the SpnegoPrincipal.java and (with a proper cast) they can be got from it or using isUserInRole method (roles references cannot be retrieved, so the argument should be a SID).
// request the SIDs as a list
List<String> sids = ((SpnegoPrincipal)request.getUserPrincipal()).getGroupSIDs();
// check if the user belogs to "Domain Users" (SID = 513)
request.isUserInRole("S-1-5-21-2145774160-2038213957-1596523949-513");
I already know that all the solution is a shit but it should be considered as a simple PoC (better integration should be done in a real project). And more important, the solution is feasible, here it is my demonstration video (it just shows how the SIDs are obtained from the token in the first request and, in the second, they are just got from the session).
Well today's entry is a horrible messed JAR that lets the SPNEGO filter decode the PAC (AD Kerberos custom section) in order to add to the principal the list of SIDs (group identifiers) of the logged user. The JAR (the project can be downloaded in a previous link) is the SPNEGO project original JAR file with some additions, a modified jaaslounge decoding package, used for Kerberos token parsing, and a utility class from Spring Kerberos authentication, used to transform binary SIDs into strings. Please remember all these entries use Samba 4 as the DC. The solution is just a PoC, a proof that Jens' idea about Windows Kerberos security is possible. I know the whole implementation is (very) improvable but it is the best I could do with the little time I spent on it, I will try to get a more robust solution if I get time.
Sometimes you eat the bear... And sometimes the bear eats you.
Comments