In my previous article, "Generate SAML Tokens Using Windows Identity Foundation," I provided an overview of the SAML token format and explained how to create a SAML bearer token and a SAML holder-of-key token using Windows Identity Foundation (WIF). The impetus for the article was to give you a foundation in SAML before showing you how to handle a few scenarios I have encountered involving interoperability and showing you how to produce SAML tokens from WCF and WIF-enabled clients. These are the topics of this second article. I will discuss a few overrides that might be important to you when interoperating with other platforms using SAML tokens and show you how to look for these settings in the XML. The sample code will also include a custom ClientCredentials type that produces SAML tokens without federating with an STS. This may also come in handy for building a test harness for SAML scenarios or for producing tokens in response to WCF service call.

Signing and Encrypting SAML Tokens

Last time I very briefly described how to create a SAML bearer token typical of passive federation scenarios through the web browser, and how to create a SAML holder-of-key token typical of active federation scenarios between clients and services. In the process, I discussed the contents of a SAML token—focusing on the most commonly used elements to provide some background. I discussed digital signatures and encryption only from a very high level, providing diagrams that illustrated how related private and public keys might be applied to passive and active scenarios.

Digital signatures and encryption are critical to integrity and privacy, and there are many possibilities. For example, you can use symmetric or asymmetric keys to produce signatures or to encrypt content. With symmetric keys, both sides of the communication share a key (a shared secret), whereas with asymmetric keys, each side of the exchange owns a private key and shares the public key—well, publicly. For example, integrity protection is supplied by signing content with the private key—the signature validated with the matching public key at the receiver. Likewise, content is encrypted using the public key of the receiver, and only the receiver (who owns the private key) can decrypt that content. SAML tokens are almost always signed with a private key. If the token is protected by the wire, encryption is usually provided through a secure session produced with symmetric keys—unrelated to your efforts to produce a token. If the issuer is also protecting the token before releasing it on the wire, it will typically use asymmetric encryption using the public key of the relying party. For that reason, my focus in this article is asymmetric means for signing and encryption.

Figure 1 illustrates the flow of communication between a client application that acts as a SAML token issuer, the relying party application, and the resulting SAML bearer token showing its signature and encryption block. Upcoming sections will focus on how to produce this result, digging a layer deeper into the some of the features of signing and encryption critical to interoperability.

Figure 1: Sending signed and encrypted SAML bearer tokens from a client application

You may also recall from my last article the additional proof key that may be embedded if a SAML holder-of-key token is generated. Likewise, some of these features are relevant to how the proof key is included in the SAML token.

Algorithms

The choice of signing and encryption algorithms is critical to interoperability. In this section I'll discuss how to detect the required algorithm by looking at the serialized SAML token. In a later section I'll address the code required to select the correct algorithm.

Consider the signed SAML bearer token in Figure 2.

<Assertion ID="_88ec89e0-2ead-4ddf-b820-7f2eb12e23dd" IssueInstant="2010-07-05T13:14:25.923Z" Version="2.0" xmlns="urn:oasis:names:tc:SAML:2.0:assertion">
  <Issuer>CN=busta-rpsts.com</Issuer>
    <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
      <ds:SignedInfo>
      <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
BEGIN CALLOUT A
      <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" />
END CALLOUT A
        <ds:Reference URI="#_88ec89e0-2ead-4ddf-b820-7f2eb12e23dd">
          <ds:Transforms>
          <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
          <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
        </ds:Transforms>
BEGIN CALLOUT B
        <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
