SharePoint 2010 and 2013 FedAuth cookie encryption


The issue

 

If you are using SharePoint with AD FS, you may know how these two products interact. The following summarizes the login process:

  1. When the user is not logged in, SharePoint will redirect the user to the AD FS login page.
  2. The user enters his credentials.
  3. AD FS validates the credentials and creates a Security Token with the user's claims.
  4. AD FS makes the user's browser or client POST this token to SharePoint's /_trust endpoint.
  5. SharePoint validates the digital signature on the token and stores the token in the Distributed Cache.
  6. SharePoint creates a FedAuth cookie with some information about the session. The contents of the cookie are Base 64 encoded and sent to the client. Inside the cookie we can find the user identity claim, the scope, expiration and a couple more things.

For example, this is the content of a FedAuth cookie in my lab.

 

 <?xmlversion="1.0" encoding="utf-8"?><SP>05.t|adfs email|a1@email.local,05.t|adfs email|a1@email.local,130866957906607856,True,[signature],https://adfs.2013.sps/</SP>

 

Depending on the configuration of the AD FS provider in SharePoint, one of the claims in the AD FS token will be used as identity claim in SharePoint. This claims will be used to uniquely identify the user in SharePoint and thus it's very important to choose a property of the user that's unique. Most organizations choose the e-mail address or the User-Principal-Name.

 

Some organizations may want to use a different identity claim like the social security number or another fiscal information of the user. These organizations may consider this kind of information sensitive and they even may want to protect this information.

 

Since anybody can decode Base64, anybody can see the contents of the FedAuth cookie. In the one above, I have highlighted the identity claim. One way to protect this information would be to encrypt the value of the cookie.

 

The solution

 

SharePoint does not offer cookie encryption out of the box so if you want to encrypt this value, custom code needs to be created.

 

One possible solution could be a custom HttpModule that will encrypt the cookie when set by SharePoint and decrypt it when SharePoint receives it.

 

The custom HttpModule would attach to:

  • BeginRequest: to decrypt any FedAuth cookie that may come with the client's request.
  • EndRequest: to encrypt any FedAuth cookie that may be set by SharePoint to the client.

 

How should we encrypt the value?

 

I'm my opinion, a symmetric algorithm would be ideal over an asymetric. Symmetric algorithms are usually faster and don't require a key pair to work, just a password. How to choose a proper password and salt would be the interesting part. It has to be a password/salt combination that doesn't change between application pool recycles and both need to be the same in every web server. As an example, the code below uses a password and salt stored in the web.config of the application.

 

The code

 

The code consists of two parts: the custom HttpModule and a utility class to handle the encryption. This utility class I got from the following link: http://www.superstarcoders.com/blogs/posts/symmetric-encryption-in-c-sharp.aspx

 

This should be compiled to a DLL. The easiest way to install it would be to copy the DLL into the bin folder of the web application and then add the module with IIS Manager.

 

HttpModule

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Security.Cryptography;

using System.Text;

using System.Web;

using System.Web.Configuration;

 

namespace jesusfer.SharePoint.FedAuthEncryptionModule

{

    /// <summary>

    /// This is a HttpModule that will encrypt the value of a certain cookie when it's set by the server and will decrypt it when sent by the client.

    ///

    /// This is helpful in cases when the FedAuth cookie includes an identity claim that can be considered sensitive.

    /// </summary>

    public class FedAuthEncryptionModule : IHttpModule

    {

        private string m_CookieName = "FedAuth";

 

        // If true, unencrypted FedAuth cookies will still be allowed.

        // If false, unecrypted FedAuth cookies will be removed, which will force SPS to ask the user to authenticate again.

        private bool m_AllowUnencryptedCookies;

        private bool m_Enabled;

        private string m_Password;

        private string m_Salt;

 

        /*

         * These are the expected settings keys in the web.config:

         *

         * <configuration>

         * ...

         *    <appSettings>

         *       <add key="FedAuthEncryptionEnabled" value="true"/>

         *       <add key="FedAuthEncryptionPassword" value="FedAuthEncryptionPassword"/>

         *       <add key="FedAuthEncryptionSalt" value="FedAuthEncryptionSalt"/>

         *       <add key="FedAuthEncryptionAllowUnencryptedCookies" value="true"/>

         *    </appSettings>

         * ...

         * </configuration>

         * */

 

