3

On March 10th, 2018, Canada Post changed the way plugins connect to their API.

The Magento plugin - Demac Canada Post Shipping - stopped working and started to show error "Cannot connect to host".

I decided to add solution on Stackoverflow, because Canada Post forum is not a public forum and requires login to see the solutions.

NOTE FROM OTHERS: The solution posted below does not seem to work to fix the backend issue to be able to generate a manifest.

NOTE FROM ME: we dont use the manifest function, so I did not test it. I believe that function was always broken on Demac plugin and was throwing 400 from time to time. If anybody has a solution let me know.

4 Answers 4

2

Due to lack of rep, I can't comment on Kalvin Klien's solution.

Demac has released an update that unpins the SSL Certificate. This update can be acquired from Canada Post, or if you're a developer (which, by looking at this I assume you are), you can implement the following changes:

EDIT: app/code/community/Demac/CanadaPost/Model/Adapter/Abstract.php

FIND:

    $cert = Mage::getModuleDir('etc', 'Demac_CanadaPost') . DS . 'cert' . DS . 'cacert.pem';

    $client_options = array(
        'location' => $location,
        'features' => SOAP_SINGLE_ELEMENT_ARRAYS,
        'trace' => 1,
        'stream_context' => stream_context_create(
            array(
                'http' => array(
                    'protocol_version' => 1.0,
                ),
            )
        ),
    );

    if ($this->getConfigData('mode') != 'development')
    {
        $opts = array(
            'ssl' => array(
                'verify_peer'=> true,
                'cafile' => $cert,
                'peer_name' => $this->_location
            ),
            'http' => array(
                'protocol_version' => 1.0,
            ),
        );
        $ctx = stream_context_create($opts);

        $client_options['stream_context'] = $ctx;
    }

    $client = new SoapClient($wsdl, $client_options);

REPLACE WITH:

    $client_options = array(
        'location' => $location,
        'features' => SOAP_SINGLE_ELEMENT_ARRAYS,
        'trace' => 1,
        'stream_context' => stream_context_create(
            array(
                'ssl' => array(
                    'verify_peer'=> true,
                    'peer_name' => $this->_location
                ),
                'http' => array(
                    'protocol_version' => 1.0,
                ),
            )
        ),
    );

    $client = new SoapClient($wsdl, $client_options);
19
  • I already posted Demac solution in accepted answer: demacmedia.com/… I have asked Canada Post on how they want us to implement this and they said to use chain validation which uses Certificate Authority verification - which I believe is already implemented in Demac's solution already. I just didn't add an up date to the original post, which is what I'm going to do now :) Jun 22, 2018 at 20:47
  • @KalvinKlien The changes in affect as of today (June 25th), there is a new certificate for the api endpoints. The original solution from Demac (Forcing HTTP protocol version 1.0) was for the infrastructure changes on the CanadaPost side. The solution I posted here removes the certificate requirement, thus allowing Canada Post to change their cert as often as they'd like without updates needed on the Magento side
    – Stoyvo
    Jun 25, 2018 at 13:25
  • 1
    @KalvinKlien Are you sure it's connecting with TLS 1.2? Though their dev server should enforce TLS 1.2, you can always try adding the option (crypto_method): 'stream_context' => stream_context_create(array( 'ssl' => array( 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT, ) )
    – Stoyvo
    Jun 25, 2018 at 19:43
  • 1
    @KalvinKlien Due to rep, cannot comment on your new solution. However, try removing the SSL cert (cafile) from the array. We're seeing success without it, but it does require the HTTP 1.1 protocol.
    – Stoyvo
    Jun 26, 2018 at 17:57
  • 1
    @KalvinKlien I see your comment about Manifests. One of the WSDL urls was marked "https" when it should be "http", this resolved our Manifests issues.
    – Stoyvo
    Jul 14, 2018 at 4:02
4

Demac has posted their own solution here:

https://www.demacmedia.com/important-new-patch-magento-canada-post-application/

I have not tested it, but do plan to install it later today (deployment on Friday??? ooooo)

PS: I just received it from Canada Post rep.

UPDATE: just tested it and it's working well. Thanks Demac!

UPDATE (June 13, 2018): this fix will stop working because Canada Post is planning to update the certificates. They suggest using a different way of checking SSL certificate.enter image description here

I will see if Demac has any solutions for how to change Certificate Pinning that is currently being used in the plugin.

UPDATE: I have asked Canada Post to comment on how exactly they want us to do validation and they mentioned chain "validation" using a PEM file with Certificate Authority certificates.

You can find this answer here: https://forums.canadapost.ca/forum/posts/list/134221.page

