18

I'm getting the below error. This seems to have only started after I upgraded my visual studio 2015 to have the first update. I have read a few threads here about this being an issue with the machine key? I'm not sure how to fix it though and prevent it. Currently I'm getting this error on my local machine when I run this in debug using IIS express.

Exception Details: System.Security.Cryptography.CryptographicException: Error occurred during a cryptographic operation.

Cache = db.UserTokenCacheList.FirstOrDefault(c => c.webUserUniqueId == userId);

// place the entry in memory
this.Deserialize((Cache == null) ? null : MachineKey.Unprotect(Cache.cacheBits,"ADALCache"));


[CryptographicException: Error occurred during a cryptographic operation.]
   System.Web.Security.Cryptography.HomogenizingCryptoServiceWrapper.HomogenizeErrors(Func`2 func, Byte[] input) +115
   System.Web.Security.Cryptography.HomogenizingCryptoServiceWrapper.Unprotect(Byte[] protectedData) +70
   System.Web.Security.MachineKey.Unprotect(ICryptoServiceProvider cryptoServiceProvider, Byte[] protectedData, String[] purposes) +62
   System.Web.Security.MachineKey.Unprotect(Byte[] protectedData, String[] purposes) +121
   LEDES.Models.ADALTokenCache..ctor(String signedInUserId) in C:\Users\RLewis\Source\Workspaces\Workspace\LEDES\LEDES\Models\AdalTokenCache.cs:28
   LEDES.Startup.<ConfigureAuth>b__7_0(AuthorizationCodeReceivedNotification context) in C:\Users\RLewis\Source\Workspaces\Workspace\LEDES\LEDES\App_Start\Startup.Auth.cs:54
   Microsoft.Owin.Security.OpenIdConnect.<AuthenticateCoreAsync>d__1a.MoveNext() +4388
   System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() +26
   Microsoft.Owin.Security.OpenIdConnect.<AuthenticateCoreAsync>d__1a.MoveNext() +5776
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +92
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +58
   System.Runtime.CompilerServices.TaskAwaiter`1.GetResult() +28
   Microsoft.Owin.Security.Infrastructure.<BaseInitializeAsync>d__0.MoveNext() +471
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +92
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +58
   System.Runtime.CompilerServices.TaskAwaiter.GetResult() +26
   Microsoft.Owin.Security.Infrastructure.<Invoke>d__0.MoveNext() +218
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +92
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +58
   System.Runtime.CompilerServices.TaskAwaiter.GetResult() +26
   Microsoft.Owin.Host.SystemWeb.IntegratedPipeline.<RunApp>d__5.MoveNext() +170
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +92
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +58
   System.Runtime.CompilerServices.TaskAwaiter.GetResult() +26
   Microsoft.Owin.Security.Infrastructure.<Invoke>d__0.MoveNext() +525
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +92
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +58
   System.Runtime.CompilerServices.TaskAwaiter.GetResult() +26
   Microsoft.Owin.Host.SystemWeb.IntegratedPipeline.<RunApp>d__5.MoveNext() +170
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +92
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +58
   System.Runtime.CompilerServices.TaskAwaiter.GetResult() +26
   Microsoft.Owin.Host.SystemWeb.IntegratedPipeline.<DoFinalWork>d__2.MoveNext() +166
   System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() +26
   Microsoft.Owin.Host.SystemWeb.Infrastructure.ErrorState.Rethrow() +26
   Microsoft.Owin.Host.SystemWeb.IntegratedPipeline.StageAsyncResult.End(IAsyncResult ar) +81
   Microsoft.Owin.Host.SystemWeb.IntegratedPipeline.IntegratedPipelineContext.EndFinalWork(IAsyncResult ar) +30
   System.Web.AsyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +380
   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +155

Here is the code from the where it is failing - This was generated by VS when i choose the Azure authentication when setting up the project.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;
using System.Linq;
using System.Web;
using System.Web.Security;
using Microsoft.IdentityModel.Clients.ActiveDirectory;

namespace LEDES.Models
{
    public class ADALTokenCache : TokenCache
    {
        private ApplicationDbContext db = new ApplicationDbContext();
        private string userId;
        private UserTokenCache Cache;

        public ADALTokenCache(string signedInUserId)
        {
            // associate the cache to the current user of the web app
            userId = signedInUserId;
            this.AfterAccess = AfterAccessNotification;
            this.BeforeAccess = BeforeAccessNotification;
            this.BeforeWrite = BeforeWriteNotification;
            // look up the entry in the database
            Cache = db.UserTokenCacheList.FirstOrDefault(c => c.webUserUniqueId == userId);
            // place the entry in memory
            this.Deserialize((Cache == null) ? null : MachineKey.Unprotect(Cache.cacheBits,"ADALCache"));
        }

        // clean up the database
        public override void Clear()
        {
            base.Clear();
            var cacheEntry = db.UserTokenCacheList.FirstOrDefault(c => c.webUserUniqueId == userId);
            db.UserTokenCacheList.Remove(cacheEntry);
            db.SaveChanges();
        }

        // Notification raised before ADAL accesses the cache.
        // This is your chance to update the in-memory copy from the DB, if the in-memory version is stale
        void BeforeAccessNotification(TokenCacheNotificationArgs args)
        {
            if (Cache == null)
            {
                // first time access
                Cache = db.UserTokenCacheList.FirstOrDefault(c => c.webUserUniqueId == userId);
            }
            else
            { 
                // retrieve last write from the DB
                var status = from e in db.UserTokenCacheList
                             where (e.webUserUniqueId == userId)
                select new
                {
                    LastWrite = e.LastWrite
                };

                // if the in-memory copy is older than the persistent copy
                if (status.First().LastWrite > Cache.LastWrite)
                {
                    // read from from storage, update in-memory copy
                    Cache = db.UserTokenCacheList.FirstOrDefault(c => c.webUserUniqueId == userId);
                }
            }
            this.Deserialize((Cache == null) ? null : MachineKey.Unprotect(Cache.cacheBits, "ADALCache"));
        }

        // Notification raised after ADAL accessed the cache.
        // If the HasStateChanged flag is set, ADAL changed the content of the cache
        void AfterAccessNotification(TokenCacheNotificationArgs args)
        {
            // if state changed
            if (this.HasStateChanged)
            {
                Cache = new UserTokenCache
                {
                    webUserUniqueId = userId,
                    cacheBits = MachineKey.Protect(this.Serialize(), "ADALCache"),
                    LastWrite = DateTime.Now
                };
                // update the DB and the lastwrite 
                db.Entry(Cache).State = Cache.UserTokenCacheId == 0 ? EntityState.Added : EntityState.Modified;
                db.SaveChanges();
                this.HasStateChanged = false;
            }
        }

        void BeforeWriteNotification(TokenCacheNotificationArgs args)
        {
            // if you want to ensure that no concurrent write take place, use this notification to place a lock on the entry
        }

        public override void DeleteItem(TokenCacheItem item)
        {
            base.DeleteItem(item);
        }
    }
}

Info from call stack and the point of hitting the CryptographicEsception occured

    System.Web.dll!System.Web.Security.Cryptography.HomogenizingCryptoServiceWrapper.HomogenizeErrors(System.Func<byte[], byte[]> func, byte[] input)   Unknown
    System.Web.dll!System.Web.Security.Cryptography.HomogenizingCryptoServiceWrapper.Unprotect(byte[] protectedData)    Unknown
    System.Web.dll!System.Web.Security.MachineKey.Unprotect(System.Web.Security.Cryptography.ICryptoServiceProvider cryptoServiceProvider, byte[] protectedData, string[] purposes) Unknown
    System.Web.dll!System.Web.Security.MachineKey.Unprotect(byte[] protectedData, string[] purposes)    Unknown
>   LEDES.dll!LEDES.Models.ADALTokenCache.ADALTokenCache(string signedInUserId) Line 28 C#
    LEDES.dll!LEDES.Startup.ConfigureAuth.AnonymousMethod__7_0(Microsoft.Owin.Security.Notifications.AuthorizationCodeReceivedNotification context) Line 54 C#
    Microsoft.Owin.Security.OpenIdConnect.dll!Microsoft.Owin.Security.OpenIdConnect.OpenIdConnectAuthenticationHandler.AuthenticateCoreAsync()  Unknown
    mscorlib.dll!System.Runtime.CompilerServices.AsyncMethodBuilderCore.MoveNextRunner.InvokeMoveNext(object stateMachine)  Unknown
    mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)   Unknown
    mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)   Unknown
    mscorlib.dll!System.Runtime.CompilerServices.AsyncMethodBuilderCore.MoveNextRunner.Run()    Unknown
    mscorlib.dll!System.Threading.Tasks.AwaitTaskContinuation.InvokeAction(object state)    Unknown
    mscorlib.dll!System.Threading.Tasks.AwaitTaskContinuation.RunCallback(System.Threading.ContextCallback callback, object state, ref System.Threading.Tasks.Task currentTask) Unknown
    mscorlib.dll!System.Threading.Tasks.SynchronizationContextAwaitTaskContinuation.Run(System.Threading.Tasks.Task task, bool canInlineContinuationTask)   Unknown
    mscorlib.dll!System.Threading.Tasks.Task.FinishContinuations()  Unknown
    mscorlib.dll!System.Threading.Tasks.Task.FinishStageThree() Unknown
    mscorlib.dll!System.Threading.Tasks.Task<System.__Canon>.TrySetResult(System.__Canon result)    Unknown
    mscorlib.dll!System.Runtime.CompilerServices.AsyncTaskMethodBuilder<Microsoft.Owin.IFormCollection>.SetResult(Microsoft.Owin.IFormCollection result)    Unknown
    Microsoft.Owin.dll!Microsoft.Owin.OwinRequest.ReadFormAsync()   Unknown
    mscorlib.dll!System.Runtime.CompilerServices.AsyncMethodBuilderCore.MoveNextRunner.InvokeMoveNext(object stateMachine)  Unknown
    mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)   Unknown
    mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)   Unknown
    mscorlib.dll!System.Runtime.CompilerServices.AsyncMethodBuilderCore.MoveNextRunner.Run()    Unknown
    mscorlib.dll!System.Threading.Tasks.SynchronizationContextAwaitTaskContinuation..cctor.AnonymousMethod__8_0(object state)   Unknown
    System.Web.dll!System.Web.AspNetSynchronizationContext.Post.AnonymousMethod__0()    Unknown
    System.Web.dll!System.Web.Util.SynchronizationHelper.SafeWrapCallback(System.Action action) Unknown
    System.Web.dll!System.Web.Util.SynchronizationHelper.QueueAsynchronous.AnonymousMethod__0(System.Threading.Tasks.Task _)    Unknown
    mscorlib.dll!System.Threading.Tasks.ContinuationTaskFromTask.InnerInvoke()  Unknown
    mscorlib.dll!System.Threading.Tasks.Task.Execute()  Unknown
    mscorlib.dll!System.Threading.Tasks.Task.ExecutionContextCallback(object obj)   Unknown
    mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)   Unknown
    mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)   Unknown
    mscorlib.dll!System.Threading.Tasks.Task.ExecuteWithThreadLocal(ref System.Threading.Tasks.Task currentTaskSlot)    Unknown
    mscorlib.dll!System.Threading.Tasks.Task.ExecuteEntry(bool bPreventDoubleExecution) Unknown
    mscorlib.dll!System.Threading.Tasks.Task.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() Unknown
    mscorlib.dll!System.Threading.ThreadPoolWorkQueue.Dispatch()    Unknown
    mscorlib.dll!System.Threading._ThreadPoolWaitCallback.PerformWaitCallback() Unknown
    [Native to Managed Transition]  
