Skip to content

Commit 8f56ac8

Browse files
committed
Address feature request #38917 for native SPKAC (HTML5 keygen element) support
1 parent da07e91 commit 8f56ac8

6 files changed

+653
-0
lines changed

ext/openssl/openssl.c

+299
Original file line numberDiff line numberDiff line change
@@ -391,11 +391,35 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_random_pseudo_bytes, 0, 0, 1)
391391
ZEND_ARG_INFO(0, length)
392392
ZEND_ARG_INFO(1, result_is_strong)
393393
ZEND_END_ARG_INFO()
394+
395+
ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_spki_new, 0, 0, 2)
396+
ZEND_ARG_INFO(0, privkey)
397+
ZEND_ARG_INFO(0, challenge)
398+
ZEND_ARG_INFO(0, algo)
399+
ZEND_END_ARG_INFO()
400+
401+
ZEND_BEGIN_ARG_INFO(arginfo_openssl_spki_verify, 0)
402+
ZEND_ARG_INFO(0, spki)
403+
ZEND_END_ARG_INFO()
404+
405+
ZEND_BEGIN_ARG_INFO(arginfo_openssl_spki_export, 0)
406+
ZEND_ARG_INFO(0, spki)
407+
ZEND_END_ARG_INFO()
408+
409+
ZEND_BEGIN_ARG_INFO(arginfo_openssl_spki_export_challenge, 0)
410+
ZEND_ARG_INFO(0, spki)
411+
ZEND_END_ARG_INFO()
394412
/* }}} */
395413

