I've been reading up A LOT on everything related with XML signatures, and it's a painful, painful world as it is today.
I'm able to make a SOAP request to a remote WSDL (https://tbk.orangepeople.cl/WSWebpayTransaction/cxf/WSWebpayService?wsdl), and I'm supposed to validate their responses as well (naturally!), however I can't seem to figure out how to properly verify the XML SignatureValue.
I also couldn't verify this signature on a Terminal (MAC OSX 10.11.1) using openssl (OpenSSL 0.9.8zg 14 July 2015), although I'm not confident enough that my file inputs and commands were 100% right to give a positive result.
The entity which provided me with the public certificate assured me this is the correct one, so no luck there. I also created a SoapUI project properly configured with Outgoing & Incoming WSS to back up this claim.
For anyone who's curious, I'm using Signer with Nokogiri::XML::Builder to produce the signed XML for my request, Savon to process the requests themselves, and in the code below I'm using Akami::WSSE::VerifySignature separately to process the response.
Here's what I have so far:
XML(response):
<?xml version="1.0"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" soap:mustUnderstand="1">
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="SIG-1426">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
<ec:InclusiveNamespaces xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="soap"/>
</ds:CanonicalizationMethod>
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<ds:Reference URI="#id-1425">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
<ec:InclusiveNamespaces xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList=""/>
</ds:Transform>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<ds:DigestValue>o6RiSp7nGmMWf01mYh3FGNpK80A=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>irR3w9BKEf64eN9FJRnCBqHEj5kWZu+Bn5QlNrBTxp/tbGxgU1ViTLFNu6kQJFfxEFB1AoN7Ks5c
QKOK5eBwbA3j6486XQPMoYOvOWxtr73PS09LVFp6P1aZ0y5wHTf7dQ89GJ35Zb0JemvkeSN0XWQt
X3USE7A6z4t04jx95FX+Me6dTAFyf3ealyISfrfYkIsasqU6W/orhRgyKunq6N1aTZ7HmphaSgtu
EEncUiKS6aEdvD0NjwKWXlTr/5NT5BQ7T9cmWS00QYjlRlF2SGww44SAehNojwqFy40SEpuVPVJv
DH9GH4ITsy72DeY/PXkHkaEpDIPM32EUfobE1zRM0zwPLGQysGcELSRfzAWR9QWO1NmPecABymZ8
qMNQRxUK5MkX2S/O29Jpmq/8q/VWQQhnMmj6YdL8NAE1RmjH11wNXdWHRM+3iLndMk5EpiDtFZSo
fDmtNBWhiBzE3g/OZYBbZVM9MvQjMj0x2aK8rZK/qRylbVjhYaJI8hEOiAJZeAHErwuynjP01ONI
bXeyqZik5x54zamdsQs5UlXaGYRAqVInKr1j4+trJTstAqP7C22cEUULyKO2jkBj/wWpRhucjbJH
4XXHuVHQWP2myvnImaOHuAC8TFSCsV6/hOx206G4Yd1cyHSfNM3XEQURVlOaO0an582OlFxFvnU=</ds:SignatureValue>
<ds:KeyInfo Id="KI-FBCFA2CE61C97ADD4A14467251913442138">
<wsse:SecurityTokenReference wsu:Id="STR-FBCFA2CE61C97ADD4A14467251913442139">
<ds:X509Data>
<ds:X509IssuerSerial>
<ds:X509IssuerName>CN=10,OU=ExperTI,O=ExperTI,L=Santiago,ST=Santiago,C=CL,1.2.840.113549.1.9.1=#16116a636572646140657870657274692e636c</ds:X509IssuerName>
<ds:X509SerialNumber>1401281826</ds:X509SerialNumber>
</ds:X509IssuerSerial>
</ds:X509Data>
</wsse:SecurityTokenReference>
</ds:KeyInfo>
</ds:Signature>
</wsse:Security>
</soap:Header>
<soap:Body xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="id-1425">
<ns2:initTransactionResponse xmlns:ns2="http://service.wswebpay.webpay.transbank.com/">
<return>
<token>e30965b17f7854b21bf77f313cb9a117214e62fb5d98c6ff2580e859d013ed32</token>
<url>https://tbk.orangepeople.cl/filtroUnificado/initTransaction</url>
</return>
</ns2:initTransactionResponse>
</soap:Body>
</soap:Envelope>
Certificate (certificate_server.crt):
-----BEGIN CERTIFICATE-----
MIIFhTCCA20CBFOF3SIwDQYJKoZIhvcNAQEFBQAwgYYxIDAeBgkqhkiG9w0BCQEW
EWpjZXJkYUBleHBlcnRpLmNsMQswCQYDVQQGEwJDTDERMA8GA1UECAwIU2FudGlh
Z28xETAPBgNVBAcMCFNhbnRpYWdvMRAwDgYDVQQKDAdFeHBlclRJMRAwDgYDVQQL
DAdFeHBlclRJMQswCQYDVQQDDAIxMDAeFw0xNDA1MjgxMjU3MDZaFw0xNjA1Mjcx
MjU3MDZaMIGGMSAwHgYJKoZIhvcNAQkBFhFqY2VyZGFAZXhwZXJ0aS5jbDELMAkG
A1UEBhMCQ0wxETAPBgNVBAgMCFNhbnRpYWdvMREwDwYDVQQHDAhTYW50aWFnbzEQ
MA4GA1UECgwHRXhwZXJUSTEQMA4GA1UECwwHRXhwZXJUSTELMAkGA1UEAwwCMTAw
ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCX1t11QZSgfodg+NPwKdof
lakbpxsFmCvjbY3Vpp6/bTv56XIZe/Z3gBlU4zeGslEaqzs1k4cGAcdZPHxSMydC
oLxmyXpdS2hVFUZTYAeanXHhQzUKmNlgYj3uObprPEEQzD/vEuqpwz2/ZGyaHsWs
exu9aeuLAUUSNne0yqobrzRfEp2FqCp4sJB80cXgGfPA4Cr5jROHqUi8sVWpWRy5
ai5ZaiXRPo3YKF1336twuw9lRS3cRtJh9/AoJElGT7G7BMDpxyTTa08y1iRliDGu
mwWivQMHoKqEs0lCtF9Uz8cFdmiIoRPTt6McpmLoapL9v19xjNnim4lke6DPvtcg
uato7T+frDqA5Cj5GRP/8jbe90Y+YjHuJTkw+fkV6gDTRmJ3wCWDIw/07aY6nZ+H
24Imu6N2YBsMEsa8j9OW04mNgtppRC4dFBh0FIKXC35kJgN38y+6T7MsQThX1XZS
SlK0FygJJADVGelmxtsrRRfnp4yLYRjwdkRGExRjVs/+fkOyKI+fX0o68z6MEDyA
5epVHpgwJ/Yz3Lo7cgXy0hO5a/MfZc0Y0ofb29g8sJMJ8j/SSR85i4pFxudn+HH0
SmkkzE/P10adF/X+pqjscOE+aXwnX09lUUQ9TIlpYaR3hUhONsuefYJ2sHz2z+vt
K5btQwN7u9+QeXLgb20PMwIDAQABMA0GCSqGSIb3DQEBBQUAA4ICAQAqCepOSFi7
8d6lKTfWpMuj5ygpplBa3Bj81AKkNfSGDN7zkNX2sCbqn9aEjniIPtldG6I2fgCs
FYTZE9oEPNiuEuH7PNtAjTExIUi9Jzm7bqjCdSs7Ioek1cPePgst017zJ6NSTkaM
r7pUDaS3855xO92uaJskppAeegwz9Dv3d5wY+wVViqUki4pZyxa95IvBJz4NR1Xr
cO6XtUUT9M0wbd0jAkRp7DPQfkihZj8vLSvlUYTRdlF2swIBE/ME2T3NCa0/kt1c
IA1Aq/zn7t0yKvyaJ/O//LrHA1Lfa/uC61O/9P3t+eXDsYl73CeGQdSYZp2DAZmA
Ek3tzwhFa6HR+POIo8MptWMT3DQ0ISHH+EW1Xp8GHIGsk2ELsXuA6XTNwpfz9yvl
9d7IGsq4cdX88cNUCbXm4tj7F3s6i8pNWeCImaYcXKGCBdsLM+lbmqbuV7o3d1Ei
efbR1TQkCxRBNCMUI0pF7NW8PvY3QER9/jEnN52SX+tuQRVdpgl+PyTdSASr4FhV
+HHmgeOgeOewXDnZ7aA1F6f8+CY8Niv4FGZIAptdxTqdynY4nUy/wFowBouO3LEF
6nIcQ3Jx1pDXoEmcLa03JaL7qQNSHyqSe/YEl8E5fdDr7vApzw9pvpAjj1aslidL
bNd4l1YGlL2vbGsIXZlbdBLiblXRi78AyQ==
-----END CERTIFICATE-----
Ruby source code (a little modified, OpenSSL::Digest::SHA1.new is used instead of a digester mapping method to simplify):
# load certificate
certificate_server = OpenSSL::X509::Certificate.new(File.read("certificate_server.crt"))
# CERT INFORMATION IS VERIFIED SUCCESSFULLY HERE
# DIGEST IS VERIFIED SUCCESSFULLY HERE
# certificate signature initialization
response_signature = Akami::WSSE::VerifySignature.new(response)
document = response_signature.document
namespaces = response_signature.namespaces
# retrieve Signature data & digest value
data = document.at_xpath('//wse:Security/ds:Signature/ds:SignedInfo', namespaces).canonicalize(Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0)
signature = Base64.decode64(document.at_xpath('//wse:Security/ds:Signature/ds:SignatureValue', namespaces).text)
# check if Signature is valid
return false unless certificate_server.public_key.verify(OpenSSL::Digest::SHA1.new, signature, data)
true
This only fails in the last line:
certificate_server.public_key.verify(signature_digester, signature, data)
If anyone can spot the error/bug/missing code, or maybe provide a working example, I would be very grateful.
Had to verify this by making new requests using PHP xmlseclibs, but it turned out to be missing '\n's on the signature field and a missing namespace from the root Envelope XML tag on the data field of the following line:
certificate_server.public_key.verify(signature_digester, signature, data)
For this case in particular, the element SignedInfo in data needed to be verified while also having the following namespace, which was not included by the 'canonicalize' method:
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
Hope this helps someone in the future!