Saml Adapter

Follow

Saml Adapter

Functionality

The SAML adapter allows TrustBuilder to generate and validate SAML-tokens:

  • version 1.1
  • version 2.0

Prerequisites

A keystore with privatekey(alias/password) are required for

  • Signing a saml generated token
  • Decrypting a saml validated token

A truststore is required for validation. If a certificateAlias is given it will be validated against that certificate otherwise the whole truststore is taken into account.

Configuration

AdapterUniqueID

Unique name assigned to this adapter; the name is used to reference the adapter in the workflow. The ID has following requirements:

  • START with a letter or _ (underscore)
  • FOLLOWED by a combination of following characters: Letter, Number, '.' (dot), '-' (dash), '_' (underscore)
  • PrivateKeyAlias The PrivateKeyAlias identifies the key for signing and decrypting attributes. The matching certificate can either be send manually to the other end or be included in the token/template.
  • PrivateKeyPassword Password of the private key
  • CertificateAlias The CertificateAlias identifies the certificate used for validating signatures. If not specified a TrustManager will be used over the truststore defined in the config. If this truststore is not available the keystore will be taken instead. The role of this trustmanager is to attempt to take the correct certificate for the job at hand.
  • AddSignatureKeyInfo Whether to include a key info element containing the signer certificate in the generated token's signature.
  • UseSignatureKeyInfo Whether to validate the certifcate found in the token against CertificateAlias or truststore

Workflow Settings

A request for the adapter is prepared by specifying the following properties/scripts in the adapter activity:

  • Input Property: the variable containing the instructions the adapter have to execute
  • Output Property: the variable the adapter will store the response in after execution
  • Before Adapter Script: script that will be executed before calling the adapter
  • After Adapter Script: script that will be executed after the adapter fulfilled its task

Request - API

SamlGenerateRequest

The SamlGenerateRequest allows clients to generate a SAML token. This will be signed by the adapter.

samlGenerateRequest(template) 

with parameters:

template: Non-null, non-empty string of a base 64 encoded saml template. You can specify the signature method inside the template as the attribute with name "SignatureMethod" inside the signature tag. 

other properties are:

binding: REDIRECT, allow the use of HTTP Redirect Binding
timezone: Timezone to use for the timestamps used within the saml request
keyName: KeyName instead of key/x509 certificate

Example token (SAML V2.0):

<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Version="2.0">
   <samlp:Status>
     <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
   </samlp:Status>
   <saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Version="2.0">
     <saml:Issuer>...</saml:Issuer>
     <saml:Conditions Validity="...">
      <saml:AudienceRestriction>
        <saml:Audience>...</saml:Audience>
      </saml:AudienceRestriction>
     </saml:Conditions>
    <saml:AttributeStatement>
        <saml:Attribute Name="..." NameFormat="...">
            <saml:AttributeValue>...</saml:AttributeValue>
        </saml:Attribute>
    </saml:AttributeStatement>
   </saml:Assertion>
</samlp:Response>

SamlMetadataRequest

The SamlMetadataRequest allows clients to sign and validate SAML metadata tokens.

samlMetadataRequest(template) 

with parameters:

template: Non-null, non-empty string of a base 64 encoded saml template. 

Example metadata (SAML V2.0):

