January 15th, 2015

WS-Trust Proof-of-Possession (PoP) tokens with client and server entropy (with partial keys) – Part 1

As a security feature WS-Trust supports Proof-of-Possession Tokens. In this post I want to show you how you can consume a service that requires PoP token security with client and server entropy (going deep in a min). This method has been tested with Microsoft Dynamics CRM and ADFS.

This is a very long topic, so I will split it into two posts. This, the first one, will show how to request the token and calculate the PoP key (shared key), in the second one I will show how to sign the request using this key.

But, lets start with a little of context…

Proof-of-Possession tokens (PoP) vs Bearer tokens

To start understanding a PoP token we can compare it with its more common alternative: the Bearer token.

Bearer tokens are like cash, the mere fact of having one allows you to use it without any other step/verification. In the opposite way PoP tokens are like credit card, the mere fact of possessing it is not enough, to use it you must prove that you are the real owner of it by using, for example, a valid id.

When we use Bearer tokens the security of our applications rely on some transport encryption mechanism like TLS/SSL, that is ok in some cases but there are some scenarios that require stronger security mechanisms. PoP tokens add a security layer, because if someone captures one of your tokens he will still need to prove that he is the real owner of the token in order to use it on your application.

What is the problem with TLS/SSL?

Well, as I said, rely on TLS/SSL is ok in some cases but in some others you want stronger security features. The first problem of relying only on TLS/SSL is that you are putting all the eggs in one basket and in any 101 security class you will learn that the main security principle is layering your security.

On the other hand, TLS/SSL security depends on the client application validating the certificate, other way you are completely exposed to Man-in-the-middle attacks. That means that you are moving the entire security of your service to the client and you know what? developers and TLS/SSL don’t like each other.

If you google for “How to handle SSL certificate error” the first thing that you will find is a lot of ways of bypassing this validation in any platform. For example, Node.js just changed the default from not validating to validating the certificate in version 0.10.x.

Now, before of going on how WS-Trust implements this feature, let me share with you the good new: there is some work on progress to take PoP tokens to OAuth. You can read more about that here: OAuth Proof-of-Possession Drafts.

WS-Trust and PoP Tokens

To start look at how we can use PoP tokens on WS-Trust lets look at the spec for a PoP definition:

A proof-of-possession (POP) token is a security token that contains secret data that can be used to demonstrate authorized use of an associated security token, thereby the final service (relying party) can validate that the caller is the “real” owner of the token that he is presenting. Typically, although not exclusively, the POP token consist of a key known by the relying party.

Now, you need to know that WS-Trust specifies two ways of use PoP token keys: specific and partial.

When you use specific keys, the client can specify the key when he requests the token or the security token service (ADFS for example) can retrieve the key to be used. In both cases you just need to use the specific key to sign the requests you perform to the relying party (final service).

The partial keys scenario is a bit different because you will use two keys, one for the server, and one for the client, and the final key, the key with which you will sign the requests you perform to the relying party must be calculated combining both keys.

In this post I will explain the partial scenario that is the more complex, but at the same time, the more secure scenario.

Let’s start!

The first thing that you need to know if which type of keys does the service you want to call needs. You will find that described with WS-Policy in the WSDL file of the service:

<sp:Trust13 xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
   <wsp:Policy>
     <sp:MustSupportIssuedTokens/>
     <sp:RequireClientEntropy/>
     <sp:RequireServerEntropy/>
   </wsp:Policy>
</sp:Trust13>

In this example the service requires client and server entropy, that means partial keys.

Request the token

Now we need to request a token, I’ve explained how to request a token in this post from any platform and language, but it will be basically a SOAP call to the WS-Trust endpoint of our STS (for example ADFS). In the body of our SOAP envelope we will need to specify the Request Security Token (A predefined WS-Trust format to request tokens), the key we want to use:

<t:RequestSecurityToken xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust">
   <t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType>
   <wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
      <EndpointReference xmlns="http://www.w3.org/2005/08/addressing">
         <Address>https://the-service-i-want-to-call.com/</Address>
      </EndpointReference>
   </wsp:AppliesTo>
   <t:Entropy>
      <t:BinarySecret Type="http://schemas.xmlsoap.org/ws/2005/02/trust/Nonce">
         yEEN5hsRamzDqFKmNqvp+3d2yzGOU+czcEeEXVJJ4fA=
      </t:BinarySecret>
   </t:Entropy>
   <t:KeySize>256</t:KeySize>
   <t:KeyType>http://schemas.xmlsoap.org/ws/2005/02/trust/SymmetricKey</t:KeyType>
   <t:ComputedKeyAlgorithm>http://schemas.xmlsoap.org/ws/2005/02/trust/CK/PSHA1</t:ComputedKeyAlgorithm>
</t:RequestSecurityToken>

This key yEEN5hsRamzDqFKmNqvp+3d2yzGOU+czcEeEXVJJ4fA=, specified using the Entropy tag will be our “Client Entropy”.

