1

I am attempting to connect to a 3rd party API, and they require me to pass 3 certificate files they have given me: public cert, private cert, and CA cert. It works fine in cURL with the following settings:

if (empty($this->order['connector'])) {
    curl_setopt($_curl, CURLOPT_SSLKEY, API_PRIVATE_CERT);
    curl_setopt($_curl, CURLOPT_CAINFO, API_CA_CERT);
    curl_setopt($_curl, CURLOPT_SSLCERT, API_PUBLIC_CERT);
}

Each value passed is a path to a physical file on the server. This works fine.

With one request, however, I have to pass a header 'Content-Type: Multipart/Related; boundary="---BOUNDARY123456"' with a MIME message that contains an XML file and a Base64 encoded PDF. This fails with a 500 error on their end. And in researching this, I have seen cURL cannot properly handle Content-Type: Multipart/Related posts.

https://stackoverflow.com/a/25998544/3434084

So I have tried to send it using stream_get_contents(), but I get no response back. So I am thinking my cert data is wrong. How can I pass the same values I use in cURL via stream_get_contents()?

Here's the code:

$payload = '----=FB498299F0F50D2A190B3C
Content-Type: application/x-ofx

<?xml version="1.0" encoding="ISO-8859-1"?>
<?OFX OFXHEADER="200" VERSION="201" SECURITY="NONE" OLDFILEUID="NONE" NEWFILEUID="NONE"?>
<OFX>
        <SIGNONMSGSRQV1>
                <SONRQ>
                        <LANGUAGE>ENG</LANGUAGE>
                        <APPID>TWEEN</APPID>
                </SONRQ>
        </SIGNONMSGSRQV1>
        ...
</OFX>

----=FB498299F0F50D2A190B3C
Content-Type: application/pdf
Content-Transfer-Encoding: base64
Content-Location: full1_1559588546.pdf

JVBERi0xLjQKJcOkw7zDtsOfCjIgMCBvYmoKPDwvTGVuZ3RoIDM...PRgo=
----=FB498299F0F50D2A190B3C' . "\r\n\r\n";

$params = [
    'http' => [
        'method' => 'POST',
        'header' => 'Content-Type: Multipart/Related; boundary="----=FB498299F0F50D2A190B3C"',
        'content' => $payload
    ],
    'ssl'  => [
        'verify_peer' => true,
        'local_pk'    => API_PRIVATE_CERT,
        'cafile'      => API_CA_CERT,
        'local_cert'  => API_PUBLIC_CERT
    ]
];