<?xml version="1.0" encoding="UTF-8"?>
<md:EntityDescriptor
    ID="tst201307260955"
    xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
    xmlns:attr="urn:oasis:names:tc:SAML:metadata:attribute"
    xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
    entityID="urn:be123:entities:9000" >
   <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Reference="#tst201307260955"
        SignatureMethod="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
        DigestMethod="http://www.w3.org/2001/04/xmlenc#sha256"/>
    <md:SPSSODescriptor
       AuthnRequestsSigned="true"
       WantAssertionsSigned ="true"
       protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">

      <md:KeyDescriptor use="signing">
         <ds:KeyInfo>
            <ds:KeyName>test-key</ds:KeyName>
            <ds:X509Data>
               <ds:X509Certificate>MIIGKTC.....hb</ds:X509Certificate>
            </ds:X509Data>
         </ds:KeyInfo>
      </md:KeyDescriptor>
      <md:AssertionConsumerService
           Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
           Location="https://www.test.be/tb/html/wsaml2acs"
           index="1"
           isDefault="true"/>
   </md:SPSSODescriptor>
   <md:Organization>
      <md:OrganizationName xml:lang="nl-nl">Test</md:OrganizationName>
      <md:OrganizationDisplayName xml:lang="nl-nl">Test</md:OrganizationDisplayName>
      <md:OrganizationURL xml:lang="nl-nl">http://www.test.be</md:OrganizationURL>
   </md:Organization>
   <md:ContactPerson contactType="administrative">
      <md:Company>Test</md:Company>
      <md:GivenName>Test</md:GivenName>
      <md:SurName>Test</md:SurName>
      <md:EmailAddress>test@test.be</md:EmailAddress>
      <md:TelephoneNumber>1235426505</md:TelephoneNumber>
   </md:ContactPerson>

   <md:ContactPerson contactType="technical">
      <md:Company>Test</md:Company>
      <md:GivenName>Test</md:GivenName>
      <md:SurName>Test</md:SurName>
      <md:EmailAddress>test@test.be</md:EmailAddress>
      <md:TelephoneNumber>1235426505</md:TelephoneNumber>
   </md:ContactPerson>
</md:EntityDescriptor>

SamlValidateRequest

The SamlValidateRequest allows clients to validate a SAML 1.1 or 2.0 token and retrieve the necessary info.

samlValidateRequest(token) 

with parameters:

token: Non-null, non-empty string of a base 64 encoded token. 

For an example token, see the generation.

Signing the request/metadata

To sign the request/metadata , Trustbuilder lets you use a template in the xml structure. This way you can place the code where you need it. The SAML Adapter finds this xml code en will replace the content with a enveloped signature.

Properties : * Reference : the reference of the signature * SignatureMethod : the signature method url according to the w3 standards.
* DigestMethod : the digest method url according to the w3 standards .

By default Trustbuilder includes both PKName and X509Name. In order to exclude one or the other extra attributes can be passed.

  • excludeKeyName : Exclude the name from the signed element
  • excludeX509Name : Exclude the x509 certificate from the signed element
  • excludePKName : Exclude the rsa public key from the signed element

Signing example

<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
                       Reference="#ref20130726"
                       excludePKName="true"
                       SignatureMethod="http://www.w3.org/2000/09/xmldsig#rsa-sha256"
                       DigestMethod="http://www.w3.org/2000/09/xmldsig#sha256"
/>

Response - API

Common Properties

The response API can be applied to the variable specified in the "output property" (see "Workflow Settings"): to verify whether the action performed by the adapter was successful, to query for the data returned by the adapter.

All responses have four properties in common:

  • status Status flag indicating whether the response is ok (0) or not (1).
  • substatus Response specific number indicating what the problem was, eg. http status code
  • message Response specific message in case there was a problem (can be null)
  • rc Return Code, a human readable code based on the substatus

Adapter Specific Properties

  • generated boolean flag indicating if the response was a saml response that is being generated or being validated
  • id ID of the SAML
  • version Version of SAML
  • issuer Issuer of the SAML response
  • subject Subject of the SAML respons
  • issueInstant Date on which the saml was issued
  • statusCode Saml StatusCode
  • size Number of available assertions
  • destination Destination of the SAML
  • consent Consent of the SAML response
  • getSigners() Array of X509Certificate Objects
  • getAssertions() Array of assertions containing :
    • id
    • version
    • issuer
    • subject
    • issueInstant
    • getSigners() Array of X509Certificate Objects
    • getAuthnStatements() Array of objects containing
      • instant date of the instant
      • notOnOrAfter
      • index
      • subjectAddress
      • subjectDns
      • authnContextClassRef
      • getAuthnContextDecl()
      • getAuthnContextDeclRef()
    • getAttributes(): Array of objects containing
      • name
      • nameFormat
      • friendlyName
      • values