JQuery
  • 889
  • 3
  • 13
  • 35
  • A proper stack trace would help understanding your issue. – Martin Liversage Jan 07 '16 at 08:14
  • Can you check that thread: http://blogs.msdn.com/b/webdev/archive/2012/10/23/cryptographic-improvements-in-asp-net-4-5-pt-2.aspx there is a whole section on compatibility with machineKey settings in .config file. – Simon Mourier Jan 11 '16 at 10:14
  • Thanks I'll take a look. what I don't get is why is suddenly stopped working. worries me in case this happened if it was in production – JQuery Jan 11 '16 at 10:30
  • If the update somehow currupted your iis express certificates / keys (which can be root of you problem) try to go to Add/Remove Programs and choosing the "Repair" option on IIS Express. That way certificate will reinstall. – Marc Wittmann Jan 11 '16 at 11:55
  • It's having none of it. Done a repair on visual studio and iis, same problem – JQuery Jan 11 '16 at 15:45

6 Answers6

24

Ran into the same issue. After 'jacking with it' for more than a hour, I went into the member database (often auto-created by Visual Studio) and removed all the rows from the UserTokenCaches table. Ran the application, got past the crytographic error message. A new cache token record was created and inserted into the table.

Rob Vettor
  • 241
  • 2
  • 3
  • I also did the same thing...was thinking for posting answe...but it is already there. ...any way just clear your table "UserTokenCaches" it will solve your problem – Sandesh Daddi Oct 03 '16 at 09:52
  • This works for me but I don't know why. Azure Websites – jle Jan 12 '17 at 19:59
  • 2
    This is the most straightforward fix... Why does this happen though? – Tomanow Mar 08 '17 at 20:07
  • This just fixed the issue temporarily. Machine config was missing machine key. Adding it to the webconfig fixed the issue in my case – Ananth Sep 26 '18 at 18:36
