How do i verify & validate Base64 encoded SAML in Apigee in IDP initiated Flow

Hello, 

I am new to Apigee..  . We have an application which would post the SAML (Base 64 ) encoded  SAML to the Apigee Proxy. Wanted to know how to decode the base 64 SAML & verify the saml assertions and extract a user attribute... which i use it for the futher flows. 

 

1 12 2,369
12 REPLIES 12

The Apigee builtin policy ValidateSAMLAssertion will verify the SAML signature based on certs in the TrustStore.

ExtractVariables can allow you to extract any attributes of interest, like the user attribute, into context variables which you can then reference.

The only obstacle is that ValidateSAMLAssertion requires the SAML to be presented in an XML form. What you are talking about is a base64-encoded SAML assertion. Usually that kind of thing is produced by a multi-step process like this:

  1. generate the XML form of the signed SAML assertion
  2. compress the XML text into a bytestream
  3. base64-encode that bytestream into a String

This answer provides a way to reverse steps 3 and 2 of that process. Attached there is a Java callout that will base64-decode and then zip-inflate the resulting cleartext. That gets you an XML string  which you can pass to ValidateSAMLAssertion. I think unfortunately you cannot pass it directly, you first need to create a Message object.... arguably that Java callout should do that for you since you will always want a Message with message.content containing the XML string.  But that's currently left as an enhancement for you to make. 

Does this help?

EDIT: I updated the callout from that prior answer and pushed it to a public repo. 

find it here: https://github.com/DinoChiesa/Apigee-Java-SAML-Decoder

Thanks you. I am able to get the XML String out of the Javacallout. But, to do the saml assertion, how do i transform the given XML string into an XML payload? How do i do that ?  Or in Java Call out how to do it? I dont see any sample. Show i create a new message type or something. Pls help 🙂

The context variables within Apigee can be of multiple types, among them: String, boolean, number, or Message.   The ValidateSAMLAssertion requires a Message.  

The callout I provided, can either: 

  • emit the XML string value into a String context variable
  • emit the XML string value into the content of a  context variable of Message type

Unfortunately that callout is not able to create a Message on its own. Therefore you must create a Message. Do this with an AssignMessage policy. You can attach the policy into the flow either before or after the callout runs. 

If you attach the AssignMessage policy before the Java callout, you can configure it this way, to create an "empty" message variable: 

<AssignMessage name='AM-ContrivedMessage-1'>
  <AssignTo createNew='true' transport='http' type='request'>contrivedMessage</AssignTo>
  <Set>
    <!-- this message content will be replaced later -->
    <Payload contentType='text/xml'><![CDATA[<root/>]]></Payload>
    <Verb>POST</Verb>
  </Set>
</AssignMessage>

And then follow that with the SAML Decode Java callout: 

<JavaCallout name='Java-SamlDecode'>
  <Properties>
    <Property name='input'>variable-name-containing-encoded-saml-token</Property>
    <!-- specify the message created by the prior AssignMessage -->
    <Property name='output'>contrivedMessage</Property>
  </Properties>
  <ClassName>com.google.apigee.callouts.SamlDecoder</ClassName>
  <ResourceURL>java://apigee-java-callout-samldecoder-20220110.jar</ResourceURL>
</JavaCallout>

On the other hand if you want, you can flip the order of those policies. exec the Java callout first, configure it to emit the output to a String variable. And then follow it with AssignMessage which uses that String variable to populate the content of the payload. Those two policies would look like this: 

<JavaCallout name='Java-SamlDecode'>
  <Properties>
    <Property name='input'>inbound_encoded_saml_token</Property>
    <!-- this will be a String that the policy creates -->
    <Property name='output'>outbound_decoded_saml_assertion</Property>
  </Properties>
  <ClassName>com.google.apigee.callouts.SamlDecoder</ClassName>
  <ResourceURL>java://apigee-java-callout-samldecoder-20220110.jar</ResourceURL>