END CALLOUT B
        <ds:DigestValue>WtxMq7Zpp5iRoMhJRCdcKwaYbWqyKaddVFj+IAs4lMc=</ds:DigestValue>
      </ds:Reference>
    </ds:SignedInfo>
    <ds:SignatureValue>U1+PokUFBw9z3YqAfN3CBOw0LSqgHnq27ekrNjC18ax5SyP3UFctXda8D1Y+dD9tugZuedJ8Tlx4iaOLttldFZfCPlT9VaYUvKrM9Aq6prqKhrGRrS5SJHp2+2kbDSYUOxFQfFz9cWCr6agUGDCemfkHFywi7mAgeBzvMvM4iPSjGskoKpPIl7Vf4a7Nov9wKXITJ31Dp+mOypMhczFqUWurHY+bRq1fhWUKKcfogTYipHHB8TUlc6m8UIxdCFNvr3Q19okgYuIdCR6izbRBIR75W1LNkJhTvJ8y2Am6ZruABlx3F5zSWTNfAObKMmdnvWAnWYBTmVw9y8a1oqlO+A==</ds:SignatureValue>
      <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
        <o:SecurityTokenReference xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
        <o:KeyIdentifier ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509SubjectKeyIdentifier">31PLIxTikChLcYPk3XyFB7D+dXA=</o:KeyIdentifier>
      </o:SecurityTokenReference>
    </KeyInfo>
  </ds:Signature>
  <Subject>
    <SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer" />
  </Subject>
  <Conditions NotBefore="2010-07-05T13:14:25.920Z" NotOnOrAfter="2010-07-06T12:14:25.920Z">
    <AudienceRestriction>
      <Audience>http://localhost/RelyingPartyApplication</Audience>
    </AudienceRestriction>
  </Conditions>
  <AttributeStatement>
    <Attribute Name="User">
      <AttributeValue>Michele</AttributeValue>
    </Attribute>
    <Attribute Name="Permission">
      <AttributeValue>Read</AttributeValue>
      <AttributeValue>Write</AttributeValue>
      <AttributeValue>Update</AttributeValue>
      <AttributeValue>Delete</AttributeValue>
    </Attribute>
  </AttributeStatement>
</Assertion>

The signature is enveloped by the <Assertion> element and carries the following key sections:

  • <SignedInfo> is the section from which a signature is produced.
  • <SignatureValue> is the actual signature value produced from the <SignedInfo> element.
  • <KeyInfo> is an optional element that contains or references the key that can be used to verify the signature.

The <SignedInfo> element may contain one or more <Reference> elements, each of which contains a digest (hash value) for a particular element in the document to be signed. In the case of a SAML token, there is a single <Reference> element for the <Assertion> section since the token is the thing being signed. The assertion reference is described by the following children:

  • <DigestValue> contains the base64 encoded digest of the <Assertion> element.
  • <Transforms> indicate any transformations to be performed on the <Assertion> element prior to producing the digest. In this case, there are two transforms. One indicates that the <Signature> is enveloped ('http://www.w3.org/2000/09/xmldsig#enveloped-signature'), which means that the hash is produced before including the <Signature> element within the <Assertion> element. Another indicates the canonicalization algorithm ('http://www.w3.org/2001/10/xml-exc-c14n#') to be executed on the assertion element prior to producing the digest.
  • <DigestMethod> indicates the algorithm ('http://www.w3.org/2001/04/xmlenc#sha256') used to produce the digest for the assertion.

The <SignedInfo> element also indicates the canonicalization method ('http://www.w3.org/2001/10/xml-exc-c14n#') and the signature method ('http://www.w3.org/2001/04/xmldsig-more#rsa-sha256') used to produce the signature value from the <SignedInfo> element. The <SignatureValue> element contains the resulting base64 encoded signature.

In summary, a digest of the SAML assertion is produced for the <SignedInfo> reference element, and the digital signature value is produced from the entire <SignedInfo> element. For interoperability purposes the canonicalization algorithm is typically not an issue; most agree on this. The digest method algorithm and signature method algorithm are, on the other hand, important settings that may not use the same defaults between platforms. When WIF components are used to process incoming SAML tokens, WIF knows how to process the token according to the algorithms specified in the document. When producing a SAML token to send to another platform, however, you will need to tell WIF to use the algorithms expected by the destination platform. Callouts A and B in Figure 2 show the two algorithms that are typically of interest in this process.

