Saturday, April 29, 2017

Using KeePass with Multi Cert Key Provider plugin

I love the KeePass password managing tool, and I love the "Multi Cert Key Provider" plugin for it by Dirk Heitzmann, which allows opening the encrypted KeePass file with one or many certificates. It's very useful for sharing passwords and other sensitive data among members of a small team - everyone has his own cert and decrypts the file with his own key.

Unfortunately, the plugin is too picky: it demands that each certificate used with it would have "Data Encipherment" purpose listed in "Key usage" field. If it hasn't, the plugin just ignores the cert.

Until not so long ago, personal e-mail certificates issued by StartCom included this purpose in "Key Usage". But recently StartCom changed that - their certs now only  have "Digital Signature" and "Key Encipherment" extensions.

Of course, I could just create self-signed or internal-CA-signed certificate with any extensions I need, but why add one more cert, when I already have several?

So I just modified the plugin a bit. That's how it's done.

  1. Download the plugin source from here.
  2. Edit the keyfileClass4plugin.cs file, specifically the RSASelectCertificate() function:


public Boolean RSASelectCertificate()
{
    X509Store store = new X509Store("MY", StoreLocation.CurrentUser);
    store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
    X509Certificate2Collection collection = (X509Certificate2Collection)store.Certificates;
    X509Certificate2Collection fcollection = (X509Certificate2Collection)collection.Find(X509FindType.FindByTimeValid, DateTime.Now, false);
    fcollection = (X509Certificate2Collection)fcollection.Find(X509FindType.FindByKeyUsage, X509KeyUsageFlags.DataEncipherment, true);
    X509Certificate2Collection scollection = X509Certificate2UI.SelectFromCollection(fcollection, "Certificate Select", "Select a certificate from the following list", X509SelectionFlag.SingleSelection);
    if (scollection.Count == 0)
    {
        throw new ArgumentOutOfRangeException("ERROR : No certificate selected.");
    }
    if (!scollection[0].HasPrivateKey)
    {
        throw new CryptographicException("ERROR : Certificate contains no private key.");
    }
    p_DecryptRSACert = scollection[0];
    store.Close();
    return true;

};
           
Change two lines:
  • replace the new X509Store("MY", StoreLocation.CurrentUser) line by new X509Store("KeePass", StoreLocation.CurrentUser).
  • Comment out this line:
    fcollection = (X509Certificate2Collection)fcollection.Find(X509FindType.FindByKeyUsage, X509KeyUsageFlags.DataEncipherment, true);
Now build the MultiCertKeyProvider.dll file from these source files and drop it to the KeePass Plugins folder.

Why replacing the default "MY" store (which is synonymous to "Personal" folder in certmgr.msc) by "KeePass" store? Just because there may be several certificates in "Personal", and choosing between one of them each time you want to open a KeePass file is tiresome and unnecessary. 
So we can copy our certificate into another folder, where it will reside along.

But before that, of course, we need to create it. It's done with this command:
makecert -sr CurrentUser -ss KeePass
It's created with some default cert in it - use certmgr.msc to delete it and then just copy your KeePass cert from "Personal" to "KeePass" store. It can reside it two places while referencing the same private key.

That's all, mischief managed. :-)


Improving accuracy of timestamps based on system time

Run this small Java program on Windows. What output would you expect?


public class TimeAccuracyChecker
{
    public static void main(String[] args)
    {
        long first;
        long next;
first = System.currentTimeMillis();
        while (true)
        {
            next = System.currentTimeMillis();
            if (first != next)
                break;
        }
        System.out.println("First value: " + first + ", next value: " + next + ", difference: " + (next - first));
    }
}

The difference will vary from 15 to 16 msec:
C:\> java TimeAccuracyChecker