</JavaCallout>

<AssignMessage name='AM-ContrivedMessage-1'>
  <AssignTo createNew='true' transport='http' type='request'>contrivedMessage</AssignTo>
  <Set>
    <!-- fill the message content with the output of the prior policy -->
    <Payload contentType='text/xml'>{outbound_decoded_saml_assertion}</Payload>
    <Verb>POST</Verb>
  </Set>
</AssignMessage>

I hope this helps. I'll update the README to clarify this point. 

Hi Dino,

I cloned the new version of the code and build it in java-8. Tests everything succeed  in https://github.com/DinoChiesa/Apigee-Java-SAML-Decoder .  When i put the jar in apigee, its failing with the below error . These and the trace result.

 

 

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<JavaCallout async="false" continueOnError="false" enabled="true" name="Java-Callout-1">
    <DisplayName>Java Callout-1</DisplayName>
    <Properties>
        <Property name="input">request.formparam.SAMLResponse</Property>
        <Property name="output">outbound_decoded_saml_assertion</Property>
    </Properties>
    <ClassName>com.google.apigee.callouts.SamlDecoder</ClassName>
    <ResourceURL>java://apigee-java-callout-samldecoder-20220110.jar</ResourceURL>
</JavaCallout>

 

 

 Encoded saml :

 

 

PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIElEPSJfNjIxYzRjNGFlNWQ2MGM3NjhjYzIiICBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAxNC0xMC0xNFQxNDozMjoxN1oiICBEZXN0aW5hdGlvbj0iaHR0cHM6Ly9hcHAuYXV0aDAuY29tL3Rlc3Rlci9zYW1scCI+PHNhbWw6SXNzdWVyIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iPnVybjptYXR1Z2l0LmF1dGgwLmNvbTwvc2FtbDpJc3N1ZXI+PHNhbWxwOlN0YXR1cz48c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9zYW1scDpTdGF0dXM+PHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIFZlcnNpb249IjIuMCIgSUQ9Il81Vks3TFQ3RmxpVWtrYVF1VzZyNGJyRjBERzVFM1g3NiIgSXNzdWVJbnN0YW50PSIyMDE0LTEwLTE0VDE0OjMyOjE3LjI1MVoiPjxzYW1sOklzc3Vlcj51cm46bWF0dWdpdC5hdXRoMC5jb208L3NhbWw6SXNzdWVyPjxTaWduYXR1cmUgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPjxTaWduZWRJbmZvPjxDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+PFNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNyc2Etc2hhMSIvPjxSZWZlcmVuY2UgVVJJPSIjXzVWSzdMVDdGbGlVa2thUXVXNnI0YnJGMERHNUUzWDc2Ij48VHJhbnNmb3Jtcz48VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiLz48VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+PC9UcmFuc2Zvcm1zPjxEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjc2hhMSIvPjxEaWdlc3RWYWx1ZT5aRGtmR08zSDFUdTUwaGF3elFWanNBQ3pKd2M9PC9EaWdlc3RWYWx1ZT48L1JlZmVyZW5jZT48L1NpZ25lZEluZm8+PFNpZ25hdHVyZVZhbHVlPjFGZ3B0N0FhSGNNRTJnVEExNThhY2h2R1FWcUR3SFNIc0hGMy9hNXM3ZGplTzFBYVo4NEd6NWVpV0QrY2RJejZob1QxajJ2LzdRdGZqNWJzTU54dWxCblN6Zkw0VFQ0KytBSy96Rk80YWQybXdBQkZqTU1OaW9nd3dUM3R6eTNhUndqZ1NmbDNWS0VTb3ozWnA4S0wvSk5tL1RSS3o5NVREN0g2V25mS1pvTHdFckd6Tnc2Z1ZzMXk5WFl4SUVnNDZHelViMDdnMjNURm1ydjN3SGx4MlRwS1VOL25lNFoyOEtBUXpYcVZ5eWtKVmFLUS9nYkJOQy84QVFLbG9sOGZMR1NoZU9LUTB2Z0VFMXZGblZWQ0VtcDMwWWFwZEtlV1cycWNxSGI3T3FkbSs5YjJtT1VrcWJheEg1aXhCYllhcVphUUN0NVdGNFA1QnhuTWU0QnA4dz09PC9TaWduYXR1cmVWYWx1ZT48S2V5SW5mbz48WDUwOURhdGE+PFg1MDlDZXJ0aWZpY2F0ZT5NSUlET0RDQ0FpQ2dBd0lCQWdJSkFON085aGVmMDVSSk1BMEdDU3FHU0liM0RRRUJCUVVBTUJ3eEdqQVlCZ05WQkFNVEVXMWhkSFZuYVhRdVlYVjBhREF1WTI5dE1CNFhEVEV6TURZeE5qRXpNalF3TmxvWERUSTNNREl5TXpFek1qUXdObG93SERFYU1CZ0dBMVVFQXhNUmJXRjBkV2RwZEM1aGRYUm9NQzVqYjIwd2dnRWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUUQ4a0VmRWNMaXlNSzBxNEU0eFQvbWdsY1hzZjlvQ1dkVGpCMHZzd0ZYUVBDRGdVazVkYUptTzRvUnJDSXVXZ3RDaER5TWM1TTVNVGV4cXJDbnRRbEtwbmV4a3pEeEN2UFgvSVJKc3RqSkRTSCtXWGxqUllEYlhUZ0FaQWpqakFLcURCSWVVV2F3MUZjVXkzU09tUVZ0eVVkZmpYTnFMenhLUzhTVVdZWTNJYTZQVC91b1VqVUZtVXVKRUZwQmxCNzlHVytUdi9aaWRZNTNnSHloRVlPQnptMXZLWlMxdnRQZnh4cEJxdTdWa2YrNWxxM1JZanVvVlVZTjAzVElKdmVhYTdCQkwwamU4ejdVdnZPR2dyR3VvZFNLSG5lcTd1ZWV4NE9Dc2NDSlpiSzdNbGZibUZpdVNuaVZDZy95TXJ4L29rRG1lMzhzT1hIMUEvekp1MmxZcEFnTUJBQUdqZlRCN01CMEdBMVVkRGdRV0JCUUwzY3VEWE5YdTRVU3JwdjQ0ZHpmR3lISVhEVEJNQmdOVkhTTUVSVEJEZ0JRTDNjdURYTlh1NFVTcnB2NDRkemZHeUhJWERhRWdwQjR3SERFYU1CZ0dBMVVFQXhNUmJXRjBkV2RwZEM1aGRYUm9NQzVqYjIyQ0NRRGV6dllYbjlPVVNUQU1CZ05WSFJNRUJUQURBUUgvTUEwR0NTcUdTSWIzRFFFQkJRVUFBNElCQVFDQXRBbUtIb1g3T2VyekZVUTBPanJnOUMxdXpUNmZPTElsWHlhRHN3azJ0TEU2ZnRGaFNpVnpUMVR0Z1RGdjJvNklBNDd0VzZicUw3dDZaNUpOL0w4Y25vK2t4ZUtPZ1B6MkN2YlVUSm10Uk92RlYvVERrRnNZbUZKWjgrNm5aT0F0WFJaUFdGcGE2S0U0TGM1KzdKOTRzWDJBaVlQRkNuWFJ2WkdNaEo2cXRjK2poNTRRRUlseFp0a3hXVUJHczJmaE9RbStVeDB1eTFxeFN6aHAzbG12TmE3OUtkdm1RaXJTaXREb3Z5aWRhbHB3WDc1MFdHTmZRb0lTZTk0ZTNjbFdhNHVLWGdaN0FZbWhkMXc0a05zN3NlRmR0dkNjcVFKcktZVDJjQlRJTnp0TDZTZXFlRi94Qy8vbEdEbzlVSmljNHBjcnFqMVAzV0dIWUlzbE9XQlE8L1g1MDlDZXJ0aWZpY2F0ZT48L1g1MDlEYXRhPjwvS2V5SW5mbz48L1NpZ25hdHVyZT48c2FtbDpTdWJqZWN0PjxzYW1sOk5hbWVJRCBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OnVuc3BlY2lmaWVkIj5naXRodWJ8MTc1ODgwPC9zYW1sOk5hbWVJRD48c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+PHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbkRhdGEgTm90T25PckFmdGVyPSIyMDE0LTEwLTE0VDE1OjMyOjE3LjI1MVoiIFJlY2lwaWVudD0iaHR0cHM6Ly9hcHAuYXV0aDAuY29tL3Rlc3Rlci9zYW1scCIvPjwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPjwvc2FtbDpTdWJqZWN0PjxzYW1sOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDE0LTEwLTE0VDE0OjMyOjE3LjI1MVoiIE5vdE9uT3JBZnRlcj0iMjAxNC0xMC0xNFQxNTozMjoxNy4yNTFaIj48c2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPjxzYW1sOkF1ZGllbmNlPnVybjpmb288L3NhbWw6QXVkaWVuY2U+PC9zYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+PC9zYW1sOkNvbmRpdGlvbnM+PHNhbWw6QXR0cmlidXRlU3RhdGVtZW50IHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSI+PHNhbWw6QXR0cmlidXRlIE5hbWU9Imh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL25hbWVpZGVudGlmaWVyIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6YW55VHlwZSI+Z2l0aHVifDE3NTg4MDwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9lbWFpbGFkZHJlc3MiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czphbnlUeXBlIj5tYXRpYXN3QGdtYWlsLmNvbTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6YW55VHlwZSI+TWF0aWFzIFdvbG9za2k8L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48c2FtbDpBdHRyaWJ1dGUgTmFtZT0iaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvdXBuIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6YW55VHlwZSI+bWF0aWFzd0BnbWFpbC5jb208L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48c2FtbDpBdHRyaWJ1dGUgTmFtZT0iaHR0cDovL3NjaGVtYXMuYXV0aDAuY29tL2lkZW50aXRpZXMvZGVmYXVsdC9hY2Nlc3NfdG9rZW4iPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czphbnlUeXBlIj4zYTdkMGRmZWZmZTEyODEyYzM3MTEyZGFhODMwYWJlZjU3MDA4OWI0PC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PHNhbWw6QXR0cmlidXRlIE5hbWU9Imh0dHA6Ly9zY2hlbWFzLmF1dGgwLmNvbS9pZGVudGl0aWVzL2RlZmF1bHQvcHJvdmlkZXIiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czphbnlUeXBlIj5naXRodWI8L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48c2FtbDpBdHRyaWJ1dGUgTmFtZT0iaHR0cDovL3NjaGVtYXMuYXV0aDAuY29tL2lkZW50aXRpZXMvZGVmYXVsdC9jb25uZWN0aW9uIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6YW55VHlwZSI+Z2l0aHViPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PHNhbWw6QXR0cmlidXRlIE5hbWU9Imh0dHA6Ly9zY2hlbWFzLmF1dGgwLmNvbS9pZGVudGl0aWVzL2RlZmF1bHQvaXNTb2NpYWwiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czphbnlUeXBlIj50cnVlPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD48c2FtbDpBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIwMTQtMTAtMTRUMTQ6MzI6MTcuMjUxWiI+PHNhbWw6QXV0aG5Db250ZXh0PjxzYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOnVuc3BlY2lmaWVkPC9zYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPjwvc2FtbDpBdXRobkNvbnRleHQ+PC9zYW1sOkF1dGhuU3RhdGVtZW50Pjwvc2FtbDpBc3NlcnRpb24+PC9zYW1scDpSZXNwb25zZT4=

 

 