SAML tokens can be encrypted by the issuer for the relying party, even if transferred over a secure channel such as HTTPS. Consider Figure 3, the encrypted version of the bearer token shown in Figure 2.

<EncryptedAssertion xmlns="urn:oasis:names:tc:SAML:2.0:assertion">
  <xenc:EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Element" xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
BEGIN CALLOUT A
    <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc" />
END CALLOUT A
    <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
      <e:EncryptedKey xmlns:e="http://www.w3.org/2001/04/xmlenc#">
BEGIN CALLOUT B
        <e:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p">
          <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
END CALLOUT B
        </e:EncryptionMethod>
        <KeyInfo>
          <o:SecurityTokenReference xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
            <X509Data>
              <X509IssuerSerial>
                <X509IssuerName>CN=busta-rp.com</X509IssuerName>
                <X509SerialNumber>146947968936138442636380379933072415526</X509SerialNumber>
              </X509IssuerSerial>
            </X509Data>
          </o:SecurityTokenReference>
        </KeyInfo>
        <e:CipherData>
          <e:CipherValue>K1TGUHdTjPT39wIlD6ikCjZGEQ7PAZ8klXZdAspU2uBPuuoCSVtV8vUi65elwJRFCN+3G4TMgHximpyEC6alqUp0zXsXukjNOQCuuQGALsJBWndAyv95xK2zhDiiB4Yb6QLVFYliKwF1UwDf0jezEqhA6THZcITy8aZvOGX09XI=</e:CipherValue>
        </e:CipherData>
      </e:EncryptedKey>
    </KeyInfo>
    <xenc:CipherData>
      <xenc:CipherValue>fV3Ly…8mQ=</xenc:CipherValue>
    </xenc:CipherData>
  </xenc:EncryptedData>
</EncryptedAssertion>

An encrypted SAML token is contained within an <EncryptedAssertion> element, which in turn contains an <EncryptedData> element with the following children:

  • <EncryptionMethod> indicates which algorithm ('http://www.w3.org/2001/04/xmlenc#aes256-cbc') is used to produce the encrypted value.
  • <KeyInfo> is an optional element indicating the decryption key to the relying party. This element contains an <EncryptedKey> element, which states the encryption method algorithm ('http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p') and a key wrap or digest algorithm ('http://www.w3.org/2000/09/xmldsig#sha1') used to protect the encrypted key.
  • <CipherData> contains the actual encrypted value of the signed <Assertion> element.

For interoperability purposes, all three algorithms are important settings that may require tweaking for platform compatibility. Once again, WIF components can process incoming encrypted tokens without a problem since the document supplies all the necessary information to the runtime for processing, but when producing an encrypted token you may need to supply alternate algorithms. Callouts A and B in Figure 3 show the three algorithms of interest.

KeyInfo Format

Another relevant aspect of SAML token serialization is the manner in which <KeyInfo> is written. By default, WIF typically writes a <SecurityTokenReference> with an X509 key identifier, as shown here from Figure 2:

<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
  <o:SecurityTokenReference xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
    <o:KeyIdentifier ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509SubjectKeyIdentifier">31PLIxTikChLcYPk3XyFB7D+dXA=</o:KeyIdentifier>
  </o:SecurityTokenReference>
</KeyInfo>

Other platforms, however, almost always write an RSA key value as shown here:

<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
 <KeyValue>
   <RSAKeyValue>
               <Modulus>wshV… Zw==</Modulus>
               <Exponent>AQAB</Exponent>
        </RSAKeyValue>
      </KeyValue>
    </KeyInfo>

There are a few tricks to producing this outcome with WIF, so on to the coding details we go.

Controlling Algorithms and KeyInfo Format