First value: 1447113411315, next value: 1447113411331, difference: 16​
Why? Because in Windows, system time is measured by interrupts from a timer (called "ticks"), which triggers them at equal intervals.
By default, such interval is (2-6) = 0.015625​ seconds. That's called "precision -6":
C:\> w32tm /query /status
Leap Indicator: 0(no warning)
Stratum: 3 (secondary reference - syncd by (S)NTP)
Precision: -6 (15.625ms per tick)
Root Delay: 0.2208562s
Root Dispersion: 7.8352645s
ReferenceId: 0x1765BB44 (source IP:  23.101.187.68)
Last Successful Sync Time: 2015-11-10 01:29:10
Source: time.windows.com,0x9
Poll Interval: 11 (2048s​)
(That's why we can see that a server, which handled a message first, has logged a timestamp bigger than server which handled it afterwards, with difference up to 16 msec - even if both servers are synced well to the same clock source).


But wait, you'll say, doesn't Thread.sleep()​ work with millisecond precision? Right, it does. Because it changes the timer resolution level to 1 msec by calling Windows function ​timeBeginPeriod() at start, and returning it to default by calling timeEndPeriod() at finish.

Now what's interesting is that this change is global on system level. This means that if a single thread changes the timer resolution to smaller value, then this affects not only other threads in this process - it affects all processes in the OS! Because now the CPU(s) will receive the interrupts from the timer at smaller intervals.

(That's why the output of the above example can change, if you'll run it alongside with Intellij IDEA on your workstation, for example).

So if we're interested in improving the accuracy of timestamps in our program, all we have to do is to launch a single backgroup thread, which will do nothing but sleep.
Check this out:
public class TimeAccuracyWithBackgroundSleepChecker
{
    public static void main(String[] args)
    {
        long first;
        long next;
        
        Thread t = new Thread()
        {
            public void run()
            {
                try
                {
                    Thread.sleep(Integer.MAX_VALUE);
                } catch (InterruptedException e) {}
            }
        };
        t.start();
        
        first = System.currentTimeMillis();
        while (true)
        {
            next = System.currentTimeMillis();
            if (first != next)
                break;
        }
                
        System.out.println("First value: " + first + ", next value: " + next + ", difference: " + (next - first));
        t.interrupt();
    }
}​

Output:
C:\> java TimeAccuracyWithBackgroundSleepChecker
First value: 1447116447771, next value: 1447116447772, difference: 1
This has a price. Quote from MSDN:
However, it can also reduce overall system performance, because the thread scheduler switches tasks more often. High resolutions can also prevent the CPU power management system from entering power-saving modes. Setting a higher resolution does not improve the accuracy of the high-resolution performance counter.


The TimerTool small utility shows what's the current timer resolution on your machine and can change it. Check it out while running the examples.

Authorization of Tomcat users by client certificates & LDAP

Purpose:


We want to achieve the same aim as in the "Authorization of Tomcat clients by Kerberos and LDAP" article, but with some difference: we want user to authenticate with client certificate rather than with Kerberos ticket.
Still, we want to query Active Directory by LDAP for the user's groups (which will map to Tomcat roles), and we want to utilize Kerberos to authenticate Tomcat to LDAP.

(We could also combine these two technics: require user's cert to establish SSL channel, then get his Kerberos ticket via SPNEGO and query LDAP by UPN from the ticket, as described here. Add to this "strong protection", requiring password for the private key, and you'll get 3-factor authentication. :-) )