396414
/* {{{ openssl_functions[]
397415
*/
398416
const zend_function_entry openssl_functions[] = {
417+
/* spki functions */
418+
PHP_FE(openssl_spki_new, arginfo_openssl_spki_new)
419+
PHP_FE(openssl_spki_verify, arginfo_openssl_spki_verify)
420+
PHP_FE(openssl_spki_export, arginfo_openssl_spki_export)
421+
PHP_FE(openssl_spki_export_challenge, arginfo_openssl_spki_export_challenge)
422+
399423
/* public/private key functions */
400424
PHP_FE(openssl_pkey_free, arginfo_openssl_pkey_free)
401425
PHP_FE(openssl_pkey_new, arginfo_openssl_pkey_new)
@@ -790,6 +814,7 @@ static int add_oid_section(struct php_x509_request * req TSRMLS_DC) /* {{{ */
790814

791815
static const EVP_CIPHER * php_openssl_get_evp_cipher_from_algo(long algo);
792816

817+
int openssl_spki_cleanup(const char *src, char *dest);
793818

794819
static int php_openssl_parse_config(struct php_x509_request * req, zval * optional_args TSRMLS_DC) /* {{{ */
795820
{
@@ -1334,6 +1359,280 @@ PHP_FUNCTION(openssl_x509_export_to_file)
13341359
}
13351360
/* }}} */
13361361

1362+
/* {{{ proto string openssl_spki_new(mixed zpkey, string challenge [, mixed method])
1363+
Creates new private key (or uses existing) and creates a new spki cert
1364+
outputting results to var */
1365+
PHP_FUNCTION(openssl_spki_new)
1366+
{
1367+
int challenge_len;
1368+
char * challenge = NULL, * spkstr = NULL, * s = NULL;
1369+
long keyresource = -1;
1370+
const char *spkac = "SPKAC=";
1371+
long algo = OPENSSL_ALGO_MD5;
1372+
1373+
zval *method = NULL;
1374+
zval * zpkey = NULL;
1375+
EVP_PKEY * pkey = NULL;
1376+
NETSCAPE_SPKI *spki=NULL;
1377+
const EVP_MD *mdtype;
1378+
1379+
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs|z", &zpkey, &challenge, &challenge_len, &method) == FAILURE) {
1380+
return;
1381+
}
1382+
RETVAL_FALSE;
1383+
1384+
pkey = php_openssl_evp_from_zval(&zpkey, 0, challenge, 1, &keyresource TSRMLS_CC);
1385+
1386+
if (pkey == NULL) {
1387+
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to use supplied private key");
1388+
goto cleanup;
1389+
}
1390+
1391+
if (method != NULL) {
1392+
if (Z_TYPE_P(method) == IS_LONG) {
1393+
algo = Z_LVAL_P(method);
1394+
} else {
1395+
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Algorithm must be of supported type");
1396+
goto cleanup;
1397+
}
1398+
}
1399+
mdtype = php_openssl_get_evp_md_from_algo(algo);
1400+
1401+
if (!mdtype) {
1402+
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unknown signature algorithm");
1403+
goto cleanup;
1404+
}
1405+
1406+
if ((spki = NETSCAPE_SPKI_new()) == NULL) {
1407+
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to create new SPKAC");
1408+
goto cleanup;
1409+
}
1410+
1411+
if (challenge) {
1412+
ASN1_STRING_set(spki->spkac->challenge, challenge, (int)strlen(challenge));
1413+
}
1414+
1415+
if (!NETSCAPE_SPKI_set_pubkey(spki, pkey)) {
1416+
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to embed public key");
1417+
goto cleanup;
1418+
}
1419+
1420+
if (!NETSCAPE_SPKI_sign(spki, pkey, mdtype)) {
1421+
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to sign with specified algorithm");
1422+
goto cleanup;
1423+
}
1424+
1425+
spkstr = NETSCAPE_SPKI_b64_encode(spki);
1426+
if (!spkstr){
1427+
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to encode SPKAC");
1428+
goto cleanup;
1429+
}
1430+
1431+
s = emalloc(strlen(spkac) + strlen(spkstr) + 1);
1432+
sprintf(s, "%s%s", spkac, spkstr);
1433+
1434+
RETVAL_STRINGL(s, strlen(s), 0);
1435+
goto cleanup;
1436+
1437+
cleanup:
1438+
1439+
if (keyresource == -1 && spki != NULL) {
1440+
NETSCAPE_SPKI_free(spki);
1441+
}
1442+
if (keyresource == -1 && pkey != NULL) {
1443+
EVP_PKEY_free(pkey);
1444+
}
1445+
if (keyresource == -1 && spkstr != NULL) {
1446+
efree(spkstr);
1447+
}
1448+
1449+
if (strlen(s) <= 0) {
1450+
RETVAL_FALSE;
1451+
}
1452+
1453+
if (keyresource == -1 && s != NULL) {
1454+
efree(s);
1455+
}
1456+
}
1457+
/* }}} */
1458+
1459+
/* {{{ proto bool openssl_spki_verify(string spki)
1460+
Verifies spki returns boolean */
1461+
PHP_FUNCTION(openssl_spki_verify)
1462+
{
1463+
int spkstr_len, i = 0;
1464+
char *spkstr = NULL, * spkstr_cleaned = NULL;
1465+
1466+
EVP_PKEY *pkey = NULL;
1467+
NETSCAPE_SPKI *spki = NULL;
1468+
1469+
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &spkstr, &spkstr_len) == FAILURE) {
1470+
return;
1471+
}
1472+
RETVAL_FALSE;
1473+
1474+
if (spkstr == NULL) {
1475+
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to use supplied SPKAC");
1476+
goto cleanup;
1477+
}
1478+
1479+
spkstr_cleaned = emalloc(spkstr_len + 1);
1480+
openssl_spki_cleanup(spkstr, spkstr_cleaned);
1481+
1482+
if (strlen(spkstr_cleaned)<=0) {
1483+
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid SPKAC");
1484+
goto cleanup;
1485+
}
1486+
1487+
spki = NETSCAPE_SPKI_b64_decode(spkstr_cleaned, strlen(spkstr_cleaned));
1488+
if (spki == NULL) {
1489+
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to decode supplied SPKAC");
1490+
goto cleanup;
1491+
}
1492+
1493+
pkey = X509_PUBKEY_get(spki->spkac->pubkey);
1494+
if (pkey == NULL) {
1495+
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to aquire signed public key");
1496+
goto cleanup;
1497+
}
1498+
1499+
i = NETSCAPE_SPKI_verify(spki, pkey);
1500+
goto cleanup;
1501+
1502+
cleanup:
1503+
if (spki != NULL) {
1504+
NETSCAPE_SPKI_free(spki);
1505+
}
1506+
if (pkey != NULL) {
1507+
EVP_PKEY_free(pkey);
1508+
}
1509+
if (spkstr_cleaned != NULL) {
1510+
efree(spkstr_cleaned);
1511+
}
1512+
1513+
if (i > 0) {
1514+
RETVAL_TRUE;
1515+
}
1516+
}
1517+
/* }}} */
1518+
1519+
/* {{{ proto string openssl_spki_export(string spki)
1520+
Exports public key from existing spki to var */
1521+
PHP_FUNCTION(openssl_spki_export)
1522+
{
1523+
int spkstr_len;
1524+
char *spkstr = NULL, * spkstr_cleaned = NULL, * s = NULL;
1525+
1526+
EVP_PKEY *pkey = NULL;
1527+
NETSCAPE_SPKI *spki = NULL;
1528+
BIO *out = BIO_new(BIO_s_mem());
1529+
BUF_MEM *bio_buf;
1530+
1531+
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &spkstr, &spkstr_len) == FAILURE) {
1532+
return;
1533+
}
1534+
RETVAL_FALSE;
1535+
1536+
if (spkstr == NULL) {
1537+
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to use supplied SPKAC");
1538+
goto cleanup;
1539+
}
1540+
1541+
spkstr_cleaned = emalloc(spkstr_len + 1);
1542+
openssl_spki_cleanup(spkstr, spkstr_cleaned);
1543+
1544+
spki = NETSCAPE_SPKI_b64_decode(spkstr_cleaned, strlen(spkstr_cleaned));
1545+
if (spki == NULL) {
1546+
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to decode supplied SPKAC");
1547+
goto cleanup;
1548+
}
1549+
1550+
pkey = X509_PUBKEY_get(spki->spkac->pubkey);
1551+
if (pkey == NULL) {
1552+
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to aquire signed public key");
1553+
goto cleanup;
1554+
}
1555+
1556+
out = BIO_new_fp(stdout, BIO_NOCLOSE);
1557+
PEM_write_bio_PUBKEY(out, pkey);
1558+
goto cleanup;
1559+
1560+
cleanup:
1561+
1562+
if (spki != NULL) {
1563+
NETSCAPE_SPKI_free(spki);
1564+
}
1565+
if (out != NULL) {
1566+
BIO_free_all(out);
1567+
}
1568+
if (pkey != NULL) {
1569+
EVP_PKEY_free(pkey);
1570+
}
1571+
if (spkstr_cleaned != NULL) {
1572+
efree(spkstr_cleaned);
1573+
}
1574+
if (s != NULL) {
1575+
efree(s);
1576+
}
1577+
}
1578+
/* }}} */
1579+
1580+
/* {{{ proto string openssl_spki_export_challenge(string spki)
1581+
Exports spkac challenge from existing spki to var */
1582+
PHP_FUNCTION(openssl_spki_export_challenge)
1583+
{
1584+
int spkstr_len;
1585+
char *spkstr = NULL, * spkstr_cleaned = NULL;
1586+
1587+
NETSCAPE_SPKI *spki = NULL;
1588+
1589+
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &spkstr, &spkstr_len) == FAILURE) {
1590+
return;
1591+
}
1592+
RETVAL_FALSE;
1593+
1594+
if (spkstr == NULL) {
1595+
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to use supplied SPKAC");
1596+
goto cleanup;
1597+
}
1598+
1599+
spkstr_cleaned = emalloc(spkstr_len + 1);
1600+
openssl_spki_cleanup(spkstr, spkstr_cleaned);
1601+
1602+
spki = NETSCAPE_SPKI_b64_decode(spkstr_cleaned, strlen(spkstr_cleaned));
1603+
if (spki == NULL) {
1604+
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to decode SPKAC");
1605+
goto cleanup;
1606+
}
1607+
1608+
RETVAL_STRING(ASN1_STRING_data(spki->spkac->challenge), 1);
1609+
goto cleanup;
1610+
1611+
cleanup:
1612+
if (spkstr_cleaned != NULL) {
1613+
efree(spkstr_cleaned);
1614+
}
1615+
}
1616+
/* }}} */
1617+
1618+
/* {{{ strip line endings from spkac */
1619+
int openssl_spki_cleanup(const char *src, char *dest)
1620+
{
1621+
int removed=0;
1622+
1623+
while (*src) {
1624+
if (*src!='\n'&&*src!='\r') {
1625+
*dest++=*src;
1626+
} else {
1627+
++removed;
1628+
}
1629+
++src;
1630+
}
1631+
*dest=0;
1632+
return removed;
1633+
}
1634+
/* }}} */
1635+
13371636
/* {{{ proto bool openssl_x509_export(mixed x509, string &out [, bool notext = true])
13381637
Exports a CERT to file or a var */
13391638
PHP_FUNCTION(openssl_x509_export)

