Today I continue my series of posts that cover a behind the scenes look at how Active Directory Federation Service (AD FS) and the Microsoft Web Application Proxy (WAP) interact. In my first post I explained the business cases that would call for the usage of a WAP. In my second post I did a deep dive into the WAP registration process (MS refers to this as the trust establishment with AD FS and the WAP). In this post I decided to cover how user certificate authentication is achieved when AD FS server is placed behind the WAP.
AD FS offers a few different options to authenticate users to the service including Integrated Windows Authentication (IWA), forms-based authentication, and certificate authentication. Readers who work in environments with sensitive data where assurance of a user’s identity is important should be familiar with certificate authentication in the Microsoft world. If you’re unfamiliar with it I recommend you take a read through this Microsoft article.
With the recent release of the National Institute of Standards and Technology (NIST) Digital Identity Guidelines 800-63 which reworks the authenticator assurance levels (AAL) and relegates passwords to AAL1 only, organizations will be looking for other authenticator options. Given the maturity of authenticators that make use of certificates such as the traditional smart card it’s likely many organizations will look at opportunities for how the existing equipment and infrastructure can be further utilized. So all the more important we understand how AD FS certificate authentication works.
I’ll be using the lab I described in my first post. I made the following modifications/additions to the lab:
- Configure Active Directory Certificate Services (AD CS) certificate authority (CA) to include certificate revocation list (CRL) distribution point (CDP). The CRLs will be served up via an IIS instance with the address crl.journeyofthegeek.com. This is the only CDP listed in the certificates. Certificates created during my original lab setup that are installed within the infrastructure do not include a CDP.
- Added a non-domain-joined Windows 10 computer which be used as the endpoint the test user accesses the federation service from.
Tool-wise I used ProcMon, Fiddler, API Monitor, and WireShark.
So what did I discover?
Prior to doing any type of user interaction, I setup the tools I would be using moving forward. On the WAP I started ProcMon as an Administrator and configured my filters to capture only TCP Send and TCP Receive operations. I also setup WireShark using a filter of ip.addr==192.168.100.10 && tcp.port==80. The IP address is the IP of the web server hosting my CRLs. This would ensure I’d see the name of the process making the connection to the CDP as well as the conversation between the two nodes.
** Note that the machine will cache the CRLs after they are successfully downloaded from the CDP. It will not make any further calls until the CRLs expire. To get around this behavior while I was testing I ran the command certutil -setreg chain\ChainCacheResyncFiletime @now as outlined in this article. This forces the machine to pull the CRLs again from the CDP regardless of whether or not they are expired. I ran the command as the LOCAL SYSTEM security principal using psexec.
The final step was to start Fiddler as the NETWORK SERVICE security principal using the command psexec -i -u “NT AUTHORITY\Network Service” “C:\Program Files (x86)\Fiddler2\Fiddler.exe”. Remember that Fiddler needs the public key certificate in the appropriate file location as I outlined in my last post. Recall that the Web Application Proxy Service and the Active Directory Federation Service running on the WAP both run as that security principal.
Once all the tools were in place I logged into the non-domain joined Windows 10 box and opened up Microsoft Edge and popped the username of my test user into the username field.
After home realm discovery occurred within Azure AD, I received the forms-based login page of my AD FS instance.
Let’s take a look at what’s happened on the WAP so far.
In the initial HTTP Connect session the WAP makes to the AD FS farm, we see that the ClientHello handshake occurs where the WAP authenticates to the AD FS server to authenticate itself as described in my last post.
Once the secure session is established the WAP passes the HTTP GET request to the AD FS server. It adds a number of headers to the request which AD FS consumes to identify the client is coming from the WAP. This information is used for a number of AD FS features such as enforcing additional authentication policies for Extranet access.
The WAP also passes a number of query strings. There are a few interesting query strings here. The first is the client-request-id which is a unique identifier for the session that AD FS uses to correlate event log errors with the session. The username is obvious and shows the user’s user principal name that was inputted in the username field at the O365 login page. The wa query string shows a value of wsignin1.0 indicating the usage of WS-Federation. The wtrealm indicates the relying party identifier of the application, in this case Azure AD.
The wctx query string is quite interesting and needs to be parsed a bit on its own. Breaking down the value in the parameter we come across three unique parameters.
LoginOptions=3 indicates that the user has not selected the “Keep me signed in” option. If the user had selected that checkbox a value of 1 would have been passed and AD FS would create a persistent cookie which would exist even after the browser closes. This option is sometimes preferable for customers when opening documents from SharePoint Online so the user does not have to authenticate over and over.
The estsredirect contains the encoded and signed authentication request from O365. I stared at API monitor for a few hours going API call by API call trying to identify what this looks like once it’s decoded, but was unsuccessful. If you know how to decode it, I’d love to know. I’m very curious as to its contents.
The WAP next makes another HTTP GET to the AD FS server this time including the additional query string of pullStatus which is set equal to 0. I’m clueless as to the function on of this, I couldn’t find anything. The only other thing that changes is the referer.
My best guess on the above two sessions is the first session is where AD FS performs home realm discovery and maybe some processing on to determine if there are any special configurations for the WAP such as limited or expanded authentication options (device authN, certAuthN only). The second session is simply the AD FS server presenting the authentication methods configured for Extranet users.
The user then chooses the “Sign in with an X.509 certificate” (I’m not using SNI to host both forms and cert authN on the same port) and the WAP then performs another HTTP CONNECT to port 49443 which is the certificate authentication endpoint on the AD FS server. It again authenticates to the AD FS server with its client certificate prior to establishing the secure tunnel.
The third session we see a HTTP POST to the AD FS server with the same query parameters as our previous request but also providing a JSON object with a key of AuthMethod and the key value combination of AuthMethod=CertificateAuthentication in the body.
The next session is another HTTP POST with the same JSON object content and the key value pairs of AuthMethod=CertificateAuthentication and RetrieveCertificate=1 in the body. The AD FS server sends a 307 Temporary Redirect to the /adfs/backendproxytls/ endpoint on the AD FS server.
Prior to the redirect completing successful we see the calls to the CDP endpoint for the full and delta CRLs.
I was curious as to which process was pulling the CRLs and identified it was LSASS.EXE from the ProcMon capture.
At the /adfs/backendproxytls/ endpoint the WAP performs another HTTP POST this time posting a JSON object with a number of key value combinations.
The interesting key value types included in the JSON object are the nested JSON object for Headers which contains all the WAP headers I covered earlier. The query string JSON object which contains all the query strings I covered earlier. The SeralizedClientCertificate contains the certificate the user provided after selecting to use certificate authentication. The AD FS server then sends back a cookie to the WAP. This cookie is the cookie the representing the user’s authentication to the AD FS server as detailed in this link.
The WAP then performs a final HTTP GET back at the /adfs/ls/ endpoint including the previously described headers and query strings as well as provided the cookie it just received. The AD FS server responds by providing the assertion requested by Microsoft along with a MSISAuthenticated, MSISSignOut, and MSISLoopDetectionCookie cookies which are described in the link above.
What did we learn?
- The certificate is checked at both the WAP and the AD FS server to ensure it is valid and issued from a trusted certificate authority. Remember to verify you trust the certificate chain of any user certificates on both the AD FS servers and WAPs.
- CRL Revocation checking is enabled by default and is performed on both the AD FS server and the WAP. Remember to verify the locations in your CDP are available by both devices.
- The AD FS servers use the LSALogonUser function in the secur32.dll library to perform standard certificate authentication to Active Directory Domain Services. I didn’t include this, but I captured this by running API monitor on the AD FS server.
In short, if you’re going to use device authentication or user certificate authentication make sure you have your PKI components in order.
See you next post!