As you can see, we are also setting the KeySize to 256, they KeytType to symmetric key, and the ComputedKeyAlgorithm to PSHA1, this is the algorithm that we are going to use to combine both keys. While it can be extended, the default algorithm is PSHA1 specified in the TLS Spec.

You have to have in mind that all of this parameters are configurable and depend on the Relying Party configuration, so you should read all of them from WSDL file of the service you want to call, inside of the RequestSecurityTokenTemplate element.

Now, after you send the request for the token, the server will reply something like this:

<t:RequestSecurityTokenResponse xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust">
   <t:Entropy>
      <t:BinarySecret>TUv/+WgHQYY2nR3kqB/5/Zac117tkBf2CkxWvs4G2pA=</t:BinarySecret>
   </t:Entropy>
   <t:RequestedSecurityToken>
      <xenc:EncryptedData>...</xenc:EncryptedData>
   </t:RequestedSecurityToken>
</t:RequestSecurityTokenResponse>

I removed a lot of code to improve presentation, but inside of the response you will find two important elements, the first one is the Entropy as you can imagine, this is the “Server entropy”, and the second one is the RequestedSecurityToken containing the SAML Assertion. Now, in this scenario, the SAML Assertion must be encrypted to ensure that only the Relying Party (the service you want to call) can decrypt it because it will contain the key with which you need to sign the requests to prove that you are the real owner of this assertion.

Remember that the whole idea of this scenario is that if somehow your token is leaked the possession of it shouldn’t be enough to authenticate on the relying party (the service), so it doesn’t make sense to send the key with which you will prove that you are the real owner of the token inside of the assertion if it is not encrypted.

Calculate the shared secret (PoP key)

Now we have the three elements that we need to call the service: the SAML Assertion (encrypted) and the Client and Server Entropies.

The first thing that we need to do is calculate the final key with which we are going to sign the requests. To do that we are going to use the PSHA1 algorithm to combine both keys (client and server). As I said before, this algorithm is specified in the TLS Spec.

I’ve published a Node.js module that implements the PSHA1 algorithm, you can find it here: https://www.npmjs.org/package/psha1. But for you to have an idea this is the Node.js implementation:

var crypto = require('crypto');

module.exports = function (secret, seed, keySize) {
   keySize = keySize || 256;

   var clientBytes = new Buffer(secret, 'base64');
   var serverBytes = new Buffer(seed, 'base64');

   var sizeBytes = keySize / 8;
   var sha1DigestSizeBytes = 160 / 8; // 160 is the length of sha1 digest

   var buffer1 = serverBytes;
   var buffer2 = new Buffer(sha1DigestSizeBytes + serverBytes.length);
   var pshaBuffer = new Buffer(sizeBytes)

   var i = 0;

   var temp = null;

   while (i < sizeBytes) {
      buffer1 = new Buffer(crypto.createHmac('sha1', clientBytes)
                .update(buffer1).digest(), 'binary');

      buffer1.copy(buffer2);

      serverBytes.copy(buffer2, sha1DigestSizeBytes);

      temp = new Buffer(crypto.createHmac('sha1', clientBytes)
             .update(buffer2).digest(), 'binary');

      for (var x = 0; x < temp.length; x++) {
         if (i < sizeBytes) {
           pshaBuffer[i] = temp[x];
           i++;
         } else {
            break;
         }
      };
   }

   return pshaBuffer.toString('base64');
}

In this example, secret is the client key and seed is the server key. After applying this algorithm you will end up with the shared secret key (the final key that we are going to use to sign the request):

var sharedSecret = psha1(clientEntropy, serverEntropy, 256);

In the next post I will show how to sign the request to the service using this key… see you soon.

  • hsk3mis

    Hi, Great article

    I’ve got question about signed Assertion.

    As I imagine, when STS receive RequestSecurityToken, it gets it’s serverEntropy and calculates sharedSecret the same way as client (using psha1 algorithm). STS then puts this sharedSecret inside Token and encrypts Token using some key that is known only to the service you wanted to call in the first place.

    Is it a good reasoning?

    Waiting for the part 2.

    • Thanks! That’s correct. The token or assertion is signed with STS Private Key and encrypted with the service you want to call (Relying Party) Public Key, and has the shared secret inside.

      In that way the only the party in possession of the Relying Party Private Key (the service) can decyipt the token and see the shared secret.

      Part 2 will be out soon! Thanks again for the comment!

  • Jean-François Cyr

    Great article, can’t wait for part 2!

    There isn’t much documentation around, how does the requestor prove its identity to the Relying Party? Is it an encrypted timestamp? A challenge handshake? Magic unicorn?

    Thanks

    • Hey, thanks for the comments

      The requestor proves his identity in the relying party by sending the assertion and signing the request with the shared secret… I didn’t have time yet to finish part 2 but will be out soon explaining this process!

  • Pascal Willemsen

    Hi Leandro,

    Where can we find part 2?

    Kind regards