        public String ModuleName

        {

            get { return "CookieEncryptModule"; }

        }

 

        public void Dispose() { }

 

        public void Init(HttpApplication application)

        {

            // Read configuration

            m_Enabled = bool.Parse(WebConfigurationManager.AppSettings["FedAuthEncryptionEnabled"]);

            m_AllowUnencryptedCookies = bool.Parse(WebConfigurationManager.AppSettings["FedAuthEncryptionAllowUnencryptedCookies"]);

            m_Password = WebConfigurationManager.AppSettings["FedAuthEncryptionPassword"];

            m_Salt = WebConfigurationManager.AppSettings["FedAuthEncryptionSalt"];

 

            // Attach to the BeginRequest event so that we can check if the cookie is encrypted to decrypt it

            application.BeginRequest += new EventHandler(this.Application_BeginRequest);

            // Attach to the EndRequest event so that we can encrypt the coookie when it's set by SharePoint

            application.EndRequest += new EventHandler(this.Application_EndRequest);

        }

 

        /// <summary>

        /// This method will encrypt the cookie when it's set by SharePoint

        /// </summary>

        /// <param name="sender"></param>

        /// <param name="e"></param>

        private void Application_EndRequest(object sender, EventArgs e)

        {

            HttpApplication application = (HttpApplication)sender;

            HttpContext context = application.Context;

 

            if (m_Enabled && context.Response.Cookies.AllKeys.Contains(m_CookieName))

            {

                try

                {

                    // Try encrypting the cookie value and setting it as the new value

                    string plain = context.Response.Cookies[m_CookieName].Value;

                    string encrypted = CipherUtility.Encrypt<AesManaged>(plain, m_Password, m_Salt);

                    context.Response.Cookies[m_CookieName].Value = encrypted;

                }

                catch

                {

                    // Do nothing

                }

            }

        }

 

        /// <summary>

        /// This method will decrypt the cookie when sent by the client

        /// </summary>

        /// <param name="sender"></param>

        /// <param name="e"></param>

        private void Application_BeginRequest(object sender, EventArgs e)

        {

            HttpApplication application = (HttpApplication)sender;

            HttpContext context = application.Context;

 

            // m_enabled can be added as a condition here too, but that will break any FedAuth cookie that is encrypted and still valid

            if (m_Enabled && context.Request.Cookies.AllKeys.Contains(m_CookieName))

            {

                try

                {

                    // Try decrypting the cookie value and setting it as the new value

                    string encryped = context.Request.Cookies[m_CookieName].Value;

                    string decrypted = CipherUtility.Decrypt<AesManaged>(encryped, m_Password, m_Salt);

                    context.Request.Cookies[m_CookieName].Value = decrypted;

                }

                catch

                {

                    // We can choose to remove the cookie if decryption failed

                    if (!m_AllowUnencryptedCookies)

                    {

                        context.Request.Cookies.Remove(m_CookieName);

                    }

                }

            }

        }

    }

}

 

Utility Class

 

using System;

using System.Collections.Generic;

using System.IO;

using System.Linq;

using System.Security.Cryptography;

using System.Text;

 

namespace jesusfer.SharePoint.FedAuthEncryptionModule

{

    /// This class was copied from http://www.superstarcoders.com/blogs/posts/symmetric-encryption-in-c-sharp.aspx

 

    public class CipherUtility

    {

        public static string Encrypt<T>(string value, string password, string salt) where T : SymmetricAlgorithm, new()

        {

            DeriveBytes rgb = new Rfc2898DeriveBytes(password, Encoding.Unicode.GetBytes(salt));

 

            SymmetricAlgorithm algorithm = new T();

 

            byte[] rgbKey = rgb.GetBytes(algorithm.KeySize >> 3);

            byte[] rgbIV = rgb.GetBytes(algorithm.BlockSize >> 3);

 

            ICryptoTransform transform = algorithm.CreateEncryptor(rgbKey, rgbIV);

 

            using (MemoryStream buffer = new MemoryStream())

            {

                using (CryptoStream stream = new CryptoStream(buffer, transform, CryptoStreamMode.Write))

                {

                    using (StreamWriter writer = new StreamWriter(stream, Encoding.Unicode))

                    {

                        writer.Write(value);

                    }

                }

 

                return Convert.ToBase64String(buffer.ToArray());

            }

        }

 