ext/openssl/php_openssl.h

+5
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ PHP_FUNCTION(openssl_csr_export_to_file);
7979
PHP_FUNCTION(openssl_csr_sign);
8080
PHP_FUNCTION(openssl_csr_get_subject);
8181
PHP_FUNCTION(openssl_csr_get_public_key);
82+
83+
PHP_FUNCTION(openssl_spki_new);
84+
PHP_FUNCTION(openssl_spki_verify);
85+
PHP_FUNCTION(openssl_spki_export);
86+
PHP_FUNCTION(openssl_spki_export_challenge);
8287
#else
8388

8489
#define phpext_openssl_ptr NULL
+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
--TEST--
2+
Testing openssl_spki_export()
3+
Creates SPKAC for all available key sizes & signature algorithms and exports public key
4+
--SKIPIF--
5+
<?php
6+
if (!extension_loaded("openssl")) die("skip");
7+
if (!@openssl_pkey_new()) die("skip cannot create private key");
8+
?>
9+
--FILE--
10+
<?php
11+
12+
/* array of private key sizes to test */
13+
$ksize = array('1024'=>1024,
14+
'2048'=>2048,
15+
'4096'=>4096);
16+
17+
/* array of available hashings to test */
18+
$algo = array('md4'=>OPENSSL_ALGO_MD4,
19+
'md5'=>OPENSSL_ALGO_MD5,
20+
'sha1'=>OPENSSL_ALGO_SHA1,
21+
'sha224'=>OPENSSL_ALGO_SHA224,
22+
'sha256'=>OPENSSL_ALGO_SHA256,
23+
'sha384'=>OPENSSL_ALGO_SHA384,
24+
'sha512'=>OPENSSL_ALGO_SHA512,
25+
'rmd160'=>OPENSSL_ALGO_RMD160);
26+
27+
/* loop over key sizes for test */
28+
foreach($ksize as $k => $v) {
29+
30+
/* generate new private key of specified size to use for tests */
31+
$pkey = openssl_pkey_new(array('digest_alg' => 'sha512',
32+
'private_key_type' => OPENSSL_KEYTYPE_RSA,
33+
'private_key_bits' => $v));
34+
openssl_pkey_export($pkey, $pass);
35+
36+
/* loop to create and verify results */
37+
foreach($algo as $key => $value) {
38+
$spkac = openssl_spki_new($pkey, _uuid(), $value);
39+
echo openssl_spki_export(preg_replace('/SPKAC=/', '', $spkac));
40+
}
41+
openssl_free_key($pkey);
42+
}
43+
44+
/* generate a random challenge */
45+
function _uuid()
46+
{
47+
return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', mt_rand(0, 0xffff),
48+
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0x0fff) | 0x4000,
49+
mt_rand(0, 0x3fff) | 0x8000, mt_rand(0, 0xffff),
50+
mt_rand(0, 0xffff), mt_rand(0, 0xffff));
51+
}
52+
53+
?>
54+
--EXPECTREGEX--
55+
\-\-\-\-\-BEGIN PUBLIC KEY\-\-\-\-\-.*\-\-\-\-\-END PUBLIC KEY\-\-\-\-\-
56+
\-\-\-\-\-BEGIN PUBLIC KEY\-\-\-\-\-.*\-\-\-\-\-END PUBLIC KEY\-\-\-\-\-
57+
\-\-\-\-\-BEGIN PUBLIC KEY\-\-\-\-\-.*\-\-\-\-\-END PUBLIC KEY\-\-\-\-\-
58+
\-\-\-\-\-BEGIN PUBLIC KEY\-\-\-\-\-.*\-\-\-\-\-END PUBLIC KEY\-\-\-\-\-
59+
\-\-\-\-\-BEGIN PUBLIC KEY\-\-\-\-\-.*\-\-\-\-\-END PUBLIC KEY\-\-\-\-\-
60+
\-\-\-\-\-BEGIN PUBLIC KEY\-\-\-\-\-.*\-\-\-\-\-END PUBLIC KEY\-\-\-\-\-
61+
\-\-\-\-\-BEGIN PUBLIC KEY\-\-\-\-\-.*\-\-\-\-\-END PUBLIC KEY\-\-\-\-\-
62+
\-\-\-\-\-BEGIN PUBLIC KEY\-\-\-\-\-.*\-\-\-\-\-END PUBLIC KEY\-\-\-\-\-

0 commit comments

Comments
 (0)