$_stream = stream_context_create($params);
$response = @file_get_contents('https://blah/api/, FILE_TEXT, $_stream);

TIA!

Joel F.
  • 59
  • 8
  • you suppress errors in your example which sort of prevents trouble-shooting I guess. To further trouble-shoot you can try hooking stream notifications (events) and maybe setting timeouts to low values can help w/ trouble-shooting as well. See https://www.php.net/manual/en/stream.errors.php and https://www.php.net/manual/en/function.stream-notification-callback.php - Last but not least, this Q&A material looks also interesting: https://stackoverflow.com/q/40999726/367456 – hakre Jun 03 '19 at 20:45
  • Found another multipart/related w/ curl in PHP related Q&A, even with an accepted answer: https://stackoverflow.com/a/9252130/367456 /e: it specifically points out: "Note, the last boundary has an additional `--` at the end." also the lines in your payload are not \r\n terminated, I don't know for the mime-type specifically, but the details should be here: https://tools.ietf.org/html/rfc2387 (perhaps) – hakre Jun 03 '19 at 20:54
  • @hakre Thanks for the link! I thought I had seen every page and tried every sample here in regards to multipart/related. Will take a look. – Joel F. Jun 03 '19 at 21:09
  • It's always like that, information overflow. From what I've seen so far, it's not that curl does not per-se can't do that, it's just that you need to encode the payload your own as curl can't encode multipart/related, however sending it (and with your SSL options) does work as you told. So I'd say this should not be a show stopper in using curl (however the other question about what goes wrong with the HTTP/SSL stream is interesting as well). But as you already have curl working with the certs, I would just focus on the payload encoding as multipart/related. – hakre Jun 03 '19 at 21:14
  • @hakre The original code I had used all of the same cURL code, but I hand built the payload so it matched what the end-use API needed. But they report 500 errors w/ very little info to glean. Just frustrating. :( – Joel F. Jun 04 '19 at 20:21
  • Yes I can understand. I guess you don't have the sever logs so that you could learn more about the 500 reason and yeah, that moves things more far away which is a burden on trouble shooting. I also left some hints on how to better inspect for streams, I have a pretty high rated answer for debugging curl in PHP which might be of use if you currently opt to curl: https://stackoverflow.com/a/14436877/367456 - with further links which for curl is normally the first thing I do when I want to learn more where things go south. – hakre Jun 04 '19 at 21:50
  • Adding your PHP version to the question might make sense, too. E.g. default values for the stream context option, esp. w/ ssl/tls has undergone changes over time (normally the more recent the more strict the verification is). – hakre Jun 04 '19 at 22:02
  • 1
    @hakre It's running now on PHP 5.6, but next year will need to run on 7.x. I am also trying https://github.com/robtimus/php-multipart to see if it does it, as well. – Joel F. Jun 05 '19 at 11:40
  • Now that's great to read. If you put your learnings as an answer below it's kept for a longer time and for others this might be of use, too. Th emultipart lib looks nice on first sight, thanks for the link. – hakre Jun 06 '19 at 08:50
  • This ended up being a non-issue. There were initial issues with my cURL code that caused me not to send the headers properly. Once I fixed that, it made the above code no longer needed, as my regular cURL code worked, as expected. Thanks to @hakre for the great responses and advice. – Joel F. Jun 15 '19 at 17:29
  • Oh, it would be nice if you could add that as an answer and maybe give a bit more of a technical hint where you got stuck so that if future users find it can not only learn that it's possible but also how to trouble-shoot. You can also mark that kind of answer then as the solution which will make this totally clear (and it's not so much of a need to go through all the comments),. Thanks again! – hakre Jun 17 '19 at 09:45

2 Answers2

0

try

$params = [
    "ssl"=>[
        "verify_peer"=> true,
        "verify_peer_name"=> true,
        "cafile" => "pem.pem",
    ],
];

$response = file_get_contents($URL, 0, stream_context_create($params)
Isaac Limón
  • 1,940
  • 18
  • 15
  • Thanks for the post, but I also need to pass the public and private key. What keys under SSL would those values go to> – Joel F. Jun 04 '19 at 19:53
  • @JoelF.: From what I've seen yesterday, what you do already looks pretty good to me. I checked that at least from docs. I don't have a setup at hand which is similar to test that configuration fully. I'm pretty sure, in the end this is possible w/ streams (and I also think with curl). This is "just" (tm) finding where the configuraiton is broken. Actually, let's face it: if there is a 500, that means the transport layer made it, right? This is more that the encoding of the payload is wrong and the other server borks (or the HTTP version etc. pp.). – hakre Jun 04 '19 at 21:52
  • @hakre Thanks for all of the help. I agree that the payload does send, but they're not going to fix it on their side, so I am stuck determining why. – Joel F. Jun 05 '19 at 11:39
-1

It turns out my issues was with the original cURL code I had used that was failing. Here's the updated code I needed to use in this case to send multipart/related content via cURL, using the 3rd party's SSL certificate data provided to me to authenticate access.

$headers = [
    'Content-Type: Multipart/Related; boundary="--' . $this->api_boundary . '";type=text/xml'
];

$_curl = curl_init();
curl_setopt($_curl, CURLOPT_URL, $this->api_url);
curl_setopt($_curl, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($_curl, CURLOPT_SSLCERTTYPE, FALSE);
curl_setopt($_curl, CURLOPT_POSTFIELDS, $this->api_xml);
curl_setopt($_curl, CURLOPT_POST, TRUE);
curl_setopt($_curl, CURLOPT_HTTPHEADER, $headers);

// TLS items
curl_setopt($_curl, CURLOPT_SSLKEY, API_PRIVATE_CERT); // path to private cert in .pem format
curl_setopt($_curl, CURLOPT_CAINFO, API_CA_CERT); // path to CA cert in .pem format
curl_setopt($_curl, CURLOPT_SSLCERT, API_PUBLIC_CERT); // path to public cert in .pem format

// process
$response = curl_exec($_curl);

// check for errors
if (curl_error($_curl)) {
    // capture error here
}

$status   = curl_getinfo($_curl);
curl_close($_curl);
Joel F.
  • 59
  • 8
  • @Dharman You're rihgt. I had it off on my dev machine b/c the SSL wouldn;t validate. Production does. Answer updated. – Joel F. Jun 17 '19 at 22:24
  • Tht values are still wrong. Just remove those lines and let the default values be. – Dharman Jun 18 '19 at 08:32