        public static string Decrypt<T>(string text, string password, string salt) where T : SymmetricAlgorithm, new()

        {

            DeriveBytes rgb = new Rfc2898DeriveBytes(password, Encoding.Unicode.GetBytes(salt));

 

            SymmetricAlgorithm algorithm = new T();

 

            byte[] rgbKey = rgb.GetBytes(algorithm.KeySize >> 3);

            byte[] rgbIV = rgb.GetBytes(algorithm.BlockSize >> 3);

 

            ICryptoTransform transform = algorithm.CreateDecryptor(rgbKey, rgbIV);

 

            using (MemoryStream buffer = new MemoryStream(Convert.FromBase64String(text)))

            {

                using (CryptoStream stream = new CryptoStream(buffer, transform, CryptoStreamMode.Read))

                {

                    using (StreamReader reader = new StreamReader(stream, Encoding.Unicode))

                    {

                        return reader.ReadToEnd();

                    }

                }

            }

        }

    }

}

 

Comments (8)

  1. Pastasauce says:

    Fascinating, you have managed to provide an eloquent explanation to a profound concept of AD FS with claims. Analyzing the piecemeal operations of AD FS and claims, while arriving to the intent of the article, encrypting the FedAuth cookie. Thank you Jesus for writing this article I can see many government agencies utilizing this practice.

  2. Hi Jesus - In what format is the actual expiration date stored in the cookie ? In your example, is '130866957906607856' the expiration date ? If yes, what format is this date in, and how to decode it ?

    1. Hi Mario,

      Like many dates in SharePoint this is a FileTime formatted date. You can get the value with the following PowerShell:

      [DateTime]::FromFileTime([long]::Parse("130866957906607856", [CultureInfo]::InvariantCulture))

      1. Thanks Jesus. I was able to decode the expiration date value in the FedAuth cookie using your PowerShell code.

        I noticed that the expiration date value in the FedAuth cookie is based on the CookieLifetime value from Get-SPSecurityTokenServiceConfig (5 days, by default). However, the actual SharePoint session duration does not depend on the FedAuth cookie expiration date value. It's calculated as follows (SAML token lifetime - LogonTokenCacheExpirationWindow) .

        Do you know if there is a way using PowerShell to find what value SharePoint is using for the session duration ? (That is, without manually calculating SAML token lifetime - LogonTokenCacheExpirationWindow )

        1. Mario, the thing is that the session duration is based on the duration of the SAML token as you say. Actually, SharePoint 2013 will store the security token from SAML in the Distributed Cache for as long as the SAML token is valid and once the token expires, so it will the entry in the Distributed Cache. Once it expires, it will ask the user to login again, independently if the FedAuth cookie has expired or not.

          Taking into account that the actual expiration of the session is not a parameter you change in SharePoint, but in the SAML relying party, I don't see how you would get the actual duration from within SharePoint. If the RP is ADFS, then you can get that value with PowerShell in the ADFS server with:

          Get-AdfsRelyingPartyTrust | ft -auto Name,TokenLifetime

          I wrote a little about this in https://blogs.msdn.microsoft.com/jesusfer/2015/08/27/sharepoint-2013-authentication-lifetime-settings/

  3. Thanks for your response, Jesus. I agree with everything in your last comment, and know that I can get the SAML lifetime from the Trusted Identity Provider. Since the security tokens are stored in the SharePoint distributed cache, I was wondering if there was a way to query the tokens and their properties from PowerShell, but this is probably not possible / allowed for security reasons (and the tokens in SharePoint may be encrypted).

  4. Arthur Visser says:

    Hi Jesus, I remember you assisted us with this issue and you came up with this solution 😀 I searched it by coincidence because I was interested how it exactly worked and I see it has been turned into an article now. Thanks again!

    1. Cheers! It was quite an interesting issue and I figured somebody else might be interested 🙂

Skip to main content