Prerequisites:

  • Tomcat server should have a certificate with its name in Subject or SAN fields and with "Server Authentication" in "Enhanced Key Usage" field. It should be stored in Personal store of the account used to execute Tomcat, and its private key should be kept in CryptoAPI CSP, not in newer CNG KSP (Java doesn't support these yet).
  • It should also have a CA certificate in its Personal store (without private key) - it will be used to tell the user, certificates issued by which CAs will be accepted.
  • Tomcat should also have a keytab file, created with password of legitimate LDAP user - it will be used to bind to LDAP.
  • Tomcat should have ldaptive​ JAR in its lib folder.
  • The user should have a client certificate with his LDAP Distinguished Name (and nothing else - no e-mail, for instance) in Subject, and with "Client Authentication" in "Enhanced Key Usage" field.​


Tomcat configuration:

server.xml file:

We need to add this HTTPS-enabled Connector:
​<Connector
port="443"
protocol="org.apache.coyote.http11.Http11Protocol"
maxThreads="150"
SSLEnabled="true"
scheme="https"
secure="true"
sslProtocol="TLS" 
keystoreProvider="SunMSCAPI"
keystoreType="Windows-MY"
keyAlias="website.hosting.org"
keystoreFile=""
truststoreProvider="SunMSCAPI"
truststoreType="Windows-MY"
truststoreFile=""
clientAuth="true" 
/>   
​Notes:
  • keystoreProvider="SunMSCAPI"truststoreProvider="SunMSCAPI" - tells Tomcat to work with Windows certificates stores.
  • keystoreType="Windows-MY" - tells it to search for server's certificate + private key in the Personal store of account running Tomcat.
  • truststoreType="Windows-MY"​ - tells it where to enumerate CAs of the acceptable client certificates. Points to Personal store as well. We could also set truststoreType="Windows-ROOT"​​ as well - this will point to "Trusted Certification Authorities" store, but it lists too many CAs.
  • keystoreFile="", ​truststoreFile=""​ - needed to avoid error, although these variables are of no use: we don't use any JKS or PFX keystores.
  • keyAlias - name of the server's certificate. May be either CN in Subject, or a "friendly name" set in certificate's Properties. "Friendly name" has priority over CN.
This is the only part needed for authentication; all others are for LDAP groups-based ​authorization.

JVM variables:

Tomcat should be started with these Java parameters:

​-Djava.security.krb5.conf=%CATALINA_HOME%\conf\krb5.ini

-Djava.security.auth.login.config=%CATALINA_HOME%​\conf\jaas.conf

-Djavax.security.auth.useSubjectCredsOnly=false

  • java.security.krb5.conf - tells where to find Kerberos configuration file.
  • java.security.auth.login.config - tells where to find config file for JAAS login modules.
  • javax.security.auth.useSubjectCredsOnly=false - makes Tomcat to authenticate to LDAP with its own credentials (keytab file), rather than with user's.


web.xml file:

Can be set per webapp, in WEB-INF folder, or per whole Tomcat server, in %CATALINA_HOME%\conf.
Should contain settings like these:
<security-constraint>
    <web-resource-collection>
        <web-resource-name>Entire App</web-resource-name>
        <url-pattern>/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <role-name>Webapp-Users/role-name>
    </auth-constraint>
</security-constraint>
<login-config>
    <auth-method>CLIENT-CERT</auth-method>
    <realm-name></realm-name>
</login-config>
<security-role>
    <role-name>Webapp-Users</role-name>
</security-role>


Here I'm requiring that access to all URLs of specific webapp or the whole server will be given only to users having Webapp-Users role (in fact, which are members of Webapp-Users group).


context.xml file:

Can be set per webapp, in META-INF folder, or in %CATALINA_HOME%\conf\[enginename]\[hostname], or per whole Tomcat server, in %CATALINA_HOME%\conf, as described here.
Should include these lines:

​<Valve className="org.apache.catalina.authenticator.SSLAuthenticator" alwaysUseSession="true"/>
<Realm className="org.apache.catalina.realm.JAASRealm" appName="ldaptive" userClassNames="org.ldaptive.jaas.LdapPrincipal" roleClassNames="org.ldaptive.jaas.LdapRole" stripRealmForGss="false"/>    


Notes:
  • We could not include the SSLAuthenticator explicitly, because CLIENT-CERT auth-method in web.xml causes this Valve to be used automatically - but we need it for alwaysUseSessionparameter. This parameter causes Tomcat to provide a cookie to the user, which will use it in subsequent calls, instead of quering LDAP on each user's HTTP request.
  • appName="ldaptive"​ - defines to JAASRealm, which section of JAAS configuration file (jaas.conf) to use.
  • userClassNames​ - defines which class will be used to store user's name.
  • roleClassNames - defines instances of which class will be used to store user's groups (a.k.a "roles").
  • stripRealmForGss="false" means that user's name will be in "username@ourcompany.com" format, not just "username". We'll want this in cases where our Active Directory domain has trust relationships​ with other domains (in which the "username" can be associated with another account). The short format doesn't guarantee unique names, the long one does.

jaas.conf file:

com.sun.security.jgss.initiate
{
    com.sun.security.auth.module.Krb5LoginModule required
    doNotPrompt="true"
    principal="HTTP/website.hosting.org@OURCOMPANY.COM"
    useKeyTab="true"
    debug=false
    keyTab="../conf/ccs-keytab";
};
ldaptive
{
 org.ldaptive.jaas.LdapRoleAuthorizationModule required
    javax.security.sasl.server.authentication="true"
    ldapUrl="ldap://dc01.ourcompany.com:3268 ldap://dc02.ourcompany.com:3268"
    connectionStrategy="ACTIVE_PASSIVE"
    searchEntryHandlers="org.ldaptive.handler.RecursiveEntryHandler{{searchAttribute=memberOf}{mergeAttributes=cn}}"
    bindSaslConfig="{mechanism=GSSAPI}"
    baseDn="DC=OURCOMPANY,DC=COM"
    subtreeSearch="true"
    roleFilter="(&(member={user})(objectClass=group))"
    roleAttribute="cn";
};​
Notes:
  • The ldaptive section tells ldaptive​ that we know DN of the user (it will be obtained by SSLAuthenticator from the client cert) and want to get groups the user is member of.
  • The ​com.sun.security.jgss.initiate section is used, when ldaptive authenticates by Kerberos to LDAP. It tells where the keytab file is located.

krb5.ini file:

This is used to speed up discovery of Kerberos Key Distribution Center, KDC (normally domain controller):

[libdefaults]  
default_realm = OURCOMPANY.COM

[realms]
COMPANY.COM = {
             kdc = dc01.ourcompany.com
}

[domain_realm]
.ourcompany.com = OURCOMPANY.COM
ourcompany.com  = OURCOMPANY.COM​ 

Authorization of Tomcat clients by Kerberos and LDAP

​​​​​​​​​​Motivation
We are interested in following scenario:
  • Tomcat authenticates clients by Kerberos. So it has a keytab file for a valid Active Directory account, used to decrypt and validate clients' tickets. 
  • It means that the server cannot know passwords of its users.
  • ​So we want to use the same keytab to make the server to bind (authenticate) to domain controller's LDAP and then send queries to it in order to discover, which groups the user belongs to, in order to map them to webapp's roles.​

Configuration
Let's say, our web application runs on a server called website.hosting.org. We want it to authenticate users from Active Directory domain ourcompany.com.

I choose these names to stress that the  site doesn't have to be part of Active Directory in any way - Tomcat process doesn't have to run under domain account, and the computer it runs on doesn't have to be joined to domain at all. But it has to be able to reach domain controllers by LDAP.


Keytab file

First, we need to create Active Directory account for our webapp. Let's call it webapp@ourcompany.com.

Again, it's important to mention that although Tomcat on that PC runs on Windows, this account has nothing to do with the Windows account running the Tomcat's process or with computer's account in the domain (which can be none). It's just some AD account whose keytab is owned by our webapp - so the webapp can use this keytab to decrypt and check Kerberos tickets arriving from clients.

For security purposes, I removed this webapp@ourcompany.com account from the default Domain Users group and added it to another group, ServiceAccounts, set as Primary group. So this account is granted minimal permissions in the domain.

Next, we generate the keytab file for this account by executing Windows ktpass utility (installed by default on domain controllers):
ktpass /princ HTTP/website.hosting.org@OURCOMPANY.COM /mapuser OURCOMPANY\webapp /pass userPassword /ptype KRB5_NT_SRV_INST /crypto RC4-HMAC-NT /out tomcat-keytab​ -setupn

What happens here?
  • We associate this webapp account with Service Principal Name HTTP/website.hosting.org. This SPN will be used by clients to retrieve tickets to our webapp from KDC.
  • The userPassword of the account is encrypted with RC4 and saved to the keytab file, together with the SPN. It will be used by SpnegoAuthenticator to decrypt clients' tickets.
  • The "encryption type" (/crypto option) is set to RC4-HMAC-NT, because our domain OURCOMPANY.COM has both Windows 2003 and Windows 2008 domain controllers. Win2003 DC supports DES and RC4 encryption, while Win2008 by default supports RC4 and AES (and DES can be added per user) - see details here. So the most convenient option is RC4-HMAC-NT- it's supported out-of-the-box by DCs of both generations - but AES256-SHA1​ is the most secure.
    If we do go for the AES256-SHA1 option, then two additional steps have to be done:
    • In "Active Directory Users and Computers" tool, we need to mark in the properties of webapp account the flag "This account supports Kerberos AES 256 bit option".
    • We need to install Java Cryptography Extension package by replacing two files in %JAVA_HOME%/jre/lib/securitylocal_policy.jar & US_export_policy.jar​.
  • The "principal type" (/ptype option) is set to KRB5_NT_SRV_INST to specify that the keytab can be used to accept tickets, not to request them.
  • The keytab file is saved under name tomcat-keytab. We'll move it to our Tomcat /conf folder.
  • The -setupn option prevents ktpass from overwriting userPrincipalName of the webapp account - it stays webapp@ourcompany.com. Otherwise ktpass would change it to the SPN value, HTTP/website.hosting.org, which will create problems.
Verifications:

Contents of the keytab file can be validated by ktab command from JRE:
C:\TomEE\conf> ktab -k tomcat-keytab -l -e
Keytab name: tomcat-keytab
KVNO Principal
---- --------------------------------------------------------------------
   9 HTTP/website.hosting.org@OURCOMPANY.COM (23:RC4 with HMAC)​


SPN association can be checked with Windows setspn command.
Looking up SPNs by account:
C:\> setspn -L webapp
Registered ServicePrincipalNames for CN=webapp,OU=Service_Accounts,DC=ourcompany,DC=com:
        HTTP/website.hosting.org


Looking up account by ​SPN:
C:\> setspn -Q HTTP/website.hosting.org
Checking domain DC=ourcompany,DC=com
CN=webapp,OU=Service_Accounts,DC=ourcompany,DC=com
        HTTP/website.hosting.org
Existing SPN found!

How to check the KVNO number associated with the account - run this commands in Windows PowerShell:
$User = [ADSI]"LDAP://CN=webapp,OU=Service_Accounts,DC=ourcompany,DC=com"
$User.psbase.RefreshCache("msDS-KeyVersionNumber")
$Key = $User.Properties.Item("msDS-KeyVersionNumber")
Write-Host $Key



%CATALINA_HOME%\bin\setenv.bat​:

​@echo off
REM Needed to allow Tomcat to authenticate to Kerberos KDC and request service tickets with its own keytab:
set JAVA_OPTS=%JAVA_OPTS% -Djavax.security.auth.useSubjectCredsOnly=false

Notes:
  • The javax.security.auth.useSubjectCredsOnly=false setting is required, if we want Tomcat to authenticate with its own credentials, instead of the client's password or delegated ticket. Unfortunately, this is Tomcat-wide setting.
 
META-INF/context.xml:

​<?xml version="1.0" encoding="UTF-8"?>
<Context>

    <WatchedResource>WEB-INF/web.xml</WatchedResource>
      <Valve className="org.apache.catalina.authenticator.SpnegoAuthenticator" storeDelegatedCredential="false" alwaysUseSession="true" loginConfigName="SpnegoAuthenticator"​/>​
      <Realm className="org.apache.catalina.realm.JAASRealm" appName="ldaptive" userClassNames="org.ldaptive.jaas.LdapPrincipal" roleClassNames="org.ldaptive.jaas.LdapRole" stripRealmForGss="false"/>
</Context>
Notes about SpnegoAuthenticator:
  • storeDelegatedCredential="false" means that we don't want to use delegation and to store client's ticket for this purpose.
  • alwaysUseSession="true" creates HttpSession object, associates JSESSIONID cookie with it and passes the cookie to the browser. Subsequent requests from the browser will carry this cookie, so they won't pass the whole authentication process again to be served.
  • loginConfigName="SpnegoAuthenticator" defines section in the jaas.conf file (see below), where server-side Kerberos settings are kept.
Notes about JAASRealm:
  • appName="ldaptive" defines section in the jaas.conf file (see below), where login modules used by this Realm are defined with their parameters.
  • userClassNames and RoleClassNames define what kinds of Java objects will represent user's principal and his roles. These objects will be associated with user's Subject by the login modules.
  • stripRealmForGss="false" means that user's name will be in "username@domain.fqdn" format, not just "username". We'll want this in cases where our Active Directory domain has trust relationships​ with other domains (in which the "username" can be associated with another account). The short format doesn't guarantee unique names, the long one does.


%CATALINA_HOME%\conf\jaas.conf:

SpnegoAuthenticator
{
    com.sun.security.auth.module.Krb5LoginModule required
    doNotPrompt=true
    principal="HTTP/website.hosting.org@OURCOMPANY.COM"
    useKeyTab=true
    keyTab="../conf/tomcat-keytab"
    storeKey=true
    isInitiator=false;
};

com.sun.security.jgss.initiate
{
    com.sun.security.auth.module.Krb5LoginModule required
    doNotPrompt="true"
    principal="HTTP/
website.hosting.org@OURCOMPANY.COM"
    useKeyTab="true"
    keyTab="../conf/tomcat-keytab";
};
ldaptive
{
 org.ldaptive.jaas.LdapDnAuthorizationModule required
    javax.security.sasl.server.authentication="true"
    storePass="true"
    ldapUrl="
ldap://dc01.ourcompany.com:3268 ldap://dc02.ourcompany.com:3268"
    connectionStrategy="ACTIVE_PASSIVE"
    bindSaslConfig="{mechanism=GSSAPI}"
    baseDn="
DC=ourcompany,DC=com"
    subtreeSearch="true"
    userFilter="(&(userPrincipalName={user})(objectClass=person))";
 org.ldaptive.jaas.LdapRoleAuthorizationModule required
    javax.security.sasl.server.authentication="true"​
    ldapUrl="
ldap://dc01.ourcompany.com:3268 ldap://dc02.ourcompany.com:3268"
    connectionStrategy="ACTIVE_PASSIVE"
    searchEntryHandlers="org.ldaptive.handler.RecursiveEntryHandler{{searchAttribute=memberOf}{mergeAttributes=cn}}"​
    bindSaslConfig="{mechanism=GSSAPI}"
    baseDn="
DC=ourcompany,DC=com"
    subtreeSearch="true"
    roleFilter="
(&(member={dn})(objectClass=group))"
    roleAttribute="
cn";
};

%CATALINA_HOME%\conf\krb5.ini:

This file is optional. 
The Krb5LoginModule knows to discover automatically the Kerberos realm and KDCs on Windows (in other words, current domain and its controllers), by several methods (see sun.security.krb5.​Config class for details):

To resolve the realm:
  • By reading the environment variable USERDNSDOMAIN​.
  • By getting DNS domain from station's hostname and querying DNS for TXT record ​_kerberos.ourcompany.com.
  • By reading the krb5.ini file.
Once the realm is known, the KDCs can be obtained by:
  • ​Reading the environment variable LOGONSERVER.
  • By quering DNS for SRV records _kerberos._udp.ourcompany.com & _kerberos._tcp.ourcompany.com.
  • By reading the krb5.ini file.
So we need this file only in case the other methods fail. But its presence may speed things up.
Example of such file can be found here or here​.
[libdefaults]  
default_realm = OURCOMPANY.COM
[realms]
OURCOMPANY.COM = {
    kdc = dc01.ourcompany.com
}

[domain_realm]
.ourcompany.com = OURCOMPANY.COM
ourcompany.com  = OURCOMPANY.COM​

WEB-INF/web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">
    <display-name>IntegratedAuth</display-name>
    <security-constraint>
        <web-resource-collection>
            <web-resource-name>Entire Application</web-resource-name>
            <url-pattern>/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>
Webapp-Users</role-name>
        </auth-constraint>
    </security-constraint>
    <login-config>
        <auth-method>SPNEGO</auth-method>
        <realm-name></realm-name>
    </login-config>
    <security-role>
        <role-name>
Webapp-Users</role-name>
    </security-role>
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
        <welcome-file>index.htm</welcome-file>
        <welcome-file>index.jsp</welcome-file>
        <welcome-file>default.html</welcome-file>
        <welcome-file>default.htm</welcome-file>
        <welcome-file>default.jsp</welcome-file>
    </welcome-file-list>
</web-app>
Notes:

  • Webapp-Users is a short ("common") name of the user's group in Active Directory - the full ("distinguished") name can be something like "CN=Webapp-Users,OU=Groups,DC=ourcompany,DC=com". The short name will be returned byLdapRoleAuthorizationModule as one of user's roles (in org.ldaptive.jaas.LdapRole​ object, see above). The roleAttribute="cn"​ parameter of the login module defines that we want to use the common name, not the distinguished name.So here we can use it as name of the security role to limit access to servlets and other web resources. We can also use it inside the servlet's code, in HttpServletRequest.isUserInRole​()​calls and in @ServletSecurity​ annotations (rolesAllowed parameter).

The flow:
Let's some it up. The process is this:
  • A client accesses website.hosting.org and is required to authenticate with SPNEGO ("Integrated Windows authentication"), as defined in the web.xml file. 
  • If a client is logged on under domain account and browser policy allows that, the browser will try to obtain Kerberos ticket for this site. If it's not possible, it may prompt the user for credentials.
  • If the client enters his username in UPN format (username@ourcompany.com), then browsers such as Chrome or IE will try to look up addresses of KDCs (domain controllers) for ourcompany.com, using DNS - even if the client isn't domain user or even if he's on standalone station. See this blog post about more details of this process (russian).
  • If a browser succeeds to find a domain controller and receive a Kerberos ticket for website.hosting.org, then it'll submit the ticket to the webapp.
  • The webapp will decrypt the ticket using its symmetrical key shared with domain controller, which is contained in the tomcat-keytab file, - and check its validity.
  • If the ticket is valid, then the webapp read user's UPN from it and will connect to domain controller by LDAP, authenticating by the same key in the same tomcat-keytab file.
  • It will use the UPN to resolve full Distinguished Name (DN) of the client.
  • Then it will use the DN to obtain list of groups this user is member of. The user should be joined to them directly, nesting groups will not be resolved.
  • These groups will be translated to JAAS roles.
  • If a client belongs to a group, for which <security-constraint> tag in web.xml allows access (in our case, Webapp-Users), he'll be served by the webapp. Happy end.
  • It's also worth to mention that client's UPN can be obtained programmatically inside the webapp by getUserPrincipal(), and his group membership can be validated by isUserInRole() calls, which are part of standard servlet specification.