Serializing/Deserializing the bootstrap token

Posted on Updated on

I’ve been doing some work with WCF and WIF. Yes this new foundation thing called Windows Identity Foundation.

If you have seen the PDC09 demos than you probably thought that it’s so easy to add security to an application… well yes and no. It’s easy if you only want the standard functionality out of the box, but if you need to do something different… it’s quite complicated to get everything working. However, once you understand what’s going on then it is a lot smoother and you don’t need to worry anymore about how it works, it just works.

The last tornado I had to deal with was the serialization of security tokens. You may want to know why you would want to serialize tokens if WIF does it for you adding them to cookies or WCF heathers. Well that’s like asking why you would generate plain HTML to add custom CSS when you can use ASP.Net controls that spit out heaps of formatted html with colors. In this case, my reason is that I wanted to be able to open a windows application from the web browser and keeping the credentials I had in the browser.

To do that, you need to serialize the token into a file and your browser opens the application associated to that extension. It’s like when you download an excel sheet and instead of saving the file you open it directly in Excel or the same with a PDF. But in our scenario, we send the information that WIF had put in a cookie to the windows app so that we can call some WCF services. Easy. Serialize that monster and you deserialize it in the client to create the Channel. Go for it. If you are reading this is because it wasn’t that easy, isn’t it? Then, try to deserialize it now that you managed to serialize it. Ha!!.

The Idea is that you serialise your bootstrap token into an XML string


var bootstrapToken = ((IClaimsPrincipal)Thread.CurrentPrincipal).Identities[0].BootstrapToken; 

// Serialize
var req = new SamlSecurityTokenRequirement();
var handler = new Saml11SecurityTokenHandler(req);
var sb = new StringBuilder();
using (var writer = XmlWriter.Create(sb))
{
   handler.WriteToken(writer, bootstrapToken);                   
}

string serializedToken = sb.ToString();

Serializarion is quite simple, but to deserialize the token we need to have the public key of the signing certificate because the classes that do the deserialization want to validate that moster. This sample shows how to read the certificate from a file, but the constructors allow reading from a stream (or you may want to serialize the x509 certificate together with the token in the file that you send to your win app).

// Deserialize
string path = @"c:\temp\STSPublic.cer";
// It can be deserialised from a byte[]
var cert = new X509Certificate2(path);
var token = new X509SecurityToken(cert);
var tokens = new List<SecurityToken>() {token}; 

var resolver = SecurityTokenResolver.CreateDefaultSecurityTokenResolver(tokens.AsReadOnly(), false);
var conf = new SecurityTokenHandlerConfiguration();
conf.IssuerTokenResolver = resolver;
handler.Configuration = conf; 

using (var reader = XmlReader.Create(new StringReader(sb.ToString())))
{
   bootstrapToken2 = handler.ReadToken(reader);
}

Instead of reading the certificate from a file you can read the certificate from the list of installed certificated from the local machine you can also use the following code.


var token = new X509SecurityToken(CertificateUtil.GetCertificate(StoreName.My, StoreLocation.LocalMachine, "CN=STSCertificateName"));

Now it’s up to you to do whatever you want with the token in the windows app.

I hope this helps.

R.