6

added stack trace

You didn't. Pretty important to understand why you get such a useless exception message. It is intentional. System.Web hides the real reason that the cryptographic code failed. You get a bland error message ("it did not work") and no stack trace of the actual code that failed.

Important because not doing so is dangerous, it allows an attacker to probe your web app with intentionally malformed data and gain knowledge from the exceptions to find a way to crack your secure code.

You need to get a better stack trace and exception message to find the real reason. That requires you to tell the debugger to stop when the exception is thrown. The real one, not the bland one. In VS2015, use Debug > Windows > Exception Settings. Click the "Common Language Runtime Exceptions" checkbox so it turns from a rectangle to a check-mark. Also: Tools > Options > Debugging > General > untick the "Enable Just My Code" checkbox.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • thanks, that has given me another error now, the same as this which you have commented on - http://stackoverflow.com/questions/21985217/mscorlib-pdb-not-loaded-yet-the-mscorlib-dll-is-not-missing it finishes by going back to the error above and displaying the same 'stack trace' information shown above. Bit lost what to do next – JQuery Jan 11 '16 at 09:14
  • That is not an error. Use Debug > Windows > Call Stack to get the stack trace. The exception message is visible in the Output window. – Hans Passant Jan 11 '16 at 11:21
  • added the info from call stack – JQuery Jan 11 '16 at 11:41
