The UTC time represented... Error in ReceiveSSO


My application currently handles SSO from two different IdPs. One works flawlessly. Another I receive a very unusual error after calling SAMLServiceProvider.ReceiveSSO.

The UTC time represented when the offset is applied must be between year 0 and 10,000.
Parameter name: offset

Stack trace:
at System.DateTimeOffset…ctor(DateTime dateTime)
at ComponentSpace.SAML2.Data.InMemoryIDCache.Add(String id, DateTime expirationDateTime) in c:\Sandboxes\ComponentSpace\SAMLv20\Library\Data\InMemoryIDCache.cs:line 42
at ComponentSpace.SAML2.InternalSAMLServiceProvider.CheckForAssertionReplay(SAMLAssertion samlAssertion) in c:\Sandboxes\ComponentSpace\SAMLv20\Library\InternalSAMLServiceProvider.cs:line 320
at ComponentSpace.SAML2.InternalSAMLServiceProvider.ProcessSAMLAssertion(SAMLAssertion samlAssertion, String& userName, SAMLAttribute[]& attributes) in c:\Sandboxes\ComponentSpace\SAMLv20\Library\InternalSAMLServiceProvider.cs:line 384
at ComponentSpace.SAML2.InternalSAMLServiceProvider.ReceiveSSO(HttpRequestBase httpRequest, Boolean& isInResponseTo, String& partnerIdP, String& userName, SAMLAttribute[]& attributes, String& relayState) in c:\Sandboxes\ComponentSpace\SAMLv20\Library\InternalSAMLServiceProvider.cs:line 599
at ComponentSpace.SAML2.SAMLServiceProvider.ReceiveSSO(HttpRequestBase httpRequest, Boolean& isInResponseTo, String& partnerIdP, String& userName, IDictionary& attributes, String& relayState) in c:\Sandboxes\ComponentSpace\SAMLv20\Library\SAMLServiceProvider.cs:line 231
at …Controllers.SSOController.d__1.MoveNext() in …\SSOController.cs:line 84
— End of stack trace from previous location where exception was thrown —
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Web.Mvc.Async.TaskAsyncActionDescriptor.EndExecute(IAsyncResult asyncResult)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass37.b__36(IAsyncResult asyncResult)
at Castle.Proxies.Invocations.AsyncControllerActionInvoker_EndInvokeActionMethod.InvokeMethodOnTarget()
at Castle.DynamicProxy.AbstractInvocation.Proceed()
at Glimpse.Mvc.AlternateType.AsyncActionInvoker.EndInvokeActionMethod.NewImplementation(IAlternateMethodContext context)
at Castle.DynamicProxy.AbstractInvocation.Proceed()
at Castle.Proxies.AsyncControllerActionInvokerProxy.EndInvokeActionMethod(IAsyncResult asyncResult)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.b__3d()
at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.<>c__DisplayClass46.b__3f()
at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass33.b__32(IAsyncResult asyncResult)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass21.<>c__DisplayClass2b.b__1c()
at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass21.b__1e(IAsyncResult asyncResult)

I can’t find anything at all related to the SAML library that would lead me to an answer. Any ideas?

The SAML assertion includes a validity period expressed as NotBefore and NotOnOrAfter attributes that are UTC times.
I suspect that the format of one or both of these times is invalid.
Could you please enable SAML trace and send the generated log file as an email attachment to support@componentspace?
Also mention this post. Thanks.

I’m noticing those fields are not present in assertions that fail, but are in the other scenarios I mentioned. Should I disable TimePeriodCheck for IdPs? (email sent, btw)

You could but if they’re missing I suggest contacting the identity provider. These fields should be present.

It looks like in the InternalSAMLServiceProvider.CheckForAssertionReply method it uses DateTime.MaxValue for a default if NotOnOrAfter is not specified. You then pass the value to InMemoryIDCache.Add, which creates a DateTimeOffset from it. However when you read the .NET documentation for DateTimeOffset.MaxValue, you see that it converts the value you give it to UTC before checking against the boundaries, and so for us in a negative offset timezone, passing in DateTime.MaxValue will result in the exception specified.

Since the NotOnOrAfter attribute is defined as optional by the SAML 2.0 spec, it seems like the current implementation doesn’t properly handle that case. Seems like the best solution would be to use a nullable DateTime to represent the field, and then handle the null case, but other solutions could be to just use 1 year less than the max value, or have special handling of the max value.

Thanks for pointing this out.
The NotOnOrAfter is listed as optional but in practice it should be present and in most cases is.
We now handle a missing NotOnOrAfter in version and above of the product.