Skip to content

Bug #38917 - Native SPKAC support request #267

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 16 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
299 changes: 299 additions & 0 deletions ext/openssl/openssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -391,11 +391,35 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_random_pseudo_bytes, 0, 0, 1)
ZEND_ARG_INFO(0, length)
ZEND_ARG_INFO(1, result_is_strong)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_spki_new, 0, 0, 2)
ZEND_ARG_INFO(0, privkey)
ZEND_ARG_INFO(0, challenge)
ZEND_ARG_INFO(0, algo)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO(arginfo_openssl_spki_verify, 0)
ZEND_ARG_INFO(0, spki)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO(arginfo_openssl_spki_export, 0)
ZEND_ARG_INFO(0, spki)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO(arginfo_openssl_spki_export_challenge, 0)
ZEND_ARG_INFO(0, spki)
ZEND_END_ARG_INFO()
/* }}} */

/* {{{ openssl_functions[]
*/
const zend_function_entry openssl_functions[] = {
/* spki functions */
PHP_FE(openssl_spki_new, arginfo_openssl_spki_new)
PHP_FE(openssl_spki_verify, arginfo_openssl_spki_verify)
PHP_FE(openssl_spki_export, arginfo_openssl_spki_export)
PHP_FE(openssl_spki_export_challenge, arginfo_openssl_spki_export_challenge)

/* public/private key functions */
PHP_FE(openssl_pkey_free, arginfo_openssl_pkey_free)
PHP_FE(openssl_pkey_new, arginfo_openssl_pkey_new)
Expand Down Expand Up @@ -790,6 +814,7 @@ static int add_oid_section(struct php_x509_request * req TSRMLS_DC) /* {{{ */

static const EVP_CIPHER * php_openssl_get_evp_cipher_from_algo(long algo);

int openssl_spki_cleanup(const char *src, char *dest);

static int php_openssl_parse_config(struct php_x509_request * req, zval * optional_args TSRMLS_DC) /* {{{ */
{
Expand Down Expand Up @@ -1334,6 +1359,280 @@ PHP_FUNCTION(openssl_x509_export_to_file)
}
/* }}} */

/* {{{ proto string openssl_spki_new(mixed zpkey, string challenge [, mixed method])
Creates new private key (or uses existing) and creates a new spki cert
outputting results to var */
PHP_FUNCTION(openssl_spki_new)
{
int challenge_len;
char * challenge = NULL, * spkstr = NULL, * s = NULL;
long keyresource = -1;
const char *spkac = "SPKAC=";
long algo = OPENSSL_ALGO_MD5;

zval *method = NULL;
zval * zpkey = NULL;
EVP_PKEY * pkey = NULL;
NETSCAPE_SPKI *spki=NULL;
const EVP_MD *mdtype;

if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs|z", &zpkey, &challenge, &challenge_len, &method) == FAILURE) {
return;
}
RETVAL_FALSE;

pkey = php_openssl_evp_from_zval(&zpkey, 0, challenge, 1, &keyresource TSRMLS_CC);

if (pkey == NULL) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to use supplied private key");
goto cleanup;
}

if (method != NULL) {
if (Z_TYPE_P(method) == IS_LONG) {
algo = Z_LVAL_P(method);
} else {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Algorithm must be of supported type");
goto cleanup;
}
}
mdtype = php_openssl_get_evp_md_from_algo(algo);

if (!mdtype) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unknown signature algorithm");
goto cleanup;
}

if ((spki = NETSCAPE_SPKI_new()) == NULL) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to create new SPKAC");
goto cleanup;
}

if (challenge) {
ASN1_STRING_set(spki->spkac->challenge, challenge, (int)strlen(challenge));
}

if (!NETSCAPE_SPKI_set_pubkey(spki, pkey)) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to embed public key");
goto cleanup;
}

if (!NETSCAPE_SPKI_sign(spki, pkey, mdtype)) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to sign with specified algorithm");
goto cleanup;
}

spkstr = NETSCAPE_SPKI_b64_encode(spki);
if (!spkstr){
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to encode SPKAC");
goto cleanup;
}

s = emalloc(strlen(spkac) + strlen(spkstr) + 1);
sprintf(s, "%s%s", spkac, spkstr);

RETVAL_STRINGL(s, strlen(s), 0);
goto cleanup;

cleanup:

if (keyresource == -1 && spki != NULL) {
NETSCAPE_SPKI_free(spki);
}
if (keyresource == -1 && pkey != NULL) {
EVP_PKEY_free(pkey);
}
if (keyresource == -1 && spkstr != NULL) {
efree(spkstr);
}

