Skip to content

RFC7512 URI support #6860

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
134 changes: 111 additions & 23 deletions ext/openssl/openssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
#include <openssl/ssl.h>
#include <openssl/pkcs12.h>
#include <openssl/cms.h>
#include <openssl/engine.h>

/* Common */
#include <time.h>
Expand Down Expand Up @@ -515,7 +516,6 @@ struct php_x509_request { /* {{{ */
static X509 *php_openssl_x509_from_param(zend_object *cert_obj, zend_string *cert_str);
static X509 *php_openssl_x509_from_zval(zval *val, bool *free_cert);
static X509_REQ *php_openssl_csr_from_param(zend_object *csr_obj, zend_string *csr_str);
static EVP_PKEY *php_openssl_pkey_from_zval(zval *val, int public_key, char *passphrase, size_t passphrase_len);

static int php_openssl_is_private_key(EVP_PKEY* pkey);
static X509_STORE * php_openssl_setup_verify(zval * calist);
Expand Down Expand Up @@ -933,6 +933,46 @@ static void php_openssl_dispose_config(struct php_x509_request * req) /* {{{ */
}
/* }}} */

static ENGINE *php_openssl_make_pkcs11_engine(const bool warn) /* {{{ */
{
char *verbose = NULL;
ENGINE *engine;

engine = ENGINE_by_id("pkcs11");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As already pointed in #6860 (comment) see https://www.openssl.org/docs/man3.0/man7/migration_guide.html#Providers-are-a-replacement-for-engines-and-low-level-method-overrides

Engines are replaced by providers, I'm not sure it makes sense to add compatibility with openssl1.1 ATM

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, can someone propose an update of this serie that use the providers instead of "engine" ? As stated, I am not working on this topic anymore. I was focused on it few months ago, but I cannot spend months in waiting.

if (engine == NULL) {
if (warn) {
php_error_docref(NULL, E_WARNING, "Cannot load PKCS11 engine");
}
php_openssl_store_errors();
return NULL;
}
verbose = getenv("OPENSSL_ENGINE_VERBOSE");
if (verbose) {
if (!ENGINE_ctrl_cmd_string(engine, "VERBOSE", NULL, 0)) {
ENGINE_free(engine);
php_openssl_store_errors();
return NULL;
}
} else {
if (!ENGINE_ctrl_cmd_string(engine, "QUIET", NULL, 0)) {
ENGINE_free(engine);
php_openssl_store_errors();
return NULL;
}
}
if (!ENGINE_init(engine)) {
if (warn) {
php_error_docref(NULL, E_WARNING, "Cannot init PKCS11 engine");
}
php_openssl_store_errors();
return NULL;
}
ENGINE_free(engine);

return engine;
}
/* }}} */

#if defined(PHP_WIN32) || PHP_OPENSSL_API_VERSION >= 0x10100
#define PHP_OPENSSL_RAND_ADD_TIME() ((void) 0)
#else
Expand Down Expand Up @@ -1384,9 +1424,9 @@ PHP_FUNCTION(openssl_get_cert_locations)
}
/* }}} */