4

Found a solution to this.

The section of code the error was coming from is the AdalToken.Cache.cs file.

userId = signedInUserId;
this.AfterAccess = AfterAccessNotification;
this.BeforeAccess = BeforeAccessNotification;
this.BeforeWrite = BeforeWriteNotification;
// look up the entry in the database
Cache = db.UserTokenCacheList.FirstOrDefault(c => c.webUserUniqueId == userId);
// place the entry in memory
this.Deserialize((Cache == null) ? null : MachineKey.Unprotect(Cache.cacheBits,"ADALCache"));

Specifically the last line.

Also relevant is the context for the db.UserTokenCacheList which is:

{
    public class ApplicationDbContext : DbContext
    {
        public ApplicationDbContext()
            : base("DefaultConnection")
        {
        }

        public DbSet<UserTokenCache> UserTokenCacheList { get; set; }
    }

    public class UserTokenCache
    {
        [Key]
        public int UserTokenCacheId { get; set; }
        public string webUserUniqueId { get; set; }
        public byte[] cacheBits { get; set; }
        public DateTime LastWrite { get; set; }
    }
}

All of this was generated by visual studio when I went through the wizard for setting up Azure authentication when I started this new project.

Regarding the base("DefaultConnection") in the ApplicationDbContext.

There was no entry for this in my web.config, however up until recently this has always worked.

In the web.config, within the < connectionStrings > I added a line for DefaultConnection to point at my database and now it all works, at least for now.

Hope this may be of help to anyone who gets the same error.

Bridge
  • 29,818
  • 9
  • 60
  • 82
JQuery
  • 889
  • 3
  • 13
  • 35
3

The reason for the CryptographicException is explained by the documentation for the MachineKey.Unprotect method, which is also available in VS' Intellisense:

"Possible causes include the following: The application is deployed to more than one server and is using auto-generated encryption keys." (emphasis added)

This error occurs when you have an existing session cookie with the same session ID as a previous session, but the server is using a different key for crypto operations. ADALTokenCache is encrypting / decrypting the session using symmetric encryption, which requires the same key in both directions.

The session data is retrieved from the UserTokenCaches table as follows:

  1. Get the userID (e.g. from claims)
  2. Retrieve cacheBits where webUserUniqueID = userID
  3. Decrypt cacheBits using MachineKey as the decryption key

When MachineKey has changed since the value was first encrypted, the decryption fails and throws a CryptographicException. You can create a similar situation by copying these two symmetric encryption examples: Encrypting Data and Decrypting Data, but changing either of the keys.

For some reason, when you updated Visual Studio it re-generated your MachineKey. Since your web browser still had the same session cookie, ASP.NET retrieved your earlier session from the DB, but couldn't decrypt it anymore. All the other answers solve the problem for local debugging by preventing AdalTokenCache from being able to retrieve the previous session, but don't really address the underlying reason (and certainly aren't viable for production systems!)

  • Clearing your cookies works, as ASP.NET has to create a new session instead of retrieving your previous one. This would be difficult to communicate to the users of a production system.
  • Deleting all rows from the UserTokenCaches database table works for the same reason - it can't retrieve the previous session and has to make a new one instead. This would end all user sessions across all production instances of your application, and you won't get a maintenance window to do it in, because some of your users will be getting exceptions.

A better solution is to ensure that production deployments are all using the same encryption key (e.g. set MachineKey in web.config), or by securing access to session data in a different way. Some people work around this by storing the encryption key in the same database as the encrypted data, but I don't really see the point of doing this...

Matt Wanchap
  • 841
  • 8
  • 20
1

Please check if your machine config file has Machine key configuration. If not then add machine key config in your web.config.

<system.web>    
    <machineKey
      validationKey="your validationKey"
      decryptionKey="your decryptionKey"      
    />
</system.web>

This solved the issue for me

Ananth
  • 10,330
  • 24
  • 82
  • 109
  • If you have a machine key already, I had to regenerate mine after updating the AD components. – Matty Sep 27 '18 at 18:25
0

None of these answers worked for me. In my case, clearing the history of the browser solved this problem. Probably, it could be enough just removing the cookies.

Tonatio
  • 4,026
  • 35
  • 24