pythonsoapsamlzeep

SOAP Header Invalid Signature on Timestamp


One of our SAML signatures in the SOAP header is invalid. You can confirm it fails at this website: https://tools.chilkat.io/xmlDsigVerify.cshtml . We have attempted troubleshooting to no avail. We believe the issue lies somewhere with how we are creating the signature itself. I've included a test XML we generate, and then the code block for how we create the signature for the timestamp.

This is the XML that we generate (I made it pretty print for everyone to review but we don't actually pass it as pretty print):

<?xml version="1.0"?>
<soap-env:Envelope xmlns:soap-env="http://www.w3.org/2003/05/soap-envelope">
  <soap-env:Header xmlns:wsa="http://www.w3.org/2005/08/addressing">
    <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" soap-env:mustUnderstand="true">
      <Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="_1">
        <Created>2024-03-08T16:26:59.802Z</Created>
        <Expires>2024-03-08T17:26:59.802Z</Expires>
      </Timestamp>
      <samlns:Assertion xmlns:samlns="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Version="2.0" ID="_1d69ba1f-e047-45e9-a2be-d21ec70827eb" IssueInstant="2024-03-08T16:26:59.802Z">
        <samlns:Issuer NameQualifier="urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName">CN=ourorgname, OU=IT, O=ourorgname, L=NewYork</samlns: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#"/>
            <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
            <ds:Reference URI="#_1d69ba1f-e047-45e9-a2be-d21ec70827eb">
              <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>
              <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
              <ds:DigestValue>+Kbf8gnL85LKLfHFtRftp9FtiPStCklxeY+mBko9B14=</ds:DigestValue>
            </ds:Reference>
          </ds:SignedInfo>
          <ds:SignatureValue>Fftcv2W9Eq+iGl+8Yp6wjufX0wMCf3JDsN83kRE7dHiL2ADKEW2bV4akyNIo/hnzDopBi6USQPx1//wQ1BoqVaYKheK+AUHWh7i91zSaVy665OaZO+cA8xXKBCVSTmL617pcsrN7+25FtILn0cdTxGG+WbKrZWgG+WWrXeZpJ6idLlJbm+DK0EQUS/aTocxro7Al6/Grg2jG5bT9pARk35zIJfrzX6Chun6OBLXVxUInVk1CFeLhPFvK3qNqb3DhGXQ5nN0eIjhI/YxGR6omlMFpXRUuLGVmEQNx5R24u1Nzok1DqErGLEO9yW6Wj1e4U6D1M5NslOwula9T8o4ltw==</ds:SignatureValue>
          <ds:KeyInfo>
            <ds:KeyValue>
              <ds:RSAKeyValue>
                <ds:Modulus>3yi31ivq8LPcR+e7d52IoY576QqrlkyriwKEPcPp1mkOJ5ScgtTEyEAqz2doE0aQJ8TEY2phzRIk5nnkM0ZE6DkK+1IeLW5JhAqJlpAgjbsJMcPTX6ftjQtOWyB4r5pgxG5BagiYHyLUiVavO3lP7DsaNLrKA6sRBvan+19DnZN9q7vvdG3fnioZNh91EZRsG8ZbBIuG6wp2ctqWcdTHBlEtCO4cmk5tiU6IdxoXiLR1PdrBq336t11dS+0iGVaBXNz+An/AuslVw0rwB+JxEtggrAL+ZXJ9WkVPZh9gQMacSrz9LGZN6lv06QVXI1wJZgG/cjjL2tWy8iyoB4VN6w==</ds:Modulus>
                <ds:Exponent>AQAB</ds:Exponent>
              </ds:RSAKeyValue>
            </ds:KeyValue>
            <ds:X509Data>
              <ds:X509Certificate>MIIGojCCBIqgAwIBAgIRAINdCG9+zvGuT8bNnY1bUJswDQYJKoZIhvcNAQELBQAwZTELMAkGA1UE
BhMCVVMxEzARBgNVBAgMCk5FVyBKRVJTRVkxETAPBgNVBAcMCEZvcnQgTGVlMQ4wDAYDVQQKDAVN
YXhNRDEeMBwGA1UEAwwVTWF4TUQgVExTIFJTQSBFVkFMIENBMB4XDTI0MDEyMjE5MzE1OFoXDTI1
MDEyMTE5MzE1OFowcDELMAkGA1UEBhMCVVMxITAfBgNVBAoMGEFic3RyYWN0aXZlIEhlYWx0aCwg
SW5jLjEZMBcGA1UECwwQQ0FSRVFVQUxJVFktVEVTVDEjMCEGA1UEAwwaY2FyZXF1YWxpdHkuYWJz
dHJhY3RpdmUuYWkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDfKLfWK+rws9xH57t3
nYihjnvpCquWTKuLAoQ9w+nWaQ4nlJyC1MTIQCrPZ2gTRpAnxMRjamHNEiTmeeQzRkToOQr7Uh4t
bkmEComWkCCNuwkxw9Nfp+2NC05bIHivmmDEbkFqCJgfItSJVq87eU/sOxo0usoDqxEG9qf7X0Od
k32ru+90bd+eKhk2H3URlGwbxlsEi4brCnZy2pZx1McGUS0I7hyaTm2JToh3GheItHU92sGrffq3
XV1L7SIZVoFc3P4Cf8C6yVXDSvAH4nES2CCsAv5lcn1aRU9mH2BAxpxKvP0sZk3qW/TpBVcjXAlm
Ab9yOMva1bLyLKgHhU3rAgMBAAGjggJAMIICPDAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYI
KwYBBQUHAwEGCCsGAQUFBwMCMEUGA1UdEQQ+MDyCGmNhcmVxdWFsaXR5LmFic3RyYWN0aXZlLmFp
hh5IVFRQOi8vV1dXLkNBUkVRVUFMSVRZLk9SRy9WMDEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU
aeChng95VdF8VTR9dYonqCfjBLEwHwYDVR0jBBgwFoAUtovLrSuyoOG2jMJKizXgt4QpwVYwbgYI
KwYBBQUHAQEEYjBgMF4GCCsGAQUFBzAChlJodHRwOi8vd3d3LmRpcmVjdG1kZW1haWwuY29tL0NB
X1JlcG9zaXRvcnkvTWF4TURUbHNSc2FFVkFMQ0EvTWF4TURUbHNSc2FFVkFMQ0EucDdiMGMGA1Ud
HwRcMFowWKBWoFSGUmh0dHA6Ly93d3cuZGlyZWN0bWRlbWFpbC5jb20vQ0FfUmVwb3NpdG9yeS9N
YXhNRFRsc1JzYUVWQUxDQS9NYXhNRFRsc1JzYUVWQUxDQS5jcmwwgaAGA1UdIASBmDCBlTAMBgor
BgEEAYLBWwEFMAwGCisGAQQBgsFbAwEwDAYKKwYBBAGCwVsCBTBpBgsrBgEEAYLBWwACADBaMFgG
CCsGAQUFBwIBFkxodHRwczovL3d3dy5kaXJlY3R0cnVzdC5vcmcvcmVzb3VyY2VzL2NvbXBsaWFu
Y2UtYW5kLWtleS1wb2xpY2llcyNjb21wbGlhbmNlMA0GCSqGSIb3DQEBCwUAA4ICAQCZzgvvC3mp
vil434M4VSl90WhZXwLfBsFCZjjGwkWAZDAuNHGIzQTq7EmVBmHTlswz5xg3tNw31Oq6GOO7yuUt
vWip5OMK1X2jxclyTENyZUNf38Sj0TjC0/JYDTaqlksjoHaUaXCuY/qHVek13lCGjRxj4HSV8eCo
NFyhyn4rujNcpelomxFXPo5ahZ9sqoF64AxJ4I8FCmHUVAXe8wjOAWkXSw8tTVl3Mt9HG6zeTPAw
mnV0pJtcwIB5UQnaZn01MoGvaxP0ddcpLADOb3+uDaKV3XJT72K3nzlc7HEQlkKgrSaEvGhrcu2a
Rkr2QgyRzeaF1Oo907G+Yt6OdjBf8ctgrhCsDW1GTeGogeE0xNyvSPjXRv3eUWVMR/IUERfmhnEB
m1+kpCDEXplAXNcaM+QijayufKDFX8dPd6wCk39QuSDDemdpof0fdbv4vrJ6AU9uqhtgIyOcnLJe
z2JLnIcNCXQ2icAEKnN1UG7zfbdd1ghuD+aIxKLzN0YNXbtf5XzOIFMvf3MDrgn1iEvGnCAToOK6
JUjElJNumAsJbh/gvSgRh5RP7s/c7A14KGrNoSPrFjjjLGUhISPpREeCZwb7ZdaWiaaaQDzq++eS
9eCUbnosagtLxOq2ndJQxdf0YaKvPd5QDA0u1Km+jThzJXs1xZOTIpWDQFN1KMENyg==
</ds:X509Certificate>
            </ds:X509Data>
          </ds:KeyInfo>
        </ds:Signature>
        <samlns:Subject>
          <samlns:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName">CN=ourorgname, OU=IT, O=ourorgname, L=NewYork</samlns:NameID>
          <samlns:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:holder-of-key">
            <samlns:SubjectConfirmationData/>
          </samlns:SubjectConfirmation>
        </samlns:Subject>
        <samlns:Conditions NotBefore="2024-03-08T16:26:59.802Z" NotOnOrAfter="2024-03-08T17:26:59.802Z">
          <samlns:AudienceRestriction>
            <samlns:Audience>http://ihe.connectathon.XUA/X-ServiceProvider-IHE-Connectathon</samlns:Audience>
          </samlns:AudienceRestriction>
        </samlns:Conditions>
        <samlns:AuthnStatement AuthnInstant="2024-03-08T16:26:59.802Z">
          <samlns:AuthnContext>
            <samlns:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</samlns:AuthnContextClassRef>
          </samlns:AuthnContext>
        </samlns:AuthnStatement>
        <samlns:AttributeStatement>
          <samlns:Attribute Name="urn:oasis:names:tc:xspa:1.0:subject:subject-id" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic" FriendlyName="XSPA Subject">
            <samlns:AttributeValue xsi:type="xs:string">valid</samlns:AttributeValue>
          </samlns:Attribute>
          <samlns:Attribute Name="urn:oasis:names:tc:xspa:1.0:subject:organization" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" FriendlyName="XSPA Organization">
            <samlns:AttributeValue xsi:type="xs:string">ourorgname</samlns:AttributeValue>
          </samlns:Attribute>
          <samlns:Attribute Name="urn:oasis:names:tc:xspa:1.0:subject:organization-id" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" FriendlyName="XSPA Organization ID">
            <samlns:AttributeValue xsi:type="xs:string">urn:oid:0.0</samlns:AttributeValue>
          </samlns:Attribute>
          <samlns:Attribute Name="urn:ihe:iti:xca:2010:homeCommunityId" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" FriendlyName="XCA Home Community ID">
            <samlns:AttributeValue xsi:type="xs:string">urn:oid:0.0</samlns:AttributeValue>
          </samlns:Attribute>
          <samlns:Attribute Name="urn:oasis:names:tc:xspa:1.0:subject:purposeofuse" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" FriendlyName="Purpose of Use">
            <samlns:AttributeValue xsi:type="xs:string">TREATMENT</samlns:AttributeValue>
          </samlns:Attribute>
          <samlns:Attribute Name="urn:oasis:names:tc:xacml:2.0:subject:role" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" FriendlyName="HL7 Role">
            <samlns:AttributeValue xsi:type="xs:string">MedicalDoctor</samlns:AttributeValue>
          </samlns:Attribute>
        </samlns:AttributeStatement>
      </samlns:Assertion>
      <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#"/>
          <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
          <ds:Reference URI="#_0">
            <ds:Transforms>
              <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
            </ds:Transforms>
            <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
            <ds:DigestValue>07nepYP63amsgcufvkLHuLlIMOGG8r2g54JkSTx/d5g=</ds:DigestValue>
          </ds:Reference>
          <ds:Reference URI="#_1">
            <ds:Transforms>
              <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
            </ds:Transforms>
            <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
            <ds:DigestValue>0x8aRv+y3gfky0FaUMxHWPaNmwc4fYq0anpvHyees/0=</ds:DigestValue>
          </ds:Reference>
        </ds:SignedInfo>
        <ds:SignatureValue>wmP3xxadW9s6VcLlDOTICH1MWS4aH4WDvu3saw/JmBfDROY7V63q6yRRGCSV24ywtVZZ+euL1jkxHb44QtSwKPH6SB1ZSMihapSJJjLAUM74TFhsb2is+NAqqBcvux/U+CXD5TSKxTKJgBFGDHUCI8jEaF8+SBx1awpWpVkcxQQD0fVMiOpyDaqex6UfAVuagDho7zHYr3/jKhLvlqzBpelYS0W9P7V7PeSqBGjjyPm/YOCQ1T7K2PIERISyP335JbWgn14GSgyaR/QQVCr00MnzwlD+sJBMNtFwfLYi7f51f/Q28HBr6h/Yl6/C87KXL7Y84S0d00Y0HNfS5nNODQ==</ds:SignatureValue>
        <ds:KeyInfo>
          <wsse:SecurityTokenReference TokenType="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0">
            <wsse:KeyIdentifier ValueType="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLID">_1d69ba1f-e047-45e9-a2be-d21ec70827eb</wsse:KeyIdentifier>
          </wsse:SecurityTokenReference>
        </ds:KeyInfo>
      </ds:Signature>
    </wsse:Security>
    <wsa:Action>urn:hl7-org:v3:PRPA_IN201305UV02:CrossGatewayPatientDiscovery</wsa:Action>
    <wsa:MessageID>urn:uuid:b0fe7b2a-9129-4c11-992c-1fcd1201dcbc</wsa:MessageID>
    <wsa:To xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="_0">replacementto.org</wsa:To>
    <wsa:Action soap-env:mustUnderstand="1">urn:hl7-org:v3:PRPA_IN201305UV02:CrossGatewayPatientDiscovery</wsa:Action>
    <wsa:ReplyTo>
      <wsa:Address>ourownurl.org</wsa:Address>
    </wsa:ReplyTo>
  </soap-env:Header>
  <soap-env:Body>
    <ns0:PRPA_IN201305UV02 xmlns:ns0="urn:hl7-org:v3" ITSVersion="XML_1.0">
      <ns0:id extension="2211" root="4d271a79-0b22-439a-b64a-72d961a6cd38"/>
      <ns0:creationTime value="20240308162659"/>
      <ns0:interactionId extension="PRPA_IN201305UV02" root="2.16.840.1.113883.1.6"/>
      <ns0:processingCode code="P"/>
      <ns0:processingModeCode code="T"/>
      <ns0:acceptAckCode code="AL"/>
      <ns0:receiver typeCode="RCV">
        <ns0:device classCode="DEV" determinerCode="INSTANCE">
          <ns0:id root="ourownoid"/>
          <ns0:asAgent classCode="AGNT">
            <ns0:representedOrganization classCode="ORG" determinerCode="INSTANCE">
              <ns0:id root="ourownoid"/>
            </ns0:representedOrganization>
          </ns0:asAgent>
        </ns0:device>
      </ns0:receiver>
      <ns0:sender typeCode="SND">
        <ns0:device classCode="DEV" determinerCode="INSTANCE">
          <ns0:id root="2.16.840.1.113883.3.9918"/>
        </ns0:device>
      </ns0:sender>
      <ns0:controlActProcess classCode="CACT" moodCode="EVN">
        <ns0:code code="PRPA_TE201305UV02" codeSystemName="2.16.840.1.113883.1.6"/>
        <ns0:authorOrPerformer typeCode="AUT">
          <ns0:assignedPerson classCode="ASSIGNED"/>
        </ns0:authorOrPerformer>
        <ns0:queryByParameter>
          <ns0:queryId root="61023518-3f6e-4ad5-a465-87082e96b66f"/>
          <ns0:statusCode code="new"/>
          <ns0:responseModalityCode code="R"/>
          <ns0:responsePriorityCode code="I"/>
          <ns0:matchCriterionList/>
          <ns0:parameterList>
            <ns0:livingSubjectAdministrativeGender>
              <ns0:value code="male"/>
              <ns0:semanticsText>LivingSubject.AdministrativeGender</ns0:semanticsText>
            </ns0:livingSubjectAdministrativeGender>
            <ns0:livingSubjectBirthTime>
              <ns0:value value="1955-06-27"/>
              <ns0:semanticsText>LivingSubject.birthTime</ns0:semanticsText>
            </ns0:livingSubjectBirthTime>
            <ns0:livingSubjectName>
              <ns0:value>
                <ns0:family>Hickle134</ns0:family>
                <ns0:given>Abram53</ns0:given>
              </ns0:value>
              <ns0:semanticsText>LivingSubject.name</ns0:semanticsText>
            </ns0:livingSubjectName>
          </ns0:parameterList>
        </ns0:queryByParameter>
      </ns0:controlActProcess>
    </ns0:PRPA_IN201305UV02>
  </soap-env:Body>
</soap-env:Envelope>

This is the portion of our code for signing the Timestamp and To sections of the XML:


# set reference ID for To tag, where toTag is extracted from header generated by zeep.client.create_message
    toTag.attrib[QName("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", "Id")] = "_0"


# Add timestamp to the headers
time_created = self.issued_at.strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z'
time_expires = (self.issued_at + timedelta(hours=1)).strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z'
timestamp = f'<Timestamp xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\" wsu:Id="_1"><Created>{time_created}</Created><Expires>{time_expires}</Expires></Timestamp>'

# Create the SecurityTokenReference element
nsmap = {"wsse": "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", "dsig":"http://www.w3.org/2000/09/xmldsig#"}
E = ElementMaker(namespace=nsmap["wsse"], nsmap=nsmap)
key_iden_ref = E("KeyIdentifier", ValueType="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLID")
key_iden_ref.text = "_"+refID
security_token_reference = E.SecurityTokenReference(key_iden_ref)
security_token_reference.attrib['TokenType'] = 'http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0'
    
no_nsE = ElementMaker(namespace=nsmap["dsig"], nsmap=nsmap)
key_info = no_nsE.KeyInfo(security_token_reference)
securityTag.insert(0,etree.fromstring(timestamp))

# Sign the timestamp
signedXml = XMLSigner(method=signxml.methods.detached, c14n_algorithm="http://www.w3.org/2001/10/xml-exc-c14n#").sign(soap_etree, key=self.key, cert=self.cert, key_info=key_info, reference_uri=["#_0","#_1"], always_add_key_value=False)
    
    
verified_data = XMLVerifier().verify(signedXml, x509_cert=self.cert).signed_xml
securityTag.append(signedXml)

return etree.tostring(soap_etree, encoding='utf-8', xml_declaration=False)

If you feel other code is needed to help solve the issue, happy to edit and include in the post.


Solution

  • We discovered that the timestamp was setup incorrectly. We needed to fix it so be element based and not string manipulation.

        # Create the timestamp element
        time_map = {"wsu": "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"}
        time_created = self.issued_at.strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z'
        time_expires = (self.issued_at + timedelta(hours=1)).strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z'
        
        t_E = ElementMaker(namespace=time_map["wsu"], nsmap=time_map)
        created = t_E("Created")
        created.text = time_created
        expires = t_E("Expires")
        expires.text = time_expires
        timestamp = t_E.Timestamp(created, expires, {QName(time_map["wsu"], "Id"): "_1"})
        securityTag.insert(0,timestamp)