Skip to content

Commit 0d8c83a

Browse files
committed
Merge branch 'openssl-x509-digest' of https://github.com/datibbaw/php-src
* 'openssl-x509-digest' of https://github.com/datibbaw/php-src: Using SUCCESS and FAILURE for return values Using zend_bool for boolean arguments and return values Reduced one level of zval indirection where possible show method in error message Support string and array for peer fingerprint matching who put that stupid newline there? add md5 and sha1 fingerprint tests Renamed to be more descriptive of what it does don't leak cert on errors, return null on zpp failure Added test case for openssl_x509_digest() removed the byref result indentation fail added option for hash function added option for raw output added openssl_x509_digest(), output is binary sha1
2 parents 5d430ad + 955bc1d commit 0d8c83a

File tree

4 files changed

+243
-0
lines changed

4 files changed

+243
-0
lines changed

ext/openssl/openssl.c

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,12 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_x509_export, 0, 0, 2)
129129
ZEND_ARG_INFO(0, notext)
130130
ZEND_END_ARG_INFO()
131131

132+
ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_x509_fingerprint, 0, 0, 1)
133+
ZEND_ARG_INFO(0, x509)
134+
ZEND_ARG_INFO(0, method)
135+
ZEND_ARG_INFO(0, raw_output)
136+
ZEND_END_ARG_INFO()
137+
132138
ZEND_BEGIN_ARG_INFO(arginfo_openssl_x509_check_private_key, 0)
133139
ZEND_ARG_INFO(0, cert)
134140
ZEND_ARG_INFO(0, key)
@@ -443,6 +449,7 @@ const zend_function_entry openssl_functions[] = {
443449
PHP_FE(openssl_x509_checkpurpose, arginfo_openssl_x509_checkpurpose)
444450
PHP_FE(openssl_x509_check_private_key, arginfo_openssl_x509_check_private_key)
445451
PHP_FE(openssl_x509_export, arginfo_openssl_x509_export)
452+
PHP_FE(openssl_x509_fingerprint, arginfo_openssl_x509_fingerprint)
446453
PHP_FE(openssl_x509_export_to_file, arginfo_openssl_x509_export_to_file)
447454

448455
/* PKCS12 funcs */
@@ -1665,6 +1672,121 @@ PHP_FUNCTION(openssl_x509_export)
16651672
}
16661673
/* }}} */
16671674