Last time I showed the code to build a SAML token without consideration for these few interoperability requirements. This time, I'll show a version of the code that produces the result we're looking for. Consider the serialized SAML token in Figure 4. Callouts A, B, and C in Figure 4 show an alternative signature method algorithm, an alternative digest method algorithm, and a <KeyInfo> element serialized as an RSA key value.

<Assertion ID="_85fe3438-5d28-4185-a08f-4711791b9e73" IssueInstant="2010-07-12T06:46:47.281Z" Version="2.0" xmlns="urn:oasis:names:tc:SAML:2.0:assertion">
  <Issuer>CN=busta-rpsts.com</Issuer>
  <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
    <ds:SignedInfo>
      <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
BEGIN CALLOUT A
      <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
END CALLOUT A
      <ds:Reference URI="#_85fe3438-5d28-4185-a08f-4711791b9e73">
        <ds:Transforms>
          <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
          <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
        </ds:Transforms>
BEGIN CALLOUT B
        <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
END CALLOUT B
        <ds:DigestValue>AhBKWyYLcdNKtAfcRxD2LYmXuU8=</ds:DigestValue>
      </ds:Reference>
    </ds:SignedInfo>
    <ds:SignatureValue>oIAiGxAzz2 … 7HEiKQA91Sbjx3g==</ds:SignatureValue>
    <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
BEGIN CALLOUT C
      <KeyValue>
        <RSAKeyValue>
          <Modulus>wshV … Zw==</Modulus>
          <Exponent>AQAB</Exponent>
        </RSAKeyValue>
      </KeyValue>
END CALLOUT C
    </KeyInfo>
  </ds:Signature>
  <Subject>
    <SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer" />
  </Subject>
  <Conditions NotBefore="2010-07-12T06:46:47.281Z" NotOnOrAfter="2010-07-13T05:46:47.281Z">
    <AudienceRestriction>
      <Audience>http://localhost/RelyingPartyApplication</Audience>
    </AudienceRestriction>
  </Conditions>
  <AttributeStatement>
    <Attribute Name="User">
      <AttributeValue>Michele</AttributeValue>
    </Attribute>
    <Attribute Name="Permission">
      <AttributeValue>Read</AttributeValue>
      <AttributeValue>Write</AttributeValue>
      <AttributeValue>Update</AttributeValue>
      <AttributeValue>Delete</AttributeValue>
    </Attribute>
  </AttributeStatement>
</Assertion>

Figure 5 shows the code required to produce the result in Figure 4. In fact the code is pretty simple. The hard part is to be able to understand what algorithms you are looking for and to understand how to produce an RSA key value to match the expected format. From my earlier discussion and the highlighted areas in Figure 2, I hope I've given you some idea how to note the expected algorithms and key info format.

SecurityTokenDescriptor descriptor = new SecurityTokenDescriptor();

BEGIN CALLOUT A
string digestAlgorithm = "http://www.w3.org/2000/09/xmldsig#sha1";
string signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
END CALLOUT A

X509Certificate2 signingCert = CertificateUtil.GetCertificate(StoreName.My, StoreLocation.LocalMachine, "CN=busta-rpsts.com");
BEGIN CALLOUT B
RSACryptoServiceProvider rsa = signingCert.PrivateKey as RSACryptoServiceProvider;
RsaSecurityKey rsaKey = new RsaSecurityKey(rsa);
RsaKeyIdentifierClause rsaClause = new RsaKeyIdentifierClause(rsa);
SecurityKeyIdentifier signingSki = new SecurityKeyIdentifier(new SecurityKeyIdentifierClause[] { rsaClause });
SigningCredentials signingCredentials = new SigningCredentials(rsaKey, signatureAlgorithm, digestAlgorithm, signingSki);
END CALLOUT B