9 thoughts on “Serializing/Deserializing the bootstrap token

    scott mcfadden said:
    April 1, 2010 at 02:28

    Do you have a sample app that demonstrates the calling of wcf backend services using the bootstrap token? In my case, I have a wif enabled web app / boostrap token that I want to use when calling my own backend wcf services (the web user was already issued a token from my own sts so why should I have to issue yet another token just to call my own wcf services)?

    I tried using the following to no avail:

    private static ChannelFactory m_acctSvcFactory = null;
    .
    .
    IAccountService svc = m_acctSvcFactory.CreateChannelWithIssuedToken(bsToken);

    When calling my service using the original boostrap token, I just get this error:

    System.ArgumentException was unhandled by user code
    Message=”Derived Key Token cannot derive key from the secret.”
    Source=”mscorlib”
    StackTrace:
    Server stack trace:
    at System.ServiceModel.Security.Tokens.DerivedKeySecurityToken.Initialize(String id, Int32 generation, Int32 offset, Int32 length, String label, Byte[] nonce, SecurityToken tokenToDerive, SecurityKeyIdentifierClause tokenToDeriveIdentifier, String derivationAlgorithm, Boolean initializeDerivedKey)
    at System.ServiceModel.Security.Tokens.DerivedKeySecurityToken.Initialize(String id, Int32 generation, Int32 offset, Int32 length, String label, Byte[] nonce, SecurityToken tokenToDerive, SecurityKeyIdentifierClause tokenToDeriveIdentifier, String derivationAlgorithm)
    at System.ServiceModel.Security.Tokens.DerivedKeySecurityToken..ctor(Int32 generation, Int32 offset, Int32 length, String label, Int32 minNonceLength, SecurityToken tokenToDerive, SecurityKeyIdentifierClause tokenToDeriveIdentifier, String derivationAlgorithm, String id)
    at System.ServiceModel.Security.SendSecurityHeader.SignWithSupportingTokens()
    at System.ServiceModel.Security.SendSecurityHeader.CompleteSecurityApplication()
    at System.ServiceModel.Security.SecurityAppliedMessage.OnWriteMessage(XmlDictionaryWriter writer)
    at System.ServiceModel.Channels.Message.WriteMessage(XmlDictionaryWriter writer)
    at System.ServiceModel.Channels.BufferedMessageWriter.WriteMessage(Message message, BufferManager bufferManager, Int32 initialOffset, Int32 maxSizeQuota)
    at System.ServiceModel.Channels.TextMessageEncoderFactory.TextMessageEncoder.WriteMessage(Message message, Int32 maxMessageSize, BufferManager bufferManager, Int32 messageOffset)
    at System.ServiceModel.Channels.HttpOutput.SerializeBufferedMessage(Message message)
    at System.ServiceModel.Channels.HttpOutput.Send(TimeSpan timeout)
    at System.ServiceModel.Channels.HttpChannelFactory.HttpRequestChannel.HttpChannelRequest.SendRequest(Message message, TimeSpan timeout)
    at System.ServiceModel.Channels.RequestChannel.Request(Message message, TimeSpan timeout)
    at System.ServiceModel.Channels.SecurityChannelFactory`1.SecurityRequestChannel.Request(Message message, TimeSpan timeout)
    at System.ServiceModel.Security.SecuritySessionSecurityTokenProvider.DoOperation(SecuritySessionOperation operation, EndpointAddress target, Uri via, SecurityToken currentToken, TimeSpan timeout)
    at System.ServiceModel.Security.SecuritySessionSecurityTokenProvider.GetTokenCore(TimeSpan timeout)
    at System.IdentityModel.Selectors.SecurityTokenProvider.GetToken(TimeSpan timeout)
    at System.ServiceModel.Security.SecuritySessionClientSettings`1.ClientSecuritySessionChannel.OnOpen(TimeSpan timeout)
    at System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout)
    at System.ServiceModel.Channels.ServiceChannel.OnOpen(TimeSpan timeout)
    at System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout)
    at System.ServiceModel.Channels.ServiceChannel.CallOpenOnce.System.ServiceModel.Channels.ServiceChannel.ICallOnce.Call(ServiceChannel channel, TimeSpan timeout)
    at System.ServiceModel.Channels.ServiceChannel.CallOnceManager.CallOnce(TimeSpan timeout, CallOnceManager cascade)
    at System.ServiceModel.Channels.ServiceChannel.EnsureOpened(TimeSpan timeout)
    at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
    at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs)
    at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
    at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)
    Exception rethrown at [0]:
    at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
    at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
    at MyContracts.IAccountService.GetAccounts()
    at RelyingParty.Controllers.ResourceController.GetAccounts() in C:\Dev\CSharp\WIF\WIFTest\Passive\RelyingParty\Controllers\ResourceController.cs:line 64
    at lambda_method(ExecutionScope , ControllerBase , Object[] )
    at System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, Object[] parameters)
    at System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters)
    at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters)
    at System.Web.Mvc.ControllerActionInvoker.c__DisplayClassa.b__7()
    at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func`1 continuation)
    InnerException:

    Good post on serialization.

    thanks

      rmencia responded:
      April 1, 2010 at 03:31

      I have created a base class for all the services that I’m using and I override the creation of the channel.
      The services that I’m using don’t trust the STS, they trust an ActAs STS that adds the claims required for the service to run.
      You can have a look at the code here. The Client proxies inherit from this base class, that means that I’m generating all my client proxies (using T4 templates).

      public abstract class MyClientBase : System.ServiceModel.ClientBase where TChannel : class
      {
      ChannelFactory factory;
      SecurityToken bootstrapToken;

      public MyClientBase(string endpointConfigurationName): base(endpointConfigurationName)
      {
      bootstrapToken = ((IClaimsPrincipal)Thread.CurrentPrincipal).Identities[0].BootstrapToken;\
      factory = new ChannelFactory(endpointConfigurationName);
      factory.Credentials.ServiceCertificate.SetDefaultCertificate(“CN=CertificateServices”, StoreLocation.LocalMachine, StoreName.My);
      factory.ConfigureChannelFactory();
      }

      protected override TChannel CreateChannel()
      {
      return factory.CreateChannelActingAs(bootstrapToken);
      }
      }

    online marketing said:
    April 26, 2010 at 22:04

    Hmm thanks for the share, do you have any other points of reference?

    scott_m said:
    July 21, 2010 at 04:01

    One quick cautionary note regarding the saving of the bootstrap token via the SAM / Session Cookies. Once your WIF session cookies go over 4000 bytes combined, they may as well not exist to Safari and Opera users. Safari and Opera will truncate at the 4000 byte mark. If your site doesn’t have Safari / Opera users, then no big deal. If you do need to support Safari/Opera users then you can do what I did which is just serialize the bootstrap token yourself and save it to sql for later de-serialization / use.

      Roberto Mencia responded:
      July 27, 2010 at 07:43

      The code that I posted is to serialize to a string and then you can do anything you want with the string.
      If what you need is to break into 4000kb chunks to put it in a cookie, WIF already has that functionality implemented and you can reuse it (I dont remember the class and method names right now).

      If you have problems with sizes I would reccoment to read this post in where Vittorio talks in depth about:

      http://blogs.msdn.com/b/vbertocci/archive/2010/05/26/your-fedauth-cookies-on-a-diet-issessionmode-true.aspx

    scott_m said:
    July 27, 2010 at 14:48

    Actually, I used your bootstrap token serialization code to help solve the session cookie size problem. When I had WIF automatically save the bootstrap token in the chunked session cookies, they were so large that the Opera/Safari users couldn’t retransmit the chunked cookies. I was forced to disable WIF saving of the bootstrap token via the session cookie. I still needed the bootstrap token though for wcf delegation so I used your code in global.asax / FederatedAuthenticationModule_SecurityTokenValidated event to serialize the boostrap token for later use. Your code worked well and got me out of a jam.

    thanks

    Ana Jardim said:
    July 7, 2011 at 14:17

    Hello,
    I have a webservice deserializing an adfs 2.0 token (web farm), just like described in this post, and sometimes I get “ID4037: The key needed to verify the signature could not be resolved” error. Do you have any idea why this happens intermittently? Any help would be appreciated. Great post, by the way.

    Error Description:

    Microsoft.IdentityModel.Protocols.XmlSignature.SignatureVerificationFailedException, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
    Message : ID4037: The key needed to verify the signature could not be resolved from the following security key identifier ‘SecurityKeyIdentifier
    (
    IsReadOnly = False,
    Count = 1,
    Clause[0] = X509RawDataKeyIdentifierClause(RawData = ….)
    )
    ‘. Ensure that the SecurityTokenResolver is populated with the required key.
    Source : Microsoft.IdentityModel
    Help link :
    Data : System.Collections.ListDictionaryInternal
    TargetSite : Void ResolveSigningCredentials()
    Stack Trace : at Microsoft.IdentityModel.Protocols.XmlSignature.EnvelopedSignatureReader.ResolveSigningCredentials()
    at Microsoft.IdentityModel.Protocols.XmlSignature.EnvelopedSignatureReader.OnEndOfRootElement()
    at Microsoft.IdentityModel.Protocols.XmlSignature.EnvelopedSignatureReader.Read()
    at System.Xml.XmlReader.ReadEndElement()
    at Microsoft.IdentityModel.Tokens.Saml11.
    Saml11SecurityTokenHandler.ReadAssertion(XmlReader reader)
    at Microsoft.IdentityModel.Tokens.Saml11.Saml11SecurityTokenHandler.ReadToken(XmlReader reader)

      Roberto Mencia responded:
      July 7, 2011 at 22:27

      I haven’t used adfs 2.0, but, whenever something like this happens (only sometimes) in a web farm, it’s usually due to a configuration problem in one of the servers.
      It works well until it hits the server that has the issue and then it fails.

      You can try to identify the offender machine and take it out of the farm and test again to see if that fixes the issue.
      Sticky sessions would make it more obvious as when you hit one machine that fails, it’ll fail consistently. Or even try only one machine at the time.

      Once identified, you can check for a wrong web.config cert, certificate not installed correctly, no access to certificate by IIS, Machine IDs not setup correcty…

      Just remember that there could be more than one machine with the problem, so you may need to continue testing even after finding one with an issue (if you have more that two machines in the farm).

      I don’t know if this is going to fix your issue, but I can’t think of anything else that happens only randomly.
      I hope it helps.

      Ghanshyam said:
      December 12, 2011 at 18:52

      The error is pretty indicative: ID4037: The key needed to verify the signature could not be resolved.

      It means that the webservice (in your case) is not able to verify the signature of the token. Most probable cause is certificate is not setup properly on one or more load balanced servers.

      If you are doing programatic setup, you can something like below:

      var handlers = SecurityTokenHandlerCollection.CreateDefaultSecurityTokenHandlerCollection();
      //….get certificate used for token signing
      if (rpCertificate != null)
      {
      List tokens = new List{new X509SecurityToken(rpCertificate)};
      SecurityTokenResolver resolver = SecurityTokenResolver.CreateDefaultSecurityTokenResolver(tokens.AsReadOnly(), true);
      handlers.Configuration.ServiceTokenResolver = resolver;
      }

      That should do it.

      Hope this helps someone with similar issue.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s