1675+
static int php_openssl_x509_fingerprint(X509 *peer, const char *method, zend_bool raw, char **out, int *out_len)
1676+
{
1677+
unsigned char md[EVP_MAX_MD_SIZE];
1678+
const EVP_MD *mdtype;
1679+
int n;
1680+
1681+
if (!(mdtype = EVP_get_digestbyname(method))) {
1682+
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unknown signature algorithm");
1683+
return FAILURE;
1684+
} else if (!X509_digest(peer, mdtype, md, &n)) {
1685+
php_error_docref(NULL TSRMLS_CC, E_ERROR, "Could not generate signature");
1686+
return FAILURE;
1687+
}
1688+
1689+
if (raw) {
1690+
*out_len = n;
1691+
*out = estrndup(md, n);
1692+
} else {
1693+
*out_len = n * 2;
1694+
*out = emalloc(*out_len + 1);
1695+
1696+
make_digest_ex(*out, md, n);
1697+
}
1698+
1699+
return SUCCESS;
1700+
}
1701+
1702+
static int php_x509_fingerprint_cmp(X509 *peer, const char *method, const char *expected)
1703+
{
1704+
char *fingerprint;
1705+
int fingerprint_len;
1706+
int result = -1;
1707+
1708+
if (php_openssl_x509_fingerprint(peer, method, 0, &fingerprint, &fingerprint_len) == SUCCESS) {
1709+
result = strcmp(expected, fingerprint);
1710+
efree(fingerprint);
1711+
}
1712+
1713+
return result;
1714+
}
1715+
1716+
static zend_bool php_x509_fingerprint_match(X509 *peer, zval *val)
1717+
{
1718+
if (Z_TYPE_P(val) == IS_STRING) {
1719+
const char *method = NULL;
1720+
1721+
switch (Z_STRLEN_P(val)) {
1722+
case 32:
1723+
method = "md5";
1724+
break;
1725+
1726+
case 40:
1727+
method = "sha1";
1728+
break;
1729+
}
1730+
1731+
return method && php_x509_fingerprint_cmp(peer, method, Z_STRVAL_P(val)) == 0;
1732+
} else if (Z_TYPE_P(val) == IS_ARRAY) {
1733+
HashPosition pos;
1734+
zval **current;
1735+
char *key;
1736+
uint key_len;
1737+
ulong key_index;
1738+
1739+
for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(val), &pos);
1740+
zend_hash_get_current_data_ex(Z_ARRVAL_P(val), (void **)&current, &pos) == SUCCESS;
1741+
zend_hash_move_forward_ex(Z_ARRVAL_P(val), &pos)
1742+
) {
1743+
int key_type = zend_hash_get_current_key_ex(Z_ARRVAL_P(val), &key, &key_len, &key_index, 0, &pos);
1744+
1745+
if (key_type == HASH_KEY_IS_STRING
1746+
&& Z_TYPE_PP(current) == IS_STRING
1747+
&& php_x509_fingerprint_cmp(peer, key, Z_STRVAL_PP(current)) != 0
1748+
) {
1749+
return 0;
1750+
}
1751+
}
1752+
return 1;
1753+
}
1754+
return 0;
1755+
}
1756+
1757+
PHP_FUNCTION(openssl_x509_fingerprint)
1758+
{
1759+
X509 *cert;
1760+
zval **zcert;
1761+
long certresource;
1762+
zend_bool raw_output = 0;
1763+
char *method = "sha1";
1764+
int method_len;
1765+
1766+
char *fingerprint;
1767+
int fingerprint_len;
1768+
1769+
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Z|sb", &zcert, &method, &method_len, &raw_output) == FAILURE) {
1770+
return;
1771+
}
1772+
1773+
cert = php_openssl_x509_from_zval(zcert, 0, &certresource TSRMLS_CC);
1774+
if (cert == NULL) {
1775+
php_error_docref(NULL TSRMLS_CC, E_WARNING, "cannot get cert from parameter 1");
1776+
RETURN_FALSE;
1777+
}
1778+
1779+
if (php_openssl_x509_fingerprint(cert, method, raw_output, &fingerprint, &fingerprint_len) == SUCCESS) {
1780+
RETVAL_STRINGL(fingerprint, fingerprint_len, 0);
1781+
} else {
1782+
RETVAL_FALSE;
1783+
}
1784+
1785+
if (certresource == -1 && cert) {
1786+
X509_free(cert);
1787+
}
1788+
}
1789+
16681790
/* {{{ proto bool openssl_x509_check_private_key(mixed cert, mixed key)
16691791
Checks if a private key corresponds to a CERT */
16701792
PHP_FUNCTION(openssl_x509_check_private_key)
@@ -4865,6 +4987,17 @@ int php_openssl_apply_verification_policy(SSL *ssl, X509 *peer, php_stream *stre
48654987

48664988
/* if the cert passed the usual checks, apply our own local policies now */
48674989

4990+
if (GET_VER_OPT("peer_fingerprint")) {
4991+
if (Z_TYPE_PP(val) == IS_STRING || Z_TYPE_PP(val) == IS_ARRAY) {
4992+
if (!php_x509_fingerprint_match(peer, *val)) {
4993+
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Peer fingerprint doesn't match");
4994+
return FAILURE;
4995+
}
4996+
} else {
4997+
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Expected peer fingerprint must be a string or an array");
4998+
}
4999+
}
5000+
48685001
name = X509_get_subject_name(peer);
48695002

48705003
/* Does the common name match ? (used primarily for https://) */

ext/openssl/php_openssl.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ PHP_FUNCTION(openssl_x509_free);
6666
PHP_FUNCTION(openssl_x509_parse);
6767
PHP_FUNCTION(openssl_x509_checkpurpose);
6868
PHP_FUNCTION(openssl_x509_export);
69+
PHP_FUNCTION(openssl_x509_fingerprint);
6970
PHP_FUNCTION(openssl_x509_export_to_file);
7071
PHP_FUNCTION(openssl_x509_check_private_key);
7172

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
--TEST--
2+
Testing peer fingerprint on connection
3+
--SKIPIF--
4+
<?php
5+
if (!extension_loaded("openssl")) die("skip");
6+
if (!function_exists('pcntl_fork')) die("skip no fork");
7+
--FILE--
8+
<?php
9+
$context = stream_context_create();
10+
11+
stream_context_set_option($context, 'ssl', 'local_cert', __DIR__ . "/bug54992.pem");
12+
stream_context_set_option($context, 'ssl', 'allow_self_signed', true);
13+
$server = stream_socket_server('ssl://127.0.0.1:64321', $errno, $errstr,
14+
STREAM_SERVER_BIND|STREAM_SERVER_LISTEN, $context);
15+
16+
17+
$pid = pcntl_fork();
18+
if ($pid == -1) {
19+
die('could not fork');
20+
} else if ($pid) {
21+
$contextC = stream_context_create(
22+
array(
23+
'ssl' => array(
24+
'verify_peer' => true,
25+
'cafile' => __DIR__ . '/bug54992-ca.pem',
26+
'capture_peer_cert' => true,
27+
'peer_fingerprint' => '81cafc260aa8d82956ebc6212a362ece',
28+
)
29+
)
30+
);
31+
// should be: 81cafc260aa8d82956ebc6212a362ecc
32+
var_dump(stream_socket_client("ssl://127.0.0.1:64321", $errno, $errstr, 1,
33+
STREAM_CLIENT_CONNECT, $contextC));
34+
35+
$contextC = stream_context_create(
36+
array(
37+
'ssl' => array(
38+
'verify_peer' => true,
39+
'cafile' => __DIR__ . '/bug54992-ca.pem',
40+
'capture_peer_cert' => true,
41+
'peer_fingerprint' => array(
42+
'sha256' => '78ea579f2c3b439359dec5dac9d445108772927427c4780037e87df3799a0aa0',
43+
),
44+
)
45+
)
46+
);
47+
48+
var_dump(stream_socket_client("ssl://127.0.0.1:64321", $errno, $errstr, 1,
49+
STREAM_CLIENT_CONNECT, $contextC));
50+
} else {
51+
@pcntl_wait($status);
52+
@stream_socket_accept($server, 1);
53+
@stream_socket_accept($server, 1);
54+
}
55+
--EXPECTF--
56+
Warning: stream_socket_client(): Peer fingerprint doesn't match in %s on line %d
57+
58+
Warning: stream_socket_client(): Failed to enable crypto in %s on line %d
59+
60+
Warning: stream_socket_client(): unable to connect to ssl://127.0.0.1:64321 (Unknown error) in %s on line %d
61+
bool(false)
62+
resource(9) of type (stream)
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
--TEST--
2+
Testing openssl_x509_fingerprint()
3+
--SKIPIF--
4+
<?php
5+
if (!extension_loaded("openssl")) die("skip");
6+
?>
7+
--FILE--
8+
<?php
9+
10+
$cert = "file://" . dirname(__FILE__) . "/cert.crt";
11+
12+
echo "** Testing with no parameters **\n";
13+
var_dump(openssl_x509_fingerprint());
14+
15+
echo "** Testing default functionality **\n";
16+
var_dump(openssl_x509_fingerprint($cert));
17+
18+
echo "** Testing hash method md5 **\n";
19+
var_dump(openssl_x509_fingerprint($cert, 'md5'));
20+
21+
echo "**Testing raw output md5 **\n";
22+
var_dump(bin2hex(openssl_x509_fingerprint($cert, 'md5', true)));
23+
24+
echo "** Testing bad certification **\n";
25+
var_dump(openssl_x509_fingerprint('123'));
26+
echo "** Testing bad hash method **\n";
27+
var_dump(openssl_x509_fingerprint($cert, 'xx45'));
28+
--EXPECTF--
29+
** Testing with no parameters **
30+
31+
Warning: openssl_x509_fingerprint() expects at least 1 parameter, 0 given in %s on line %d
32+
NULL
33+
** Testing default functionality **
34+
string(40) "6e6fd1ea10a5a23071d61c728ee9b40df6dbc33c"
35+
** Testing hash method md5 **
36+
string(32) "ac77008e172897e06c0b065294487a67"
37+
**Testing raw output md5 **
38+
string(32) "ac77008e172897e06c0b065294487a67"
39+
** Testing bad certification **
40+
41+
Warning: openssl_x509_fingerprint(): cannot get cert from parameter 1 in %s on line %d
42+
bool(false)
43+
** Testing bad hash method **
44+
45+
Warning: openssl_x509_fingerprint(): Unknown signature algorithm in %s on line %d
46+
bool(false)
47+

0 commit comments

Comments
 (0)