if (strlen(s) <= 0) {
RETVAL_FALSE;
}

if (keyresource == -1 && s != NULL) {
efree(s);
}
}
/* }}} */

/* {{{ proto bool openssl_spki_verify(string spki)
Verifies spki returns boolean */
PHP_FUNCTION(openssl_spki_verify)
{
int spkstr_len, i = 0;
char *spkstr = NULL, * spkstr_cleaned = NULL;

EVP_PKEY *pkey = NULL;
NETSCAPE_SPKI *spki = NULL;

if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &spkstr, &spkstr_len) == FAILURE) {
return;
}
RETVAL_FALSE;

if (spkstr == NULL) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to use supplied SPKAC");
goto cleanup;
}

spkstr_cleaned = emalloc(spkstr_len + 1);
openssl_spki_cleanup(spkstr, spkstr_cleaned);

if (strlen(spkstr_cleaned)<=0) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid SPKAC");
goto cleanup;
}

spki = NETSCAPE_SPKI_b64_decode(spkstr_cleaned, strlen(spkstr_cleaned));
if (spki == NULL) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to decode supplied SPKAC");
goto cleanup;
}

pkey = X509_PUBKEY_get(spki->spkac->pubkey);
if (pkey == NULL) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to aquire signed public key");
goto cleanup;
}

i = NETSCAPE_SPKI_verify(spki, pkey);
goto cleanup;

cleanup:
if (spki != NULL) {
NETSCAPE_SPKI_free(spki);
}
if (pkey != NULL) {
EVP_PKEY_free(pkey);
}
if (spkstr_cleaned != NULL) {
efree(spkstr_cleaned);
}

if (i > 0) {
RETVAL_TRUE;
}
}
/* }}} */

/* {{{ proto string openssl_spki_export(string spki)
Exports public key from existing spki to var */
PHP_FUNCTION(openssl_spki_export)
{
int spkstr_len;
char *spkstr = NULL, * spkstr_cleaned = NULL, * s = NULL;

EVP_PKEY *pkey = NULL;
NETSCAPE_SPKI *spki = NULL;
BIO *out = BIO_new(BIO_s_mem());
BUF_MEM *bio_buf;

if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &spkstr, &spkstr_len) == FAILURE) {
return;
}
RETVAL_FALSE;

if (spkstr == NULL) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to use supplied SPKAC");
goto cleanup;
}

spkstr_cleaned = emalloc(spkstr_len + 1);
openssl_spki_cleanup(spkstr, spkstr_cleaned);

spki = NETSCAPE_SPKI_b64_decode(spkstr_cleaned, strlen(spkstr_cleaned));
if (spki == NULL) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to decode supplied SPKAC");
goto cleanup;
}

pkey = X509_PUBKEY_get(spki->spkac->pubkey);
if (pkey == NULL) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to aquire signed public key");
goto cleanup;
}

out = BIO_new_fp(stdout, BIO_NOCLOSE);
PEM_write_bio_PUBKEY(out, pkey);
goto cleanup;

cleanup:

if (spki != NULL) {
NETSCAPE_SPKI_free(spki);
}
if (out != NULL) {
BIO_free_all(out);
}
if (pkey != NULL) {
EVP_PKEY_free(pkey);
}
if (spkstr_cleaned != NULL) {
efree(spkstr_cleaned);
}
if (s != NULL) {
efree(s);
}
}
/* }}} */

/* {{{ proto string openssl_spki_export_challenge(string spki)
Exports spkac challenge from existing spki to var */
PHP_FUNCTION(openssl_spki_export_challenge)
{
int spkstr_len;
char *spkstr = NULL, * spkstr_cleaned = NULL;

NETSCAPE_SPKI *spki = NULL;

if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &spkstr, &spkstr_len) == FAILURE) {
return;
}
RETVAL_FALSE;

if (spkstr == NULL) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to use supplied SPKAC");
goto cleanup;
}

spkstr_cleaned = emalloc(spkstr_len + 1);
openssl_spki_cleanup(spkstr, spkstr_cleaned);

spki = NETSCAPE_SPKI_b64_decode(spkstr_cleaned, strlen(spkstr_cleaned));
if (spki == NULL) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to decode SPKAC");
goto cleanup;
}

RETVAL_STRING(ASN1_STRING_data(spki->spkac->challenge), 1);
goto cleanup;

cleanup:
if (spkstr_cleaned != NULL) {
efree(spkstr_cleaned);
}
}
/* }}} */

