I ran into a problem recently with a SOAP call that was being made from one of our Ruby applications. Apparently, something had changed with the SSL certificate at the service endpoint, as we were receiving the following error:
at depth 0 – 20: unable to get local issuer certificate
OpenSSL::SSL::SSLError: certificate verify failed
Now, nothing had changed in our code, which was very simple — all of the details about service location and methods available are in the service’s WSDL file. The method that was blowing up simply created the connection to an HTTPS SOAP service and then called a method on the endpoint, like so:
def status # Read WSDL and create driver driver = SOAP::WSDLDriverFactory.new(WSDL_URI).create_rpc_driver # SOAP headers, basic auth credentials, etc. driver.headerhandler << HeaderHelper.new # Make SOAP call driver.GetStatus end
After doing some research, it was pretty clear that we were getting the error because the certificate being presented by the server was issued by someone that wasn’t in our trusted store. More specifically, it was signed using an intermediate certificate (or “chained” certificate) that was then linked to a trusted certificate.
There are two ways to fix this problem. The first way is the fastest, which is a plus if you need to get this issue solved immediately, or you’re only writing a test wrapper anyway. Add the following line:
def status # Read WSDL and create driver driver = SOAP::WSDLDriverFactory.new(WSDL_URI).create_rpc_driver driver.options['protocol.http.ssl_config.verify_mode'] = OpenSSL::SSL::VERIFY_NONE # SOAP headers, basic auth credentials, etc. driver.headerhandler << HeaderHelper.new # Make SOAP call driver.GetStatus end
By setting VERIFY_NONE, you’re telling the SSL client to ignore the certificate sent by server. The issue here is that, well, you’re ignoring the certificate sent by the server. As any security advocate will tell you, encryption without authentication is no security at all.
The longer, but correct, way to fix this issue is to figure out what intermediate certificates you are missing and then include them in your web application. This is often as simple as visiting the issuer’s HTTPS website and examining the certificate chain. (In Firefox: double-click the lock icon, click View Certificate and then select the Details tab.)
In our case, I was able to do exactly that. I exported all the certificates below the root cert into the lib folder and added the following lines:
def status # Read WSDL and create driver driver = SOAP::WSDLDriverFactory.new(WSDL_URI).create_rpc_driver driver.options['protocol.http.ssl_config.ca_file'] = 'lib/signing_cert.pem' driver.options['protocol.http.ssl_config.ca_file'] = 'lib/intermed_cert.pem' # SOAP headers, basic auth credentials, etc. driver.headerhandler << HeaderHelper.new # Make SOAP call driver.GetStatus end
The call now worked perfectly. Note that you can set the ca_file option as many times as you need to include multiple certificate files, although generally you should only need one, as typically an issuer will use only one intermediate certificate.
Note that this code doesn’t do any validation of the server’s certificate itself. If you’d like to do this as well, please check out this SOAP4R wiki entry.