Java Stack Error in Apigee Debug 

 

 

decoder_error
invalid code lengths set
decoder_stacktrace
java.util.zip.DataFormatException: invalid code lengths set at java.util.zip.Inflater.inflateBytes(Native Method) at java.util.zip.Inflater.inflate(Inflater.java:259) at java.util.zip.Inflater.inflate(Inflater.java:280) at com.google.apigee.callouts.SamlDecoder.decompress(SamlDecoder.java:121) at com.google.apigee.callouts.SamlDecoder.execute(SamlDecoder.java:155) at com.apigee.steps.javacallout.JavaCalloutStepDefinition$ClassLoadWrappedExecution.execute(JavaCalloutStepDefinition.java:204) at com.apigee.steps.javacallout.JavaCalloutStepDefinition$SecurityWrappedExecution$1.run(JavaCalloutStepDefinition.java:271) at com.apigee.steps.javacallout.JavaCalloutStepDefinition$SecurityWrappedExecution$1.run(JavaCalloutStepDefinition.java:269) at java.security.AccessController.doPrivileged(Native Method) at com.apigee.steps.javacallout.JavaCalloutStepDefinition$SecurityWrappedExecution.execute(JavaCalloutStepDefinition.java:269) at com.apigee.steps.javacallout.JavaCalloutStepDefinition$CallOutWrapper.execute(JavaCalloutStepDefinition.java:138) at com.apigee.messaging.runtime.steps.StepExecution.execute(StepExecution.java:156) at com.apigee.flow.execution.AbstractAsyncExecutionStrategy$AsyncExecutionTask.call(AbstractAsyncExecutionStrategy.java:74) at com.apigee.flow.execution.AbstractAsyncExecutionStrategy$AsyncExecutionTask.call(AbstractAsyncExecutionStrategy.java:45) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748)

 

