Skip to content

curl: Add version 7.71.0 blob options #7194

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions ext/curl/interface.c
Original file line number Diff line number Diff line change
Expand Up @@ -1153,6 +1153,16 @@ PHP_MINIT_FUNCTION(curl)
REGISTER_CURL_CONSTANT(CURL_VERSION_ALTSVC);
#endif

#if LIBCURL_VERSION_NUM >= 0x074700 /* Available since 7.71.0 */
REGISTER_CURL_CONSTANT(CURLOPT_ISSUERCERT_BLOB);
REGISTER_CURL_CONSTANT(CURLOPT_PROXY_ISSUERCERT);
REGISTER_CURL_CONSTANT(CURLOPT_PROXY_ISSUERCERT_BLOB);
REGISTER_CURL_CONSTANT(CURLOPT_PROXY_SSLCERT_BLOB);
REGISTER_CURL_CONSTANT(CURLOPT_PROXY_SSLKEY_BLOB);
REGISTER_CURL_CONSTANT(CURLOPT_SSLCERT_BLOB);
REGISTER_CURL_CONSTANT(CURLOPT_SSLKEY_BLOB);
#endif

REGISTER_CURL_CONSTANT(CURLOPT_SAFE_UPLOAD);

#ifdef PHP_CURL_NEED_OPENSSL_TSL
Expand Down Expand Up @@ -2520,6 +2530,9 @@ static int _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue, bool i
#if LIBCURL_VERSION_NUM >= 0x073d00 /* Available since 7.61.0 */
case CURLOPT_PROXY_TLS13_CIPHERS:
case CURLOPT_TLS13_CIPHERS:
#endif
#if LIBCURL_VERSION_NUM >= 0x074700 /* Available since 7.71.0 */
case CURLOPT_PROXY_ISSUERCERT:
#endif
{
zend_string *tmp_str;
Expand Down Expand Up @@ -2923,6 +2936,29 @@ static int _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue, bool i
ZVAL_COPY(&ch->handlers.fnmatch->func_name, zvalue);
break;

/* Curl blob options */
#if LIBCURL_VERSION_NUM >= 0x074700 /* Available since 7.71.0 */
case CURLOPT_ISSUERCERT_BLOB:
case CURLOPT_PROXY_ISSUERCERT_BLOB:
case CURLOPT_PROXY_SSLCERT_BLOB:
case CURLOPT_PROXY_SSLKEY_BLOB:
case CURLOPT_SSLCERT_BLOB:
case CURLOPT_SSLKEY_BLOB:
{
zend_string *tmp_str;
zend_string *str = zval_get_tmp_string(zvalue, &tmp_str);

struct curl_blob stblob;
stblob.data = ZSTR_VAL(str);
stblob.len = ZSTR_LEN(str);
stblob.flags = CURL_BLOB_COPY;
error = curl_easy_setopt(ch->cp, option, &stblob);

zend_tmp_string_release(tmp_str);
}
break;
#endif

default:
if (is_array_config) {
zend_argument_value_error(2, "must contain only valid cURL options");
Expand Down
239 changes: 239 additions & 0 deletions ext/curl/tests/curl_setopt_ssl.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
--TEST--
CURLOPT_SSL* basic client auth tests
--EXTENSIONS--
curl
--SKIPIF--
<?php
if (!function_exists("proc_open")) die("skip no proc_open");
exec('openssl version', $out, $code);
if ($code > 0) die("skip couldn't locate openssl binary");
if (PHP_OS_FAMILY === 'Windows') die('skip not for Windows');
$curl_version = curl_version();
if ($curl_version['version_number'] < 0x074700) {
die("skip: blob options not supported for curl < 7.71.0");
}
?>
--FILE--
<?php

function check_error(CurlHandle $ch) {
if (curl_errno($ch) !== 0) {
echo "CURL ERROR: " . curl_errno($ch) . "\n";
}
}

function check_response($response, $clientCertSubject) {
if (strpos($response, $clientCertSubject) === false) {
echo "client cert subject not in response\n";
} else {
echo "client cert subject in response\n";
}
}

$clientCertSubject = "Subject: C=US, ST=TX, L=Clientlocation, O=Clientcompany, CN=clientname/[email protected]";

// load server cert
$serverCertPath = __DIR__ . DIRECTORY_SEPARATOR . 'curl_setopt_ssl_servercert.pem';
$serverCert = file_get_contents($serverCertPath);

// load server key
$serverKeyPath = __DIR__ . DIRECTORY_SEPARATOR . 'curl_setopt_ssl_serverkey.pem';
$serverKey = file_get_contents($serverKeyPath);

// load client cert
$clientCertPath = __DIR__ . DIRECTORY_SEPARATOR . 'curl_setopt_ssl_clientcert.pem';
$clientCert = file_get_contents($clientCertPath);

// load client key
$clientKeyPath = __DIR__ . DIRECTORY_SEPARATOR . 'curl_setopt_ssl_clientkey.pem';
$clientKey = file_get_contents($clientKeyPath);

if ($serverCert === false
|| $serverKey === false
|| $clientCert === false
|| $clientKey === false
) {
die('failed to load test certs and keys for files');
}

$port = 14430;

// set up local server
$cmd = "openssl s_server -key $serverKeyPath -cert $serverCertPath -accept $port -www -CAfile $clientCertPath -verify_return_error -Verify 1";
$process = proc_open($cmd, [["pipe", "r"], ["pipe", "w"], ["pipe", "w"]], $pipes);

if ($process === false) {
die('failed to start server');
}
try {
// Give the server time to start
sleep(1);

echo "case 1: client cert and key from string\n";
$ch = curl_init("https://127.0.0.1:$port/");
var_dump(curl_setopt($ch, CURLOPT_SSLCERT_BLOB, $clientCert));
var_dump(curl_setopt($ch, CURLOPT_SSLKEY_BLOB, $clientKey));
var_dump(curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false));
var_dump(curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

$response = curl_exec($ch);
check_response($response, $clientCertSubject);
check_error($ch);
curl_close($ch);

echo "\n";
echo "case 2: empty client cert and key from string\n";
$ch = curl_init("https://127.0.0.1:$port/");
var_dump(curl_setopt($ch, CURLOPT_SSLCERT_BLOB, ''));
var_dump(curl_setopt($ch, CURLOPT_SSLKEY_BLOB, $clientKey));
var_dump(curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false));
var_dump(curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

$response = curl_exec($ch);
check_response($response, $clientCertSubject);
check_error($ch);
curl_close($ch);

echo "\n";
echo "case 3: client cert and empty key from string\n";
$ch = curl_init("https://127.0.0.1:$port/");
var_dump(curl_setopt($ch, CURLOPT_SSLCERT_BLOB, $clientCert));
var_dump(curl_setopt($ch, CURLOPT_SSLKEY_BLOB, ''));
var_dump(curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false));
var_dump(curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

$response = curl_exec($ch);
check_response($response, $clientCertSubject);
check_error($ch);
curl_close($ch);

echo "\n";
echo "case 4: client cert and key from file\n";
$ch = curl_init("https://127.0.0.1:$port/");
var_dump(curl_setopt($ch, CURLOPT_SSLCERT, $clientCertPath));
var_dump(curl_setopt($ch, CURLOPT_SSLKEY, $clientKeyPath));
var_dump(curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false));
var_dump(curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

$response = curl_exec($ch);
check_response($response, $clientCertSubject);
check_error($ch);
curl_close($ch);

echo "\n";
echo "case 5: issuer cert from file\n";
$ch = curl_init("https://127.0.0.1:$port/");
var_dump(curl_setopt($ch, CURLOPT_CAINFO, $serverCertPath));
var_dump(curl_setopt($ch, CURLOPT_ISSUERCERT, $serverCertPath));
var_dump(curl_setopt($ch, CURLOPT_SSLCERT, $clientCertPath));
var_dump(curl_setopt($ch, CURLOPT_SSLKEY, $clientKeyPath));
var_dump(curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true));
var_dump(curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

$response = curl_exec($ch);
check_response($response, $clientCertSubject);
check_error($ch);
curl_close($ch);

echo "\n";
echo "case 6: issuer cert from string\n";
$ch = curl_init("https://127.0.0.1:$port/");
var_dump(curl_setopt($ch, CURLOPT_CAINFO, $serverCertPath));
var_dump(curl_setopt($ch, CURLOPT_ISSUERCERT_BLOB, $serverCert));
var_dump(curl_setopt($ch, CURLOPT_SSLCERT, $clientCertPath));
var_dump(curl_setopt($ch, CURLOPT_SSLKEY, $clientKeyPath));
var_dump(curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true));
var_dump(curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

$response = curl_exec($ch);
check_response($response, $clientCertSubject);
check_error($ch);
curl_close($ch);

echo "\n";
echo "case 7: empty issuer cert from string\n";
$ch = curl_init("https://127.0.0.1:$port/");
var_dump(curl_setopt($ch, CURLOPT_CAINFO, $serverCertPath));
var_dump(curl_setopt($ch, CURLOPT_ISSUERCERT_BLOB, ''));
var_dump(curl_setopt($ch, CURLOPT_SSLCERT, $clientCertPath));
var_dump(curl_setopt($ch, CURLOPT_SSLKEY, $clientKeyPath));
var_dump(curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true));
var_dump(curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

$response = curl_exec($ch);
check_response($response, $clientCertSubject);
check_error($ch);
curl_close($ch);

} finally {
// clean up server process
proc_terminate($process);
proc_close($process);
}

?>
--EXPECT--
case 1: client cert and key from string
bool(true)
bool(true)
bool(true)
bool(true)
client cert subject in response

case 2: empty client cert and key from string
bool(true)
bool(true)
bool(true)
bool(true)
client cert subject not in response
CURL ERROR: 58

case 3: client cert and empty key from string
bool(true)
bool(true)
bool(true)
bool(true)
client cert subject not in response
CURL ERROR: 58

case 4: client cert and key from file
bool(true)
bool(true)
bool(true)
bool(true)
client cert subject in response

case 5: issuer cert from file
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
client cert subject in response

case 6: issuer cert from string
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
client cert subject in response

case 7: empty issuer cert from string
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
client cert subject not in response
CURL ERROR: 83
32 changes: 32 additions & 0 deletions ext/curl/tests/curl_setopt_ssl_clientcert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
-----BEGIN CERTIFICATE-----
MIIFgDCCA2gCCQCpI/KBeVlPsjANBgkqhkiG9w0BAQsFADCBgTELMAkGA1UEBhMC
VVMxCzAJBgNVBAgMAlRYMRcwFQYDVQQHDA5DbGllbnRsb2NhdGlvbjEWMBQGA1UE
CgwNQ2xpZW50Y29tcGFueTETMBEGA1UEAwwKY2xpZW50bmFtZTEfMB0GCSqGSIb3
DQEJARYQdGVzdEBleGFtcGxlLmNvbTAeFw0yMTA2MjYxOTAyMTlaFw00ODExMTAx
OTAyMTlaMIGBMQswCQYDVQQGEwJVUzELMAkGA1UECAwCVFgxFzAVBgNVBAcMDkNs
aWVudGxvY2F0aW9uMRYwFAYDVQQKDA1DbGllbnRjb21wYW55MRMwEQYDVQQDDApj
bGllbnRuYW1lMR8wHQYJKoZIhvcNAQkBFhB0ZXN0QGV4YW1wbGUuY29tMIICIjAN
BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA2TlNc7TMAjjjgERln914ScxiMcwH
5TVOjUxhBX9XuSNP8tnO03r4w6bQFCaq7ire63ssCx1aua4tCIMeKG++ueiMeqLc
fmc5lIs3GuMEYrcopf9FQvvDuRM8EpkIKNNp5e82TBQkEAtSFc1e8rPnjAxi68gm
g/yTrm74tG4sqyHtzpiNUwwP4sE3yfV6sENF+Sth8grfsTBShXmq1eQ+PWYBHT13
e4J0YBkOXduNIFuXs/mKUgFlEhzhthdznsN2dLzWzVmZZGX1nTsZ+yQYDdGVq0ay
F20unovhZNh2J9kfdgKDZFPgtRHgFYZZTeacZCGWg9KMkwVXqdufjk4M/khkpwTh
agZHhf1lLq3pmGzm8hZto5OKWXOk+hoopSEU0+V+PJtl5Cl7MIa6gJVsk6rT3abp
X5e3bmlABreZIlFjk2kO68gNbbSdZiZfjqtqLeTY2KwYsUZ358zAJlDT8Zc0T00u
pQEBQxFBNWQqfXwlnWtvcKpvwonbBZLcUGoWMiuFIgsnsTvxLWayX20Njy2m7K7d
002cn3KBVpu3EXO9NkYKg+q6z79FvAlf9Xq/nV9ubvGQ/wEpJOr4egvWHoC0qt3/
uW+nzhmqhBv6cGWnKDUVWknAsothJlunP55Dtq2fmxmXH4TjYqofhfvMN88XdxFb
0ZUYojirWTOSIn8CAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAa+rI3YH3HT+KiyBE
8Mv7/gH1jlQ2zPzTu0EccgnDcuXQSlAtvz+0evoUYOpREQ/lfeNeICllJgK0R0Go
QReqsnC6Tbq/pCDMmo6g6vm7H0j1b1RNNnhNuh0el/O/cnAT6KBcrmojBBkJMHRT
vHJeE0W+rr3Qvg1GM3KfHlyxQeZLQrTeuNjE5f+9LUy1d11UV2xLuiGax5zYQQM3
iwqHFvfe3EYzmRECmMY4qLZgN/c1Cd4RK8wEPKKsvOjxpNLAAR8+VeLUj32UmtWd
ZFnTFEiJI0RWtRdLt1J3r9+ybg3c0e/BJWxK6Dw00NV6A5UNd5lVbv5v9ZMGI7tu
fTaZE4AR50yd+VMZXMMZj282eZOIIoi1IeT1uofyDcYvxWjNn+gW/Di8niKunwYA
tdXANcGtzlB3slBSX0gZ9sItdFkb7gDG86L7Gg90GJEG0ZtU8WzQ5BUArMfYWZbv
l8A0kOQi6dR+/RjcswRDXWSYx3U0j0db5Ibg7D3WMRcHQasck2YHZrlku5ILC5Gd
F0N4bIps2V6kN/OWFdSyWTwvIS2m62ovg3o9hR2aODqkL36CjS/rHPu3vovveIJH
mDlauv5b5SdRRF4kk8zHZFIlFJMlT2KcOB3A6doukOVCx+2ikr1v1SbOaDZj1mre
JMf5QKxVEqCR8jT+ak7yx84JB5M=
-----END CERTIFICATE-----
51 changes: 51 additions & 0 deletions ext/curl/tests/curl_setopt_ssl_clientkey.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----
Loading