static X509 *php_openssl_x509_from_str(zend_string *cert_str) {
X509 *php_openssl_x509_from_str(zend_string *cert_str) {
X509 *cert = NULL;
BIO *in;
BIO *in = NULL;

if (ZSTR_LEN(cert_str) > 7 && memcmp(ZSTR_VAL(cert_str), "file://", sizeof("file://") - 1) == 0) {
if (php_openssl_open_base_dir_chk(ZSTR_VAL(cert_str) + (sizeof("file://") - 1))) {
Expand All @@ -1399,6 +1439,32 @@ static X509 *php_openssl_x509_from_str(zend_string *cert_str) {
return NULL;
}
cert = PEM_read_bio_X509(in, NULL, NULL, NULL);
} else if (ZSTR_LEN(cert_str) > 7 && memcmp(ZSTR_VAL(cert_str), "pkcs11:", sizeof("pkcs11:") - 1) == 0) {
ENGINE *engine = php_openssl_make_pkcs11_engine(true);
struct {
const char *s_slot_cert_id;
X509 *cert;
} parms = {
.s_slot_cert_id = ZSTR_VAL(cert_str),
.cert = NULL,
};
int force_login = 0;

if (!engine) {
return NULL;
}

if (!ENGINE_ctrl_cmd(engine, "LOAD_CERT_CTRL", 0, &parms, NULL, force_login)) {
ENGINE_finish(engine);
php_openssl_store_errors();
return NULL;
}
ENGINE_finish(engine);
if (parms.cert == NULL) {
php_openssl_store_errors();
return NULL;
}
cert = parms.cert;
} else {
in = BIO_new_mem_buf(ZSTR_VAL(cert_str), (int) ZSTR_LEN(cert_str));
if (in == NULL) {
Expand Down Expand Up @@ -3477,7 +3543,7 @@ static int php_openssl_pem_password_cb(char *buf, int size, int rwflag, void *us
}
/* }}} */

static EVP_PKEY *php_openssl_pkey_from_zval(zval *val, int public_key, char *passphrase, size_t passphrase_len)
EVP_PKEY *php_openssl_pkey_from_zval(zval *val, int public_key, char *passphrase, size_t passphrase_len)
{
EVP_PKEY *key = NULL;
X509 *cert = NULL;
Expand Down Expand Up @@ -3549,6 +3615,8 @@ static EVP_PKEY *php_openssl_pkey_from_zval(zval *val, int public_key, char *pas
} else if (Z_TYPE_P(val) == IS_OBJECT && Z_OBJCE_P(val) == php_openssl_certificate_ce) {
cert = php_openssl_certificate_from_obj(Z_OBJ_P(val))->x509;
} else {
ENGINE *engine = NULL;

/* force it to be a string and check if it refers to a file */
/* passing non string values leaks, object uses toString, it returns NULL
* See bug38255.phpt
Expand All @@ -3566,13 +3634,27 @@ static EVP_PKEY *php_openssl_pkey_from_zval(zval *val, int public_key, char *pas
TMP_CLEAN;
}
}
if (Z_STRLEN_P(val) > 7 && memcmp(Z_STRVAL_P(val), "pkcs11:", sizeof("pkcs11:") - 1) == 0) {
engine = php_openssl_make_pkcs11_engine(true);
if (engine == NULL) {
TMP_CLEAN;
}
}
/* it's an X509 file/cert of some kind, and we need to extract the data from that */
if (public_key) {
cert = php_openssl_x509_from_str(Z_STR_P(val));
if (engine) {
key = ENGINE_load_public_key(engine, Z_STRVAL_P(val), NULL, NULL);
ENGINE_finish(engine);
engine = NULL;
}
/* val could be a certificate (file, pkcs11:, etc., let's try to extract the key) */
if (!key) {
cert = php_openssl_x509_from_str(Z_STR_P(val));
}

if (cert) {
free_cert = 1;
} else {
} else if (!key) {
/* not a X509 certificate, try to retrieve public key */
BIO* in;
if (filename) {
Expand All @@ -3589,26 +3671,32 @@ static EVP_PKEY *php_openssl_pkey_from_zval(zval *val, int public_key, char *pas
}
} else {
/* we want the private key */
BIO *in;

if (filename) {
in = BIO_new_file(filename, PHP_OPENSSL_BIO_MODE_R(PKCS7_BINARY));
if (engine) {
key = ENGINE_load_private_key(engine, Z_STRVAL_P(val), NULL, NULL);
ENGINE_finish(engine);
engine = NULL;
} else {
in = BIO_new_mem_buf(Z_STRVAL_P(val), (int)Z_STRLEN_P(val));
}
BIO *in;

if (in == NULL) {
TMP_CLEAN;
}
if (passphrase == NULL) {
key = PEM_read_bio_PrivateKey(in, NULL, NULL, NULL);
} else {
struct php_openssl_pem_password password;
password.key = passphrase;
password.len = passphrase_len;
key = PEM_read_bio_PrivateKey(in, NULL, php_openssl_pem_password_cb, &password);
if (filename) {
in = BIO_new_file(filename, PHP_OPENSSL_BIO_MODE_R(PKCS7_BINARY));
} else {
in = BIO_new_mem_buf(Z_STRVAL_P(val), (int)Z_STRLEN_P(val));
}

if (in == NULL) {
TMP_CLEAN;
}
if (passphrase == NULL) {
key = PEM_read_bio_PrivateKey(in, NULL, NULL, NULL);
} else {
struct php_openssl_pem_password password;
password.key = passphrase;
password.len = passphrase_len;
key = PEM_read_bio_PrivateKey(in, NULL, php_openssl_pem_password_cb, &password);
}
BIO_free(in);
}
BIO_free(in);
}
}

Expand Down
3 changes: 3 additions & 0 deletions ext/openssl/php_openssl.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ PHP_OPENSSL_API zend_string* php_openssl_decrypt(
const char *iv, size_t iv_len,
const char *tag, zend_long tag_len,
const char *aad, size_t aad_len);
PHP_OPENSSL_API EVP_PKEY *php_openssl_pkey_from_zval(zval *val, int public_key,
char *passphrase, size_t passphrase_len);
PHP_OPENSSL_API X509 *php_openssl_x509_from_str(zend_string *cert_str);

/* OpenSSLCertificate class */

Expand Down
147 changes: 147 additions & 0 deletions ext/openssl/tests/openssl_pkey_get_public_rfc7512.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
--TEST--
openssl_pkey_get_public(), openss_pkey_get_private(), openssl_x509_read() with RFC7512 URI
--SKIPIF--
<?php
if (!extension_loaded("openssl"))
die("skip");
if (!function_exists("proc_open")) die("skip no proc_open");
$PKCS11_MODULE_PATH="/usr/lib/softhsm/libsofthsm2.so";
exec('openssl help', $out, $code);
if ($code > 0) die("skip couldn't locate openssl binary");
exec('softhsm2-util --version', $out, $code);
if ($code > 0) die("skip couldn't locate softhsm2-util binary");
exec('pkcs11-tool --show-info --module ' . $PKCS11_MODULE_PATH, $out, $code);
if ($code > 0) die("skip couldn't locate pkcs11-tool binary");
exec('pkcs11-dump info ' . $PKCS11_MODULE_PATH, $out, $code);
if ($code > 0) die("skip couldn't locate pkcs11-dump binary");
?>
--FILE--
<?php

/* simple exec */
function sexec($cmd, &$stdout=null, &$stderr=null) {
$proc = proc_open(
$cmd,
[
1 => ['pipe','w'],
2 => ['pipe','w'],
],
$pipes
);

$stdout = stream_get_contents($pipes[1]);
fclose($pipes[1]);

$stderr = stream_get_contents($pipes[2]);
fclose($pipes[2]);

return proc_close($proc);
}

$PKCS11_MODULE_PATH="/usr/lib/softhsm/libsofthsm2.so";
putenv("PKCS11_MODULE_PATH=".$PKCS11_MODULE_PATH);

$SOFTHSM2_CONF=tempnam(sys_get_temp_dir(), 'softhsm2');
$SOFTHSM2_TOKENDIR=sprintf("%s.dir", $SOFTHSM2_CONF);
mkdir($SOFTHSM2_TOKENDIR);
$PHP11_PIN=123456;
$PHP11_SOPIN=12345678;

file_put_contents(
$SOFTHSM2_CONF,
sprintf(
"directories.tokendir = %s" . PHP_EOL.
"objectstore.backend = file" . PHP_EOL.
"log.level = DEBUG" . PHP_EOL.
"slots.removable = false" . PHP_EOL.
"slots.mechanisms = ALL",
$SOFTHSM2_TOKENDIR
)
);

putenv(sprintf("SOFTHSM2_CONF=%s", $SOFTHSM2_CONF));
sexec("softhsm2-util --show-slots | grep ^Slot | cut -d ' ' -f 2", $out);
$INIT11_SLOT=(int)$out[0];
if ($INIT11_SLOT != 0) {
echo "Error slot";
exec("softhsm2-util --show-slots", $out);
var_dump($out);
exit(1);
}

sexec(sprintf("softhsm2-util --init-token --free --slot %d --label TestVJToken --pin %s --so-pin %s",
$INIT11_SLOT, $PHP11_PIN, $PHP11_SOPIN), $out);

/* XXX custom slot is always the first one */
sexec(sprintf("pkcs11-dump slotlist %s 2>/dev/null | grep SoftHSM | head -1 | cut -f 1",
$PKCS11_MODULE_PATH), $PHP11_SLOT);
if (!is_string($PHP11_SLOT)) {
echo "Cannot detect the slot" . PHP_EOL;
exit(1);
}
$PHP11_SLOT=(int)$PHP11_SLOT;

/*
* Most of these features can be supported natively by PHP, but
* the purpose is to focus on RFC7512, so let's use the system tools.
*/
$PRIVKEYPEM=$SOFTHSM2_TOKENDIR . '.key.priv.pem';
$PRIVKEYDER=$SOFTHSM2_TOKENDIR . '.key.priv.der';
$PUBKEYDER=$SOFTHSM2_TOKENDIR . '.key.pub.der';
$CERTDER=$SOFTHSM2_TOKENDIR . '.cert.der';
sexec(sprintf("openssl genrsa -out %s 2048", $PRIVKEYPEM), $genkey);
sexec(sprintf("openssl rsa -inform PEM -in %s -outform DER -out %s", $PRIVKEYPEM, $PRIVKEYDER), $pem2der);

/* extract the public key */
sexec(sprintf("openssl rsa -in %s -outform DER -pubout -out %s", $PRIVKEYPEM, $PUBKEYDER), $extract);

/* let's import these keys */
sexec(sprintf("pkcs11-tool --login --pin %s --write-object %s --type privkey --label VJPrivKey --module %s",
$PHP11_PIN, $PRIVKEYDER, $PKCS11_MODULE_PATH), $importpriv);

sexec(sprintf("pkcs11-tool --login --pin %s --write-object %s --type pubkey --label VJPubKey --module %s",
$PHP11_PIN, $PUBKEYDER, $PKCS11_MODULE_PATH), $importpub);

/* let's build the x509 */
sexec(sprintf("openssl req -new -x509 -subj '/CN=MyCertVJ' ".
"-engine pkcs11 -keyform engine -key 'pkcs11:object=VJPrivKey;type=private;pin-value=%s' ".
"-outform DER -out %s", $PHP11_PIN, $CERTDER), $req);
sexec(sprintf("pkcs11-tool --login --pin %s --write-object %s --type cert --label VJCert --module %s",
$PHP11_PIN, $CERTDER, $PKCS11_MODULE_PATH), $importcert);

/* let's start the tests */

$key = openssl_pkey_get_private(sprintf("pkcs11:object=VJPrivKey;type=private;pin-value=%s", $PHP11_PIN));
if (!($key instanceof OpenSSLAsymmetricKey)) {
echo "Private Key NOK" . PHP_EOL;
exit(1);
}
echo "Private Key OK" . PHP_EOL;

$key = openssl_pkey_get_public(sprintf("pkcs11:object=VJPubKey;type=public"));
if (!($key instanceof OpenSSLAsymmetricKey)) {
echo "Public Key NOK" . PHP_EOL;
exit(1);
}
echo "Public Key OK" . PHP_EOL;

$cert = openssl_x509_read(sprintf("pkcs11:object=VJCert;type=cert"));
if (!($cert instanceof OpenSSLCertificate)) {
echo "Cert NOK" . PHP_EOL;
exit(1);
}
echo "Cert OK" . PHP_EOL;
$certArray=openssl_x509_parse($cert);

if ($certArray['name'] !== "/CN=MyCertVJ") {
echo "Cert content NOK" . PHP_EOL;
exit(1);
}
echo "Cert content OK" . PHP_EOL;

?>
--EXPECT--
Private Key OK
Public Key OK
Cert OK
Cert content OK
Loading