descriptor.TokenType = "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0";
descriptor.TokenIssuerName = "CN=app.nhin-hv.com, OU=Domain Control Validated, O=app.nhin-hv.com";
descriptor.SigningCredentials = signingCredentials;
descriptor.Subject = new ClaimsIdentity(claims);
descriptor.AppliesToAddress = "http://localhost/RelyingPartyApplication";

DateTime issueInstant = DateTime.UtcNow;
descriptor.Lifetime = new Lifetime(issueInstant, issueInstant + new TimeSpan(23, 0, 0));

Saml2SecurityTokenHandler tokenHandler = new Saml2SecurityTokenHandler();
Saml2SecurityToken token = tokenHandler.CreateToken(descriptor) as Saml2SecurityToken;

When producing the SecurityTokenDescriptor that will supply information for SAML token generation, you must include a SigningCredentials instance representing the key used to sign the token. Using typical WIF code, you would produce SigningCredentials as follows:

X509Certificate2 signingCert = CertificateUtil.GetCertificate(StoreName.My, StoreLocation.LocalMachine, "CN=busta-rpsts.com");
SecurityKeyIdentifier ski = new SecurityKeyIdentifier(new SecurityKeyIdentifierClause[] { new X509SecurityToken(signingCert).CreateKeyIdentifierClause<X509SubjectKeyIdentifierClause>() });
X509SigningCredentials signingCreds = new X509SigningCredentials(signingCert, ski);

First, load up the X509Certificate with the private key. Next, create a SecurityKeyIdentifier from the certificate. Finally, initialize an X509SigningCredentials instance with the private key and security key identifier. The result is serialized as shown in Figure 2, with algorithms provided by the runtime and key info serialized as a security token reference.

If you are looking to adjust algorithms, you could use one of the overloaded constructors and set those algorithms as follows:

X509SigningCredentials signingCreds = new X509SigningCredentials(signingCert, signingSki, "http://www.w3.org/2000/09/xmldsig#rsa-sha1", "http://www.w3.org/2000/09/xmldsig#sha1");

This, however, will not help with the RSA key value serialization. One way to achieve this is to override how the Saml2SecurityToken is written, manually. Another way is to supply an RSA key and a signing key with an RSA key identifier clause. As Figure 4 illustrates, you can produce a RSACryptoServiceProvider instance from the PrivateKey of your X509Certificate2 instance. From this you can create the RsaSecurityKey to be used when constructing SigningCredentials. To produce the RSA security key identifier, you must first produce an RsaKeyIdentifierClause instead of the X509SubjectKeyIdentifierClause, then construct the SecurityKeyIdentifier instance from this. Instead of creating an X509SigningCredentials instance for the SigningCredentials property, a SigningCredentials instance is created from the key, key identifier, and desired algorithms as shown in callouts A and B in Figure 5. If you are not entirely comfortable with cryptography (and few are), it can be a bit of a struggle to come up with this transformation to produce the RSA key value—so I hope this proves a helpful resource to a few people!

As for supplying custom algorithms to encrypted tokens, the following code shows how to produce the encrypting credentials for the token shown in Figure 3, explicitly setting the algorithms using an overload of the EncryptedKeyEncryptingCredentials type.

string keyWrapAlgorithm = "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p";
string encryptionAlgorithm = "http://www.w3.org/2001/04/xmlenc#aes256-cbc";
X509Certificate2 encryptingCert = CertificateUtil.GetCertificate(StoreName.TrustedPeople, StoreLocation.LocalMachine, "CN=busta-rp.com");
EncryptingCredentials encryptingCredentials = new EncryptedKeyEncryptingCredentials(encryptingCert, keyWrapAlgorithm, 256, encryptionAlgorithm);

Wrapping Up

Although this was certainly not an exhaustive discussion of potential interoperability concerns or possible cryptography settings for both signing and encryption, I hope that this discussion will help you with some of the common settings I have found necessary to modify for various implementations in my travels with customers. Take a look at the code for more goodies within such as the ClientCredentials type that wraps up generating the token. Enjoy!