Response Codes

If all is ok, the status is zero, for non-zero statusses you can find the description below.

  • 1 = Internal Error
  • 2 = Token is malformed
  • 3 = Not before assertion failed
  • 4 = Not after assertion failed
  • 5 = Invalid signature structure
  • 6 = Invalid signature
  • 7 = Invalid certificate
  • 8 = Status no success
  • 9 = Error decrypting attribute

Additional Notes

Performance remark

When using the SAML adapter the CPU is used more intensive than other adapters for signing the templates. You should test your system to get an idea how many requests can be handled at the same time.

Mixing IBM and SUN JDK's

When using for example TFIM , the IBM JDK encodes the x509 different as the SUN JDK. IBM sets the x509 certificate on 1 line. Sun sets the x509 use lines breaks in the certificate. To solve this issue you have to add an attribute to your Sun JVM Arguments.

-Dorg.apache.xml.security.ignoreLineBreaks=true 

Examples

Example Generate

function prepareVerify(workItem) {
    var idpslourl = workItem.samlOutput.issuer;
    var sp_issuer = workItem.samlOutput.destination;
    var xml = tb.templater(SAMLLOGOUTXMLTEMPLATE, {issuer: sp_issuer, destination: idpslourl, requestid: workItem.response.requestId});

    workItem.generateInput = tb.samlGenerateRequest(xml);
}

Handle response

function createLogoutResponse(workItem) {
        if(workItem.generateOutput.status != 0){
                workItem.output = generateError(error.SAML_GENERAL, workItem.lang, {
                "Content-Type":"text/html; charset=utf-8"
            });
            return;

        }
        workItem.response.body = tb.templater(
            SAMLREDIRECTPAGE,
            {
                form_action: workItem.samlOutput.issuer,
                relaystate: "",
                samlresponse:workItem.generateOutput.token
            }
        );
        workItem.output=tb.generateResponse(workItem.response.body,workItem.response.headers);
}

Example Validate

function prepareVerify(workItem) {
    workItem.samlInput = tb.samlValidateRequest(workItem.input.parameter("SAMLRequest"));
}

Handle Response

function processSamlResponse(workItem) {
    try {
        log("Saml response: "+workItem.samlOutput);
        var username, auditIdentity;
        var assertions = workItem.samlOutput.getAssertions();

        if(workItem.samlOutput.status !== 0) { 
            throw new TBError(error.SAML_GENERAL); 
        }

        if(assertions.length > 1) {
            throw new TBError(error.SAML_GENERAL);
        }

        log("issuer: "+assertions[0].issuer);
        var attributes = assertions[0].getAttributes();

        if(!assertions[0].issuer.equals(server.samlIssuer)) {
            throw new TBError(error.SAML_UNKOWN_ISSUER);
        }

        // Check the attributes.
        var attrPrefix = "tagvalue_",
                    userAttr=[],
                    groups=[];

        for(var key in attributes) {
            var attrName = attributes[key].name,
                attrVal = [],
                values = attributes[key].values;

            if(values.length > 1) {
                attrVal = [];
                for (var value in values) {
                    attrVal.push(values[value]);
                }
            } else {
                attrVal = attrVal.push(values[0]);
            }
        }
        return true;
    } catch(e) {
        if(e.status) {
            workItem.auditObj.addDetail("Error Code:" + e.status);
        }
        if(e instanceof TBError){
            workItem.output = generateError(e, workItem.lang, {
                "Content-Type":"text/html; charset=utf-8"
            });
        } else if(e instanceof SyntaxError) {
            workItem.output = generateError(error.NO_JSON, workItem.lang, {
                "Content-Type":"text/html; charset=utf-8"
            });
        } else {
            log("Message: ", e.message);
            workItem.output = generateError(error.JS_ERROR, workItem.lang, {
                "Content-Type":"text/html; charset=utf-8"
            });
        }
        return false;
    }  
}
Have more questions? Submit a request

Comments