/* {{{ strip line endings from spkac */
int openssl_spki_cleanup(const char *src, char *dest)
{
int removed=0;

while (*src) {
if (*src!='\n'&&*src!='\r') {
*dest++=*src;
} else {
++removed;
}
++src;
}
*dest=0;
return removed;
}
/* }}} */

/* {{{ proto bool openssl_x509_export(mixed x509, string &out [, bool notext = true])
Exports a CERT to file or a var */
PHP_FUNCTION(openssl_x509_export)
Expand Down
5 changes: 5 additions & 0 deletions ext/openssl/php_openssl.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ PHP_FUNCTION(openssl_csr_export_to_file);
PHP_FUNCTION(openssl_csr_sign);
PHP_FUNCTION(openssl_csr_get_subject);
PHP_FUNCTION(openssl_csr_get_public_key);

PHP_FUNCTION(openssl_spki_new);
PHP_FUNCTION(openssl_spki_verify);
PHP_FUNCTION(openssl_spki_export);
PHP_FUNCTION(openssl_spki_export_challenge);
#else

#define phpext_openssl_ptr NULL
Expand Down
62 changes: 62 additions & 0 deletions ext/openssl/tests/openssl_spki_export.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
--TEST--
Testing openssl_spki_export()
Creates SPKAC for all available key sizes & signature algorithms and exports public key
--SKIPIF--
<?php
if (!extension_loaded("openssl")) die("skip");
if (!@openssl_pkey_new()) die("skip cannot create private key");
?>
--FILE--
<?php

/* array of private key sizes to test */
$ksize = array('1024'=>1024,
'2048'=>2048,
'4096'=>4096);

/* array of available hashings to test */
$algo = array('md4'=>OPENSSL_ALGO_MD4,
'md5'=>OPENSSL_ALGO_MD5,
'sha1'=>OPENSSL_ALGO_SHA1,
'sha224'=>OPENSSL_ALGO_SHA224,
'sha256'=>OPENSSL_ALGO_SHA256,
'sha384'=>OPENSSL_ALGO_SHA384,
'sha512'=>OPENSSL_ALGO_SHA512,
'rmd160'=>OPENSSL_ALGO_RMD160);

/* loop over key sizes for test */
foreach($ksize as $k => $v) {

/* generate new private key of specified size to use for tests */
$pkey = openssl_pkey_new(array('digest_alg' => 'sha512',
'private_key_type' => OPENSSL_KEYTYPE_RSA,
'private_key_bits' => $v));
openssl_pkey_export($pkey, $pass);

/* loop to create and verify results */
foreach($algo as $key => $value) {
$spkac = openssl_spki_new($pkey, _uuid(), $value);
echo openssl_spki_export(preg_replace('/SPKAC=/', '', $spkac));
}
openssl_free_key($pkey);
}

/* generate a random challenge */
function _uuid()
{
return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', mt_rand(0, 0xffff),
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0x0fff) | 0x4000,
mt_rand(0, 0x3fff) | 0x8000, mt_rand(0, 0xffff),
mt_rand(0, 0xffff), mt_rand(0, 0xffff));
}

?>
--EXPECTREGEX--
\-\-\-\-\-BEGIN PUBLIC KEY\-\-\-\-\-.*\-\-\-\-\-END PUBLIC KEY\-\-\-\-\-
\-\-\-\-\-BEGIN PUBLIC KEY\-\-\-\-\-.*\-\-\-\-\-END PUBLIC KEY\-\-\-\-\-
\-\-\-\-\-BEGIN PUBLIC KEY\-\-\-\-\-.*\-\-\-\-\-END PUBLIC KEY\-\-\-\-\-
\-\-\-\-\-BEGIN PUBLIC KEY\-\-\-\-\-.*\-\-\-\-\-END PUBLIC KEY\-\-\-\-\-
\-\-\-\-\-BEGIN PUBLIC KEY\-\-\-\-\-.*\-\-\-\-\-END PUBLIC KEY\-\-\-\-\-
\-\-\-\-\-BEGIN PUBLIC KEY\-\-\-\-\-.*\-\-\-\-\-END PUBLIC KEY\-\-\-\-\-
\-\-\-\-\-BEGIN PUBLIC KEY\-\-\-\-\-.*\-\-\-\-\-END PUBLIC KEY\-\-\-\-\-
\-\-\-\-\-BEGIN PUBLIC KEY\-\-\-\-\-.*\-\-\-\-\-END PUBLIC KEY\-\-\-\-\-
Loading