// SSL Options    
$opts = array('ssl' =>    
    array(    
        'verify_peer'=> true,    
        'cafile' => realpath(dirname($_SERVER['SCRIPT_FILENAME'])) . ‘[PATH TO YOUR TRUSTED CERTIFICATE AUTHORITY PEM FILE]',    
        ‘peer_name' => $hostName    
    ),    
            'http' => array(    
                        'protocol_version' => 1.0,    
            ),    
);    
$ctx = stream_context_create($opts);  

I looked at Demac's update and confirmed they are already using chain validation in it already.

UPDATE:

I copied over some code from sample PHP code from Canada Post: https://www.canadapost.ca/cpo/mc/assets/zip/developers/samples/CPCWS_SOAP_PHP_Samples.zip

Here is the interpretation of that code in Demac plugin:

// SSL Options
$opts = array('ssl' =>
        array(
            'verify_peer'=> false,
            'cafile' => $cert,
            'CN_match' => $this->_location,
            'stream_context' => stream_context_create(
                    array( 'ssl' => 
                        array( 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT
                            ) 
                        )
                    )
        )
);

$ctx = stream_context_create($opts);    
$client = new SoapClient($wsdl,array('location' => $location, 'features' => SOAP_SINGLE_ELEMENT_ARRAYS, 'stream_context' => $ctx));

It replaced:

$client_options = array(
    'location' => $location,
    'features' => SOAP_SINGLE_ELEMENT_ARRAYS,
    'trace' => 1,
    'stream_context' => stream_context_create(
        array(
            'http' => array(
                'protocol_version' => 1.0,
            ),
        )
    ),
);

if ($this->getConfigData('mode') != 'development')
{
    $opts = array(
        'ssl' => array(
            'verify_peer'=> true,
            'cafile' => $cert,
            'peer_name' => $this->_location
        ),
        'http' => array(
            'protocol_version' => 1.0,
        ),
    );
    $ctx = stream_context_create($opts);

    $client_options['stream_context'] = $ctx;
}

$client = new SoapClient($wsdl, $client_options);

I also copied PEM file from \CPCWS_SOAP_PHP_Samples\third-party\cert folder of the Sample PHP Code.

Thanks to @Stoyvo for:

 array( 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT

^^^ that helped to resolve logged in / logged out issue I was experiencing on production server.

UPDATE: the back-end is still untested, it is broken and cannot create labels.

UPDATE (WORKING VERSION):

I have no idea how I did this, but this is the latest from production server(production keys) with working label creation:

$client_options = array(
    'location' => $location,
    'features' => SOAP_SINGLE_ELEMENT_ARRAYS,
    'trace' => 1,
    'stream_context' => stream_context_create(
        array(
            'http' => array(
                'protocol_version' => 1.0,
            ),
        )
    ),
);

if ($this->getConfigData('mode') != 'development')
{
    $opts = array(
        'ssl' => array(
            'verify_peer'=> true,
            'cafile' => $cert,
            'peer_name' => $this->_location
        ),
        'http' => array(
            'protocol_version' => 1.0,
        ),
    );
    $ctx = stream_context_create($opts);

    $client_options['stream_context'] = $ctx;
}

$client = new SoapClient($wsdl, $client_options);
5
  • Hello @Kalvin, Thank you for your solution and have you solved the issue of the creation of the label?
    – Abbas
    Jun 28, 2018 at 0:58
  • When I check the log file. I am getting the error 'Error Fetching http body, No Content-Length, connection closed or chunked data'
    – Abbas
    Jun 28, 2018 at 5:58
  • I have added int the opts 'http' => array( 'protocol_version' => 1.0, 'header' => "Transfer-Encoding: chunked\r\n", ) but still not able to create the labels.
    – Abbas
    Jun 28, 2018 at 6:24
  • I have debuged it more and exact error is coming from Demac/CanadaPost/Model/Adapter/Artifact.php Line 30
    – Abbas
    Jun 28, 2018 at 7:38
  • @Abbas I updated the answer. Jun 29, 2018 at 15:56
1

Here is a solution for this issue.

There are 2 steps you need to do:

STEP 1:

/app/code/community/Demac/CanadaPost/Model/Adapter/Abstract.php

Change this code from:


if ($this->getConfigData('mode') != 'development') 
{ 
  $opts = array('ssl' => 
   array( 
    'verify_peer'=> true, 
    'cafile' => $cert, 
    'CN_match' => $this->_location 
  )); 

  $ctx = stream_context_create($opts); 

  $client_options['stream_context'] = $ctx; 

} 

change to:


if ($this->getConfigData('mode') != 'development') 
{ 
   $opts = array('ssl' => 
      array( 
          'verify_peer'=> true, 
          'cafile' => $cert, 
          'CN_match' => "www.canadapost.ca" 
   ), 
 'http' => array( 
          'protocol_version' => 1.0, 
  ), 
  ); 

  $ctx = stream_context_create($opts); 

  $client_options['stream_context'] = $ctx; 
} 

STEP 2:

/app/code/community/Demac/CanadaPost/etc/cert/cacert.pem

Then follow steps from:

https://forums.canadapost.ca/forum/posts/list/131224.page


1) Enter the URL https://soa-gw.canadapost.ca (using Firefox), you will be shown an XML 404 error, that is to be expected.

