WCF SSL Service with PHP

Datetime:2016-08-23 04:35:29         Topic: PHP  WCF          Share        Original >>
Here to See The Original Article!!!

We had a task recently that required our team – me with my colleague Ahmad to write php code to integrate with existing WCF webservice that includes attaching SSL certificates to requests. The application used to integrate with third-party banking system using a form of B2B web service.

In the following post, the main steps we used to write PHP code compatible with WCF:

Existing .net application

The original code was written in C# using WCF webservice over SOAP, it was attaching SSL certificate in PFX format -that includes all certificate chain -, and a separate private key file (as .key format).

PFX format ( PKCS#12 ) is binary format which is usually used in windows to export/import SSL certificates; it stores certificate, intermediate certificates – if there is any – and private key in one file that can be encrypted and signed.

In original C# code, they defined a class that inherits SoapHttpClientProtocol which was used to add SSL certificates to soap request.

Generating SSL files

The existing code was attaching ssl certificate in .pfx format file, so I converted it to .pem format (which is the standard format for openssl) and extracted the key as separate file using openssl commands as following:

openssl pkcs12 -in certificate.pfx -nocerts -out key.pem -nodes
openssl pkcs12 -in certificate.pfx -nokeys -out certificate.pem
openssl rsa -in key.pem -out certificate.key

Those commands will generate public ssl certificate (*.pem) and private ssl key (*.key) file.

To make sure the generated certificates are correct for php, I wrote this basic function to test:

function validatePublicPrivateKeys($public_key_file, $private_key_file) {
    $public = openssl_pkey_get_public(file_get_contents($public_key_file)); 
    $public_error = openssl_error_string();
    if(!empty($public_error)){
        echo "Error in public key:".$public_error."\n";
    }else{
        echo "public key is valid\n";
    }

    $private = openssl_pkey_get_private(file_get_contents($private_key_file), 'passphrase-here');
    $private_error = openssl_error_string();
    if(!empty($private_error)){
        echo "Error in private key:".$private_error;
    }else{
        echo "private key is valid\n";
    }
}

Extending SoapClient Class

Normally SSL certificate can be used in php SOAP request by setting `local_cert` parameter in SoapClient Constructor. however I found this option somehow limited, because there is no ability for a private key to be attached as separate file in the request.

so what we did is to extend soap client and override __doRequest method to be based on curl to send soap request as HTTP message as following:

class MySoapClient extends \SoapClient{

      public function __doRequest($request, $location, $action, $version, $one_way = FALSE) {
            $curl = curl_init($location);
            //setting curl options and data here...
            //...
            
    }
}

Depending in SOAP version, override to curl header values is needed, in my case version used is SOAP 1.2 , so the headers will be as following:

$curl = curl_init($location);
    $headers = array(
        "Content-type: test/xml;charset=\"utf-8\";action=\"" . $location . '/' . $action . "\"",
        "Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
        "Cache-Control: no-cache",
        "Pragma: no-cache",
        "Content-length: " . strlen($request),
    ); 
        
    curl_setopt($curl, CURLOPT_HEADER, TRUE);
    curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);

Attaching SSL certificates

I set ssl certificate public file, key file – generated earlier – and passphrase to curl request as following:

curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);
        
    curl_setopt($curl, CURLOPT_SSLKEYPASSWD, 'passphrase');
    curl_setopt($curl, CURLOPT_SSLKEY, 'your-private-key.key');
    curl_setopt($curl, CURLOPT_SSLCERT, 'your-certificate.pem');

In case curl error “Peer certificate cannot be authenticated with known CA certificates” appeared – which is usually happens in windows- you shall download CA certificate bundle from Mozilla – or other trusted source -, and save it to your system and set CURLOPT_CAINFO option in curl:

curl_setopt($curl, CURLOPT_CAINFO, “C:\full-path-to\cacert.pem”);

or better set it globally in your php.ini:

curl.cainfo=c:\full-path-to\cacert.pem

The final class implementation will be like the following snippet:

class MySoapClient extends \SoapClient{
      public $certificate_ssl_location = "/full/path/to/your-ssl-public-certificate";
      public $private_key_location = "/full/path/to/your-ssl-private-certificate";
      public $ssl_passphrase = "password";
      public $ca_cert_file = "/full/path/to/ca-file"
      
      public function __doRequest($request, $location, $action, $version, $one_way = FALSE) {

        // Call via Curl and use the timeout
        $curl = curl_init($location);

        $headers = array(
            "Content-type: test/xml;charset=\"utf-8\";action=\"" . $location . '/' . $action . "\"",
            "Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
            "Cache-Control: no-cache",
            "Pragma: no-cache",
            "Content-length: " . strlen($request),
        ); //SOAPAction: your op URL

        curl_setopt($curl, CURLOPT_VERBOSE, TRUE);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
        curl_setopt($curl, CURLOPT_POST, TRUE);
        curl_setopt($curl, CURLOPT_POSTFIELDS, $request);
        curl_setopt($curl, CURLOPT_HEADER, TRUE);
        curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);

        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);
        
        curl_setopt($curl, CURLOPT_SSLKEYPASSWD, $this->ssl_passphrase);
        curl_setopt($curl, CURLOPT_SSLKEY, $this->private_key_location);
        curl_setopt($curl, CURLOPT_SSLCERT, $this->certificate_ssl_location);
        curl_setopt($curl, CURLOPT_CAINFO, $this->ca_cert_file);

        $response = curl_exec($curl);

        if (curl_errno($curl)) {
            throw new Exception(curl_error($curl));
        }
        curl_close($curl);

        $soap_start = strpos($response, "<soapenv:Envelope");
        $soap_response = substr($response, $soap_start);

        if (!$one_way) {
            return $soap_response;
        }
    }
}

you can call soap request normally by initializing object from MySoapClient class (instead of native SoapClient class)

and now the integration with secured WFC web service works fine








New

Put your ads here, just $200 per month.