PayPal IPN acknowledgements failing with SSL routines:SSL3_READ_BYTES:sslv3 alert handshake failure

后端 未结 8 574
一生所求
一生所求 2020-12-01 12:21

With no changes on our side and perhaps related to POODLE/SSL3 our PayPal API call to PPIPNMessage::validate is now failing with.

SSL routines:SSL3_READ_BYTE         


        
相关标签:
8条回答
  • 2020-12-01 12:53

    This is the same problem as Error 0x1408F10B: "SSL3_GET_RECORD:wrong version number" with PayPal SDK

    The version of PayPal API we are using hard codes CURLOPT_SSLVERSION to 3.

    Our fix is to insert this before any PayPal calls.

    PPHttpConfig::$DEFAULT_CURL_OPTS[CURLOPT_SSLVERSION] = 4;
    
    0 讨论(0)
  • 2020-12-01 12:54

    For Me None of these things mentioned in the other answers worked but, I was able to figure out the fix and it's along the same lines, but took me a while to find out what file the sdk I was using put its curl config as there was no ipn listener file and that "PPHttpConfig" would give me a fatal error

    so I found this is the listener file they are using now:

    PayPal-PHP-SDK/paypal/rest-api-sdk-php/lib/PayPal/Core/PayPalHttpConfig.php

    I found inside

    CURLOPT_SSLVERSION => 1

    I changed it to:

    CURLOPT_SSLVERSION => 4

    and that fixed it for me.

    0 讨论(0)
  • 2020-12-01 12:57

    I had the same problem... Just change the following line on the ipnlistener.php

    from:

    curl_setopt($ch, CURLOPT_SSLVERSION, 3);
    

    to:

    curl_setopt($ch, CURLOPT_SSLVERSION, 4);
    

    ipnlistener.php

    <?php
    /**
    * PayPal IPN Listener
    *
    * A class to listen for and handle Instant Payment Notifications (IPN) from
    * the PayPal server.
    *
    * https://github.com/Quixotix/PHP-PayPal-IPN
    *
    * @package PHP-PayPal-IPN
    * @author Micah Carrick
    * @copyright (c) 2012 - Micah Carrick
    * @version 2.1.0
    */
    class IpnListener {
    
        /**
    * If true, the recommended cURL PHP library is used to send the post back
    * to PayPal. If flase then fsockopen() is used. Default true.
    *
    * @var boolean
    */
        public $use_curl = true;
    
        /**
    * If true, explicitly sets cURL to use SSL version 3. Use this if cURL
    * is compiled with GnuTLS SSL.
    *
    * @var boolean
    */
        public $force_ssl_v3 = true;
    
        /**
    * If true, cURL will use the CURLOPT_FOLLOWLOCATION to follow any
    * "Location: ..." headers in the response.
    *
    * @var boolean
    */
        public $follow_location = false;
    
        /**
    * If true, an SSL secure connection (port 443) is used for the post back
    * as recommended by PayPal. If false, a standard HTTP (port 80) connection
    * is used. Default true.
    *
    * @var boolean
    */
        public $use_ssl = true;
    
        /**
    * If true, the paypal sandbox URI www.sandbox.paypal.com is used for the
    * post back. If false, the live URI www.paypal.com is used. Default false.
    *
    * @var boolean
    */
        public $use_sandbox = false;
    
        /**
    * The amount of time, in seconds, to wait for the PayPal server to respond
    * before timing out. Default 30 seconds.
    *
    * @var int
    */
        public $timeout = 30;
    
        private $post_data = array();
        private $post_uri = '';
        private $response_status = '';
        private $response = '';
    
        const PAYPAL_HOST = 'www.paypal.com';
        const SANDBOX_HOST = 'www.sandbox.paypal.com';
    
        /**
    * Post Back Using cURL
    *
    * Sends the post back to PayPal using the cURL library. Called by
    * the processIpn() method if the use_curl property is true. Throws an
    * exception if the post fails. Populates the response, response_status,
    * and post_uri properties on success.
    *
    * @param string The post data as a URL encoded string
    */
        protected function curlPost($encoded_data) {
    
            if ($this->use_ssl) {
                $uri = 'https://'.$this->getPaypalHost().'/cgi-bin/webscr';
                $this->post_uri = $uri;
            } else {
                $uri = 'http://'.$this->getPaypalHost().'/cgi-bin/webscr';
                $this->post_uri = $uri;
            }
    
            $ch = curl_init();
    
                    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
                    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
                    curl_setopt($ch, CURLOPT_CAINFO,
                     dirname(__FILE__)."/cert/api_cert_chain.crt");
            curl_setopt($ch, CURLOPT_URL, $uri);
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $encoded_data);
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, $this->follow_location);
            curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_HEADER, true);
            curl_setopt($ch, CURLOPT_SSLVERSION, 4);
    
            if ($this->force_ssl_v3) {
                curl_setopt($ch, CURLOPT_SSLVERSION, 4); //Modified from 3 to 4
            }
    
            $this->response = curl_exec($ch);
            $this->response_status = strval(curl_getinfo($ch, CURLINFO_HTTP_CODE));
    
            if ($this->response === false || $this->response_status == '0') {
                $errno = curl_errno($ch);
                $errstr = curl_error($ch);
                throw new Exception("cURL error: [$errno] $errstr");
            }
        }
    
        /**
    * Post Back Using fsockopen()
    *
    * Sends the post back to PayPal using the fsockopen() function. Called by
    * the processIpn() method if the use_curl property is false. Throws an
    * exception if the post fails. Populates the response, response_status,
    * and post_uri properties on success.
    *
    * @param string The post data as a URL encoded string
    */
        protected function fsockPost($encoded_data) {
    
            if ($this->use_ssl) {
                $uri = 'ssl://'.$this->getPaypalHost();
                $port = '443';
                $this->post_uri = $uri.'/cgi-bin/webscr';
            } else {
                $uri = $this->getPaypalHost(); // no "http://" in call to fsockopen()
                $port = '80';
                $this->post_uri = 'http://'.$uri.'/cgi-bin/webscr';
            }
    
            $fp = fsockopen($uri, $port, $errno, $errstr, $this->timeout);
    
            if (!$fp) {
                // fsockopen error
                throw new Exception("fsockopen error: [$errno] $errstr");
            }
    
            $header = "POST /cgi-bin/webscr HTTP/1.1\r\n";
            $header .= "Host: ".$this->getPaypalHost()."\r\n";
            $header .= "Content-Type: application/x-www-form-urlencoded\r\n";
            $header .= "Content-Length: ".strlen($encoded_data)."\r\n";
            $header .= "Connection: Close\r\n\r\n";
    
            fputs($fp, $header.$encoded_data."\r\n\r\n");
    
            while(!feof($fp)) {
                if (empty($this->response)) {
                    // extract HTTP status from first line
                    $this->response .= $status = fgets($fp, 1024);
                    $this->response_status = trim(substr($status, 9, 4));
                } else {
                    $this->response .= fgets($fp, 1024);
                }
            }
    
            fclose($fp);
        }
    
        private function getPaypalHost() {
            if ($this->use_sandbox) return self::SANDBOX_HOST;
            else return self::PAYPAL_HOST;
        }
    
        /**
    * Get POST URI
    *
    * Returns the URI that was used to send the post back to PayPal. This can
    * be useful for troubleshooting connection problems. The default URI
    * would be "ssl://www.sandbox.paypal.com:443/cgi-bin/webscr"
    *
    * @return string
    */
        public function getPostUri() {
            return $this->post_uri;
        }
    
        /**
    * Get Response
    *
    * Returns the entire response from PayPal as a string including all the
    * HTTP headers.
    *
    * @return string
    */
        public function getResponse() {
            return $this->response;
        }
    
        /**
    * Get Response Status
    *
    * Returns the HTTP response status code from PayPal. This should be "200"
    * if the post back was successful.
    *
    * @return string
    */
        public function getResponseStatus() {
            return $this->response_status;
        }
    
        /**
    * Get Text Report
    *
    * Returns a report of the IPN transaction in plain text format. This is
    * useful in emails to order processors and system administrators. Override
    * this method in your own class to customize the report.
    *
    * @return string
    */
        public function getTextReport() {
    
            $r = '';
    
            // date and POST url
            for ($i=0; $i<80; $i++) { $r .= '-'; }
            $r .= "\n[".date('m/d/Y g:i A').'] - '.$this->getPostUri();
            if ($this->use_curl) $r .= " (curl)\n";
            else $r .= " (fsockopen)\n";
    
            // HTTP Response
            for ($i=0; $i<80; $i++) { $r .= '-'; }
            $r .= "\n{$this->getResponse()}\n";
    
            // POST vars
            for ($i=0; $i<80; $i++) { $r .= '-'; }
            $r .= "\n";
    
            foreach ($this->post_data as $key => $value) {
                $r .= str_pad($key, 25)."$value\n";
            }
            $r .= "\n\n";
    
            return $r;
        }
    
        /**
    * Process IPN
    *
    * Handles the IPN post back to PayPal and parsing the response. Call this
    * method from your IPN listener script. Returns true if the response came
    * back as "VERIFIED", false if the response came back "INVALID", and
    * throws an exception if there is an error.
    *
    * @param array
    *
    * @return boolean
    */
        public function processIpn($post_data=null) {
    
            $encoded_data = 'cmd=_notify-validate';
    
            if ($post_data === null) {
                // use raw POST data
                if (!empty($_POST)) {
                    $this->post_data = $_POST;
                    $encoded_data .= '&'.file_get_contents('php://input');
                } else {
                    throw new Exception("No POST data found.");
                }
            } else {
                // use provided data array
                $this->post_data = $post_data;
    
                foreach ($this->post_data as $key => $value) {
                    $encoded_data .= "&$key=".urlencode($value);
                }
            }
    
            if ($this->use_curl) $this->curlPost($encoded_data);
            else $this->fsockPost($encoded_data);
    
            if (strpos($this->response_status, '200') === false) {
                throw new Exception("Invalid response status: ".$this->response_status);
            }
    
            if (strpos($this->response, "VERIFIED") !== false) {
                return true;
            } elseif (strpos($this->response, "INVALID") !== false) {
                return false;
            } else {
                throw new Exception("Unexpected response from PayPal.");
            }
        }
    
        /**
    * Require Post Method
    *
    * Throws an exception and sets a HTTP 405 response header if the request
    * method was not POST.
    */
        public function requirePostMethod() {
            // require POST requests
            if ($_SERVER['REQUEST_METHOD'] && $_SERVER['REQUEST_METHOD'] != 'POST') {
                header('Allow: POST', true, 405);
                throw new Exception("Invalid HTTP request method.");
            }
        }
    }
    ?>
    
    0 讨论(0)
  • 2020-12-01 12:57

    SSLv3 is no more available for www.paypal.com:

    # sslscan www.paypal.com|grep Accepted
    Accepted  TLSv1  256 bits  AES256-SHA
    Accepted  TLSv1  128 bits  AES128-SHA
    Accepted  TLSv1  168 bits  DES-CBC3-SHA
    Accepted  TLSv1  128 bits  RC4-SHA
    Accepted  TLSv1  128 bits  RC4-MD5
    

    You should change your CURLOPT_SSLVERSION to TLSv1:

    curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);
    

    (This "CURL_SSLVERSION_TLSv1"-constant is not available for older PHP Versions, so another way is to remove simply the CURLOPT_SSLVERSION enforcement.)

    0 讨论(0)
  • 2020-12-01 13:09

    PayPal disabled SSLv3 in response to the "POODLE" vunerability. Read about it here: PayPal Response

    If you are using ipnlistener.php, force TLS 1.2 as the SSL Protocol:

    curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2); 
    

    (Updated: TLS V1.2 required for 2017)

    Note: PayPal is upgrading the protocols used to secure all external connections made to their systems. Transport Layer Security version 1.2 (TLS 1.2) and Hypertext Transfer Protocol version 1.1 (HTTP/1.1) will become mandatory for communication with PayPal in 2018. See their Security Roadmap

    0 讨论(0)
  • 2020-12-01 13:10

    Use this setting in your paypal class on calling function PPHttpPost()

    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); 
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); 
    curl_setopt($ch, CURLOPT_SSLVERSION, 6); //6 is for TLSV1.2
    
    0 讨论(0)
提交回复
热议问题