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.

1

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);
| improve this answer | |
  • 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 :) – Kalvin Klien Jun 22 '18 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 '18 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 '18 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 '18 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 '18 at 4:02
3

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);
| improve this answer | |
  • Hello @Kalvin, Thank you for your solution and have you solved the issue of the creation of the label? – Abbas Jun 28 '18 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 '18 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 '18 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 '18 at 7:38
  • @Abbas I updated the answer. – Kalvin Klien Jun 29 '18 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.

| improve this answer | |
  • Awesome and quick solution. Thank you very much for a brilliant solution. – Abbas Mar 16 '18 at 11:37
  • One question why I can not open the link forums.canadapost.ca/forum/posts/list/131224.page – Abbas Mar 16 '18 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. – Kalvin Klien Mar 16 '18 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;
| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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