{
    "fault": {
        "faultstring": "Execution returned an error result",
        "detail": {
            "errorcode": "flow.execution.ExecutionReturnedFailure"
        }
    }
}

 

Yes.  Your encoded SAML assertion is not deflated. Deflation is an optional part of how SAML assertions get encoded. 

By default the callout assumes the assertion has been deflated. If you have an assertion that has not been deflated, then you need to configure the policy to NOT try to inflate it. like this:

 

<JavaCallout name='Java-SamlDecode-No-Inflate'>
  <Properties>
    <Property name='input'>request.formparam.SAMLResponse</Property>
    <Property name='inflate'>false</Property> <!-- DO NOT INFLATE -->
    <Property name='output'>output_decoded_saml_assertion</Property>
  </Properties>
  <ClassName>com.google.apigee.callouts.SamlDecoder</ClassName>
  <ResourceURL>java://apigee-java-callout-samldecoder-20220110.jar</ResourceURL>
</JavaCallout>

 

 

 now it worked!!  🙂 Back to the SAML Assertion. I tried to validate the SAML assertion for the new XML output. It fails saying .. 

 

 

 

{"fault":{"faultstring":"ValidateSAMLAssertion[SAML]: Invalid Media Type application\/x-www-form-urlencoded. Should be XML.","detail":{"errorcode":"steps.saml.validate.InvalidMediaType"}}}

 

 

 

 Looks like the Assign message isnt working as expected. . I tried changing content type also in Assign message. Even tough, create new="true" , It is somehow taking the previous request content type ( The POST request which i submitted while  sending the encoded SAML-application/x-www-form-urlencoded

<AssignMessage name='AM-ContrivedMessage-1'>
  <AssignTo createNew='true' transport='http' type='request'>contrivedMessage</AssignTo>
  <Set>
    <!-- fill the message content with the output of the prior policy -->
    <Payload contentType='text/xml'>{outbound_decoded_saml_assertion}</Payload>
    <Verb>POST</Verb>
  </Set>
</AssignMessage>

 

What does your ValidateSAML policy look like? It should refer to the contrivedMessage variable. Are you doing that?

 

<ValidateSAMLAssertion name="SAML" ignoreContentType="false">

  <!-- what did you configure for Source ? -->
  <Source name="contrivedMessage">  <!-- HERE -->

    <Namespaces>
      <Namespace prefix='soap'>http://schemas.xmlsoap.org/soap/envelope/</Namespace>
      <Namespace prefix='wsse'>http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd</Namespace>
      <Namespace prefix='saml'>urn:oasis:names:tc:SAML:2.0:assertion</Namespace>
    </Namespaces>
    <XPath>/soap:Envelope/soap:Header/wsse:Security/saml:Assertion</XPath>
  </Source>
  <TrustStore>TrustStoreName</TrustStore>
  <RemoveAssertion>false</RemoveAssertion>
</ValidateSAMLAssertion>

 

Here is the related documentation. It would be very surprising to me, if you configured ValidateSAML to refer to contrivedMessage, and yet the policy picks up the content-type from the original request message.

It was pointing to request! My Bad. 

Now, i am getting a another issue ..

When i say source = contrivedMessage i am unable to save. 

Below is my Assign message & Validate SAML

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<AssignMessage name="AM-ContrivedMessage-1">
    <AssignTo createNew="true" transport="http" type="request">contrivedMessage</AssignTo>
    <Set>
        <!-- fill the message content with the output of the prior policy -->
        <Payload contentType="text/xml">{abc}</Payload>
        <Verb>POST</Verb>
    </Set>
</AssignMessage>

 

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ValidateSAMLAssertion ignoreContentType="false" name="Validate-SAML-Assertion-1">
    <DisplayName>Validate SAML Assertion-1</DisplayName>
<Source name="contrivedMessage"> 
<Namespaces>
     <Namespace prefix="samlp">urn:oasis:names:tc:SAML:2.0:protocol</Namespace>
     <Namespace prefix="saml">urn:oasis:names:tc:SAML:2.0:assertion</Namespace>
    </Namespaces>
    <XPath>/samlp:Response/saml:Assertion</XPath>
 </Source> 
  <TrustStore>testsamlks</TrustStore>
  <RemoveAssertion>true</RemoveAssertion>
</ValidateSAMLAssertion>

 

I am unable to save :

 

apiproxy/policies/SAML.xml: The Source element name attribute "contrivedMessage" must be one of "message", "request", or "response".

 

This is only observed in cloud version of Apigee(https://apigee.google.com/) . We have a private cloud instance of Apigee there i am not seeing this error . Any syntax issue ??

Oops, it was my mistake . I had given as request. But, when i change it to  "contrivedMessage" , i am unable to save it. It is throwing below error. 

 

apiproxy/policies/SAML.xml: The Source element name attribute "contrivedMessage" must be one of "message", "request", or "response".

 

This is the behaviour in Apigee Cloud (https://apigee.google.com/) . But when i give the same in Apigee instance of our company's  private cloud(v4.19.01.00), i am not getting any error while saving. But, Signature verification is failing with the below error. 

 

{
    "fault": {
        "faultstring": "ValidateSAMLAssertion[SAML]: Digital Signature Validation Failed",
        "detail": {
            "errorcode": "steps.saml.validate.SignatureValidationFailed"
        }
    }
}

 

If i directly copy the decoded SAML  message output and do the SAML Assertion it is working fine . The signature is able to verify. 

Below is my policy configs

 

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<JavaCallout name="Java-SamlDecode">
    <Properties>
        <Property name="input">request.formparam.SAMLResponse</Property>
        <Property name="output">abc</Property>
        <Property name="inflate">false</Property>
    </Properties>
    <ClassName>com.google.apigee.callouts.SamlDecoder</ClassName>
    <ResourceURL>java://apigee-java-callout-samldecoder-20220110.jar</ResourceURL>
</JavaCallout>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<AssignMessage name="AM-ContrivedMessage-1">
    <AssignTo createNew="true" transport="http" type="request">contrivedMessage</AssignTo>
    <Set>
        <!-- fill the message content with the output of the prior policy -->
        <Payload contentType="text/xml">{abc}</Payload>
        <Verb>POST</Verb>
    </Set>
</AssignMessage>

 

 

 

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ValidateSAMLAssertion name="SAML" ignoreContentType="false">
    <Source name="contrivedMessage">
        <Namespaces>
            <Namespace prefix="samlp">urn:oasis:names:tc:SAML:2.0:protocol</Namespace>
            <Namespace prefix="saml">urn:oasis:names:tc:SAML:2.0:assertion</Namespace>
        </Namespaces>
        <XPath>/samlp:Response/saml:Assertion</XPath>
    </Source>
    <TrustStore>testsamlks</TrustStore>
    <RemoveAssertion>true</RemoveAssertion>
</ValidateSAMLAssertion>

 

OK you've got multiple problems there.

when i change it to "contrivedMessage" , i am unable to save it. It is throwing below error.

That's a bug. Sorry you're having that experience. It sounds to me that Apigee uses validation logic that is too strict on that policy. The source should allow any message, including a custom message of any name that you create with AssignMessage. I'll file a defect on this. (EDIT: done. Reference: b/219067839) We'll get it fixed, but the fix won't be available in a timeframe that allows you to make progress.

So you'll need to use a workaround, which is... to assign the content to the request message. If you have something in the content of the request message that you want to preserve, then you need to first save that content, which you can do with a different AssignMessage policy - basically just copy the request message into a new message, maybe called something like "savedRequest". And then at a later point you can restore the request message if you need to do that.

But anyway, saving and restoring the request message seems like an issue you could defer for now. The first thing is to decode the assertion, and then validate the signature.

Which brings us to the 2nd problem you described:

Signature verification is failing with the below error.

And thank you so much for adding this bit of information:

If i directly copy the decoded SAML message output and do the SAML Assertion it is working fine . The signature is able to verify.

That's good news.

Something is different in what you're doing with the direct copy, and what the policy steps are doing, using AssignMessage to copy the content.  

The best thing I can suggest to diagnose that, is to examine the results of the proxy in a Debug session. Can you start a trace and observe the interim results of each policy?  Does the value of "abc" look correct to you after the Java callout policy? Does it look correct after the AssignMessage policy.

We should be able to figure this out, with some additional diagnostic efforts. We're pretty close.

I recommend you look at one thing closely: url encoding.

If submitted as a form parameter, the encoded SAMLResponse parameter must be url-encoded. This is usually the case if the form request is sent from within an app, but it might not be the case in the limited testing you are doing. 

In particular if there are + characters in the encoded assertion, they need to be encoded as %2B by the client.  And if there are = characters, then you need to encode those as %3D. 

I noticed that the SAMLResponse sample you offered in your above post includes both + and = characters. 

When I tried this from curl, like so:

curl -i $endpoint/validate-saml-assertion/byo-assertion -d SAMLResponse=ENCODED_SAML_RESPONSE_HERE

...I saw these results:  

  • the operation failed if i used the SAMLResponse exactly as you pasted it in the above message (there are  + and = in the string you showed for the encoded saml response)
  • the operation succeeded when I url-encoded the base64-encoded SAMLResponse. ( i replaced each + with %2B and each = with %3D )

Curl will not encode these characters for you, automatically.  You need to do that yourself. So maybe you have the same kind of problem with other clients that you might be using, like postman etc.

 

Hey Dino,

I did a work around to handle this. 

  • Created a Proxy to handle the XML Payload as request and do the SAML Assertion(/samlAssert).  
  • Created a Proxy to Decode the base64 SAML and pass assign the output of that to a new message and then did a service callout to (/samlAssert) 

This worked  🙂