2) Look at the address bar of the browser, there should be a green lock icon to the left, click on the icon.

3) A dropdown should appear with two items, the first item should be the site name with a right arrow. Click on the right arrow and then click on “More Information”.

4) A popup windows should appear, click on “View Certificate”.

5) The “Certificate Viewer” window should open, click on the “Details” tab.

6) In the “Certificate Hierarchy” section, highlight the last/lowest item, which should be www.canadapost.ca and then click on “Export…”.

7) You can then choose to save the certificate in the format compatible with your system, usually PEM works.


BUT you need to export ALL 3 of the certificate levels:

GeoTrust Global CA GeoTrust SSL CA - G3 www.canadapost.ca

and put the contents into cacert.pem

Then upload to your server here: /app/code/community/Demac/CanadaPost/etc/cert/cacert.pem

I tested only the front-end, but so far seems to work.

EDIT: added to cover the label generation issue

'http' => array( 
    'protocol_version' => 1.0, 
 ), 

I believe this is due to PHP 5.3 requiring this parameter. I'm running on 5.4 so not sure why it is still required.

3
  • Awesome and quick solution. Thank you very much for a brilliant solution.
    – Abbas
    Mar 16, 2018 at 11:37
  • One question why I can not open the link forums.canadapost.ca/forum/posts/list/131224.page
    – Abbas
    Mar 16, 2018 at 11:40
  • 1
    you need Canada Post login / account. That's why I posted my solution here. Please see my post below - Demac has posted a patch for the plugin. Mar 16, 2018 at 16:17
0

I have replaced the Artifact code

In file Demac/CanadaPost/Model/Adapter/Artifact.php function getArtifact replace the code

$client = $this->createSOAPClient('artifact');

        $request = array(
            'get-artifact-request' => array(
                'locale'            => $this->_lang,
                'mailed-by'         => $this->_shipperNum,
                'artifact-id'       => $artifactId
            )
        );
        if($this->getConfigData('include_platformid'))
        {
            $request['get-artifact-request']['platform-id'] = $this->_platformId;
        }
        $result = $client->__soapCall('GetArtifact',$request, NULL);
        return $result;

with the following code

 $wsdl = Mage::getModuleDir('etc', 'Demac_CanadaPost') . DS . 'wsdl' . DS . 'artifact.wsdl';

    $hostName = 'soa-gw.canadapost.ca';

    // SOAP URI
    $location = 'https://' . $hostName . '/rs/soap/artifact';

    // SSL Options
    $cert = Mage::getModuleDir('etc', 'Demac_CanadaPost') . DS . 'cert' . DS . 'cacert.pem';
    $opts = array('ssl' =>
        array(
            'verify_peer'=> true,
            'cafile' => $cert,
            'peer_name' => $hostName
        ),
        'http' => array(
            'protocol_version' => 1.0,
        ),
    );

    $ctx = stream_context_create($opts);
    $client = new SoapClient($wsdl,array('location' => $location, 'features' => SOAP_SINGLE_ELEMENT_ARRAYS, 'stream_context' => $ctx));

    // Set WS Security UsernameToken
    $WSSENS = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd';
    $usernameToken = new stdClass();
    $usernameToken->Username = new SoapVar($this->_userId, XSD_STRING, null, null, null, $WSSENS);
    $usernameToken->Password = new SoapVar($this->_password, XSD_STRING, null, null, null, $WSSENS);
    $content = new stdClass();
    $content->UsernameToken = new SoapVar($usernameToken, SOAP_ENC_OBJECT, null, null, null, $WSSENS);
    $header = new SOAPHeader($WSSENS, 'Security', $content);
    $client->__setSoapHeaders($header);


    $mailedBy = $this->_shipperNum;
    // Execute Request
    $result = $client->__soapCall('GetArtifact', array(
        'get-artifact-request' => array(
            'locale'            => $this->_lang,
            'mailed-by'         => $mailedBy,
            'artifact-id'       => $artifactId,
            'page-index'        => '0',
            'platform-id' => $this->_platformId
        )
    ), NULL, NULL);

    return $result;

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.