Jeremy Danyow
Jeremy Danyow

Reputation: 26406

Node.js / SAML: How to decrypt contents of RequestedSecurityToken

I'm using the passport-wsfed-saml2, the passport strategy for both WS-fed and the SAML2 protocol.

The WS-fed part of the logic does not seem to handle RequestedSecurityToken elements whose content is an <xenc:EncryptedData> element.

This makes the strategy incompatible with ADFS 2.0 relying parties that have an encryption certificate specified.

I'd like to monkey-patch the strategy's WsFederation.extractToken method with some decryption logic.

Below is a sample of the RequestSecurityTokenResponse xml that I would like to preprocess. How should I go about decrypting the token? Specifically, how do I use the information provided in the <KeyInfo> element in conjunction with the <xenc:CipherData> element to access the plaintext token data.

<?xml version="1.0" encoding="UTF-8"?>
<t:RequestSecurityTokenResponse xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust">
  <t:Lifetime>
    <wsu:Created xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2017-01-05T21:02:07.193Z</wsu:Created>
    <wsu:Expires xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2017-01-05T22:02:07.193Z</wsu:Expires>
  </t:Lifetime>
  <wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
    <wsa:EndpointReference xmlns:wsa="http://www.w3.org/2005/08/addressing">
      <wsa:Address>https://localhost</wsa:Address>
    </wsa:EndpointReference>
  </wsp:AppliesTo>
  <t:RequestedSecurityToken>
    <xenc:EncryptedData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" Type="http://www.w3.org/2001/04/xmlenc#Element">
      <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc" />
      <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
        <e:EncryptedKey xmlns:e="http://www.w3.org/2001/04/xmlenc#">
          <e:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p">
            <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
          </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=token-signing, OU=SomeOrg, O=EvilCorp, L=Williston, S=VT, C=US</X509IssuerName>
                  <X509SerialNumber>13135613350938963680</X509SerialNumber>
                </X509IssuerSerial>
              </X509Data>
            </o:SecurityTokenReference>
          </KeyInfo>
          <e:CipherData>
            <e:CipherValue>v6ueRi+G+s31b9RZxE1X8gfNWk6qC9EWimhmDQzLOl/9HQrToqcLRNVqpdocfAgAGp3RkyR9IcwED7PZkreNNzEYMN3pntqS1372Nk6EEYwJSVmWkXmsv4m+xeJvGPQrDIZOwlq22OBt0EAwXoq7LvkmF0s/uhB4TItD47iAsDOFThMpuPoYo0EDLgPWzHtrZqTsC33c10zKKgyynSJPAKaC/+a9mSc4uxq55njU4GLVP/p4FvubPF2U1j4I7ozRGGWsAD5iTGwIOIF7H/ftKoRGIoFen29Ud87mm00BrF0GSUzcxTX+isMfI+HWp8u9zaO1ZLge5+x12BJcVWOYwblTQ7IPWyCMmaUscGgQPZ82ROrMCbX2f6HcGHtl8rwzXbz/VfAZkkxXZAfq9NRjSIRcmVtwC4cjwPAAcwE6V8+lvFn/2dUgzSz9y5K4HpzWZc2jg91oyzhFV+5luC+NV2HPAtTshjWOWhAcVuZYdINfcU1rSHKirBtDPQjxEWcyxkGyrl6UfWq1sEDuaXBPVNWT9/jyjuf7Rzyxnype8SleTK197FnD+rq6NzG9H4MpTFwhgokiPx4/RONjog7I1qnNM5wFybJ5WvkSh+x1w1w7/CNGipJSXCy3swGuSgSF3LI1bUZSzL+JqhUmYq8EVxW31TPe7JbBwMdvnGl7e6Q=</e:CipherValue>
          </e:CipherData>
        </e:EncryptedKey>
      </KeyInfo>
      <xenc:CipherData>
        <xenc:CipherValue>.... whole bunch of base64 encoded data ....</xenc:CipherValue>
      </xenc:CipherData>
    </xenc:EncryptedData>
  </t:RequestedSecurityToken>
  <t:TokenType>urn:oasis:names:tc:SAML:1.0:assertion</t:TokenType>
  <t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType>
  <t:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</t:KeyType>
</t:RequestSecurityTokenResponse>

Upvotes: 1

Views: 2438

Answers (1)

Jeremy Danyow
Jeremy Danyow

Reputation: 26406

The <RequestedSecurityToken> element contains an <EncryptedData> element. The EncryptedData element is comprised of three parts:

1. EncryptionMethod, in my case it's aes256-cbc:
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc" />
2. KeyInfo, in my case this is the AES encryption key, but the key has been encrypted using the encryption certificate configured in ADFS. We'll need to decrypt the key using the RSA private key of the encryption certificate, giving us the AES encryption key/password that can be used to decrypt the SAML security token:
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
  <e:EncryptedKey xmlns:e="http://www.w3.org/2001/04/xmlenc#">
    <e:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p">
      <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
    </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=token-signing, OU=SomeOrg, O=EvilCorp, L=Williston, S=VT, C=US</X509IssuerName>
            <X509SerialNumber>13135613350938963680</X509SerialNumber>
          </X509IssuerSerial>
        </X509Data>
      </o:SecurityTokenReference>
    </KeyInfo>
    <e:CipherData>
      <e:CipherValue>v6ueRi+G+s31b9RZxE1X8gfNWk6qC9EWimhmDQzLOl/9HQrToqcLRNVqpdocfAgAGp3RkyR9IcwED7PZkreNNzEYMN3pntqS1372Nk6EEYwJSVmWkXmsv4m+xeJvGPQrDIZOwlq22OBt0EAwXoq7LvkmF0s/uhB4TItD47iAsDOFThMpuPoYo0EDLgPWzHtrZqTsC33c10zKKgyynSJPAKaC/+a9mSc4uxq55njU4GLVP/p4FvubPF2U1j4I7ozRGGWsAD5iTGwIOIF7H/ftKoRGIoFen29Ud87mm00BrF0GSUzcxTX+isMfI+HWp8u9zaO1ZLge5+x12BJcVWOYwblTQ7IPWyCMmaUscGgQPZ82ROrMCbX2f6HcGHtl8rwzXbz/VfAZkkxXZAfq9NRjSIRcmVtwC4cjwPAAcwE6V8+lvFn/2dUgzSz9y5K4HpzWZc2jg91oyzhFV+5luC+NV2HPAtTshjWOWhAcVuZYdINfcU1rSHKirBtDPQjxEWcyxkGyrl6UfWq1sEDuaXBPVNWT9/jyjuf7Rzyxnype8SleTK197FnD+rq6NzG9H4MpTFwhgokiPx4/RONjog7I1qnNM5wFybJ5WvkSh+x1w1w7/CNGipJSXCy3swGuSgSF3LI1bUZSzL+JqhUmYq8EVxW31TPe7JbBwMdvnGl7e6Q=</e:CipherValue>
    </e:CipherData>
  </e:EncryptedKey>
</KeyInfo>
3. CipherData: the SAML security token, encrypted using AES.
<xenc:CipherData>
  <xenc:CipherValue>.... whole bunch of base64 encoded data ....</xenc:CipherValue>
</xenc:CipherData>

Solution

Here's the code to monkey patch the passport-wsfed-saml2 library's WsFederation.extractToken method:

import WsFederation = require('passport-wsfed-saml2/lib/passport-wsfed-saml2/wsfederation');
import { createPrivateKey } from 'ursa-purejs';
import { createDecipheriv, randomBytes } from 'crypto';
import { DOMParser } from 'xmldom';
import { readFileSync } from 'fs';

const tokenSigningKey = createPrivateKey(readFileSync('./certs/token-signing.pem'));
const parser = new DOMParser();

WsFederation.prototype.standardExtractToken = WsFederation.prototype.extractToken;
WsFederation.prototype.extractToken = function (this: any, req: string) {
  const token: Element | null = this.standardExtractToken(req);
  // Is the SAML token encrypted?
  if (!token || token.nodeName !== 'xenc:EncryptedData') {
    // no. return it.
    return token;
  }

  // We need to decrypt the SAML token...

  // Grab the CipherValue elements. There will be two:
  //   0. The encryption key for the SAML token, encrypted by ADFS using the rsa-oaep-mgf1p 
  //      algo and the public key of the encryption certificate configured in the relying party.
  //   1. The SAML token, encrypted using the aes-256-cbc algo with the key from #0 ^^^
  const ciphers = token.getElementsByTagNameNS('http://www.w3.org/2001/04/xmlenc#', 'CipherValue');
  const aesPasswordCipher = <string>ciphers[0].textContent;
  const samlTokenCipher = <string>ciphers[1].textContent;

  // Decrypt the password for the SAML token.
  const aesPassword = tokenSigningKey.decrypt(aesPasswordCipher, 'base64');

  // Decrypt the SAML token.
  const decipher = createDecipheriv('aes-256-cbc', aesPassword, randomBytes(16));
  let saml = decipher.update(new Buffer(samlTokenCipher, 'base64'), 'binary', 'utf8');
  saml += decipher.final('utf8');

  // Parse the XML and return the token.
  return parser.parseFromString(saml);
};

Useful link: https://coolaj86.com/articles/asymmetric-public--private-key-encryption-in-node-js/

Upvotes: 3

Related Questions