diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index 2c09b89e31200..23b011d761860 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -1998,6 +1998,183 @@ PHP_FUNCTION(openssl_csr_get_public_key) } /* }}} */ +/* Returns an array of the fields/values of the Certificate Request */ +PHP_FUNCTION(openssl_csr_parse) +{ + X509_REQ * csr = NULL; + zend_object *csr_obj; + zend_string *csr_str; + int i, sig_nid; + bool useshortnames = 1; + zval subitem; + zval critext; + int critcount = 0; + X509_EXTENSION *extension; + X509_NAME *subject_name; + char *csr_name; + char *extname; + BIO *bio_out; + BUF_MEM *bio_buf; + char buf[256]; + STACK_OF(X509_EXTENSION) *exts = NULL; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_OBJ_OF_CLASS_OR_STR(csr_obj, php_openssl_request_ce, csr_str) + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(useshortnames) + ZEND_PARSE_PARAMETERS_END(); + + csr = php_openssl_csr_from_param(csr_obj, csr_str, 1); + if (csr == NULL) { + php_error_docref(NULL, E_WARNING, "First parameter must be a valid CSR"); + RETURN_FALSE; + } + array_init(return_value); + + subject_name = X509_REQ_get_subject_name(csr); + csr_name = X509_NAME_oneline(subject_name, NULL, 0); + if (csr_name) { + add_assoc_string(return_value, "name", csr_name); + OPENSSL_free(csr_name); + } + + php_openssl_add_assoc_name_entry(return_value, "subject", subject_name, useshortnames); + /* hash as used in CA directories to lookup csr by subject name */ + { + char buf[32]; + snprintf(buf, sizeof(buf), "%08lx", X509_NAME_hash_ex(subject_name, NULL, NULL, NULL)); + add_assoc_string(return_value, "hash", buf); + } + + add_assoc_long(return_value, "version", X509_REQ_get_version(csr)); + + sig_nid = X509_REQ_get_signature_nid(csr); + add_assoc_string(return_value, "signatureTypeSN", (char*)OBJ_nid2sn(sig_nid)); + add_assoc_string(return_value, "signatureTypeLN", (char*)OBJ_nid2ln(sig_nid)); + add_assoc_long(return_value, "signatureTypeNID", sig_nid); + + array_init(&subitem); + array_init(&critext); + + int attrcnt = X509_REQ_get_attr_count(csr); + if (attrcnt > 0) { + const char unknown[] = "Unknown"; + for (i = 0; i < attrcnt; i++) { + X509_ATTRIBUTE *attr = X509_REQ_get_attr(csr,i); + if (attr) { + char objbuf[80]; + /* Adapted from openssl's "req" app */ + ASN1_TYPE *at; + ASN1_BIT_STRING *bs = NULL; + ASN1_OBJECT *aobj; + int j, type = 0, count = 1, ii = 0; + + aobj = X509_ATTRIBUTE_get0_object(attr); + if (X509_REQ_extension_nid(OBJ_obj2nid(aobj))) + continue; + if ((j = i2t_ASN1_OBJECT(objbuf, sizeof(objbuf), aobj)) > 0) { + ii = 0; + count = X509_ATTRIBUTE_count(attr); + if (count == 0) { + goto err_subitem; + } +get_next: + at = X509_ATTRIBUTE_get0_type(attr, ii); + type = at->type; + bs = at->value.asn1_string; + } else { + strcpy(objbuf, unknown); + } + switch (type) { + case V_ASN1_PRINTABLESTRING: + case V_ASN1_T61STRING: + case V_ASN1_NUMERICSTRING: + case V_ASN1_UTF8STRING: + case V_ASN1_IA5STRING: + add_assoc_stringl(&subitem, objbuf, (char *)bs->data, bs->length); + break; + default: + add_assoc_stringl(&subitem, objbuf, unknown, sizeof(unknown)); + break; + } + if (++ii < count) + goto get_next; + + } + } + add_assoc_zval(return_value, "attributes", &subitem); + } + + array_init(&subitem); + exts = X509_REQ_get_extensions(csr); + if (exts) { + int count = sk_X509_EXTENSION_num(exts); + for (i = 0; i < count; i++) { + int nid; + extension = sk_X509_EXTENSION_value(exts, i); + nid = OBJ_obj2nid(X509_EXTENSION_get_object(extension)); + if (nid != NID_undef) { + extname = (char *)OBJ_nid2sn(nid); + } else { + if (OBJ_obj2txt(buf, sizeof(buf)-1, X509_EXTENSION_get_object(extension), 1) < 0) { + php_openssl_store_errors(); + goto err_subitem; + } + extname = buf; + } + if (X509_EXTENSION_get_critical(extension)) { + add_next_index_string(&critext, extname); + critcount++; + } + bio_out = BIO_new(BIO_s_mem()); + if (bio_out == NULL) { + php_openssl_store_errors(); + goto err_subitem; + } + if (nid == NID_subject_alt_name) { + if (openssl_x509v3_subjectAltName(bio_out, extension) == 0) { + BIO_get_mem_ptr(bio_out, &bio_buf); + add_assoc_stringl(&subitem, extname, bio_buf->data, bio_buf->length); + } else { + BIO_free(bio_out); + goto err_subitem; + } + } + else if (X509V3_EXT_print(bio_out, extension, 0, 0)) { + BIO_get_mem_ptr(bio_out, &bio_buf); + add_assoc_stringl(&subitem, extname, bio_buf->data, bio_buf->length); + } else { + php_openssl_add_assoc_asn1_string(&subitem, extname, X509_EXTENSION_get_data(extension)); + } + BIO_free(bio_out); + } + add_assoc_zval(return_value, "extensions", &subitem); + if (critcount > 0) { + add_assoc_zval(return_value, "criticalExtensions", &critext); + } else { + zval_ptr_dtor(&critext); + } + sk_X509_EXTENSION_pop_free(exts, X509_EXTENSION_free); + exts = NULL; + } + if (csr) { + X509_REQ_free(csr); + } + return; + +err_subitem: + zval_ptr_dtor(&subitem); + zval_ptr_dtor(&critext); + zend_array_destroy(Z_ARR_P(return_value)); + if (csr) { + X509_REQ_free(csr); + } + if (exts) { + sk_X509_EXTENSION_pop_free(exts, X509_EXTENSION_free); + } + RETURN_FALSE; +} + /* }}} */ /* {{{ EVP Public/Private key functions */ diff --git a/ext/openssl/openssl.stub.php b/ext/openssl/openssl.stub.php index 94902a4acf0da..04a13d177f020 100644 --- a/ext/openssl/openssl.stub.php +++ b/ext/openssl/openssl.stub.php @@ -501,6 +501,8 @@ function openssl_csr_get_subject(OpenSSLCertificateSigningRequest|string $csr, b function openssl_csr_get_public_key(OpenSSLCertificateSigningRequest|string $csr, bool $short_names = true): OpenSSLAsymmetricKey|false {} +function openssl_csr_parse(OpenSSLCertificateSigningRequest|string $csr, bool $short_names = true): array|false {} + function openssl_pkey_new(?array $options = null): OpenSSLAsymmetricKey|false {} /** diff --git a/ext/openssl/openssl_arginfo.h b/ext/openssl/openssl_arginfo.h index 796582c185bb6..18ca4f5080fd0 100644 --- a/ext/openssl/openssl_arginfo.h +++ b/ext/openssl/openssl_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 8233a8abc8ab7145d905d0fa51478edfe1e55a06 */ + * Stub hash: 7eeae72288dfe690115b837ea8ed372f9c3a734b */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_openssl_x509_export_to_file, 0, 2, _IS_BOOL, 0) ZEND_ARG_OBJ_TYPE_MASK(0, certificate, OpenSSLCertificate, MAY_BE_STRING, NULL) @@ -110,6 +110,8 @@ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_openssl_csr_get_public_key, ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, short_names, _IS_BOOL, 0, "true") ZEND_END_ARG_INFO() +#define arginfo_openssl_csr_parse arginfo_openssl_csr_get_subject + ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_openssl_pkey_new, 0, 0, OpenSSLAsymmetricKey, MAY_BE_FALSE) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, options, IS_ARRAY, 1, "null") ZEND_END_ARG_INFO() @@ -424,6 +426,7 @@ ZEND_FUNCTION(openssl_csr_sign); ZEND_FUNCTION(openssl_csr_new); ZEND_FUNCTION(openssl_csr_get_subject); ZEND_FUNCTION(openssl_csr_get_public_key); +ZEND_FUNCTION(openssl_csr_parse); ZEND_FUNCTION(openssl_pkey_new); ZEND_FUNCTION(openssl_pkey_export_to_file); ZEND_FUNCTION(openssl_pkey_export); @@ -493,6 +496,7 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(openssl_csr_new, arginfo_openssl_csr_new) ZEND_FE(openssl_csr_get_subject, arginfo_openssl_csr_get_subject) ZEND_FE(openssl_csr_get_public_key, arginfo_openssl_csr_get_public_key) + ZEND_FE(openssl_csr_parse, arginfo_openssl_csr_parse) ZEND_FE(openssl_pkey_new, arginfo_openssl_pkey_new) ZEND_FE(openssl_pkey_export_to_file, arginfo_openssl_pkey_export_to_file) ZEND_FE(openssl_pkey_export, arginfo_openssl_pkey_export) diff --git a/ext/openssl/tests/openssl_csr_parse_basic.phpt b/ext/openssl/tests/openssl_csr_parse_basic.phpt new file mode 100644 index 0000000000000..231368a5de665 --- /dev/null +++ b/ext/openssl/tests/openssl_csr_parse_basic.phpt @@ -0,0 +1,117 @@ +--TEST-- +openssl_csr_parse() tests +--EXTENSIONS-- +openssl +--FILE-- + +--EXPECT-- +bool(true) +array(9) { + ["name"]=> + string(71) "/C=UK/ST=England/L=London/CN=test.php.net/emailAddress=test.php@php.net" + ["subject"]=> + array(5) { + ["C"]=> + string(2) "UK" + ["ST"]=> + string(7) "England" + ["L"]=> + string(6) "London" + ["CN"]=> + string(12) "test.php.net" + ["emailAddress"]=> + string(16) "test.php@php.net" + } + ["hash"]=> + string(8) "b21872c1" + ["version"]=> + int(0) + ["signatureTypeSN"]=> + string(10) "RSA-SHA256" + ["signatureTypeLN"]=> + string(23) "sha256WithRSAEncryption" + ["signatureTypeNID"]=> + int(668) + ["attributes"]=> + array(8) { + ["streetAddress"]=> + string(0) "" + ["facsimileTelephoneNumber"]=> + string(0) "" + ["postalCode"]=> + string(3) "N11" + ["telephoneNumber"]=> + string(9) "012345678" + ["name"]=> + string(12) "Organisation" + ["1.3.6.1.4.1.11278.1150.2.1"]=> + string(8) "11112222" + ["1.3.6.1.4.1.11278.1150.2.2"]=> + string(8) "12345678" + ["emailAddress"]=> + string(16) "info@example.com" + } + ["extensions"]=> + array(1) { + ["basicConstraints"]=> + string(8) "CA:FALSE" + } +} +array(9) { + ["name"]=> + string(71) "/C=UK/ST=England/L=London/CN=test.php.net/emailAddress=test.php@php.net" + ["subject"]=> + array(5) { + ["countryName"]=> + string(2) "UK" + ["stateOrProvinceName"]=> + string(7) "England" + ["localityName"]=> + string(6) "London" + ["commonName"]=> + string(12) "test.php.net" + ["emailAddress"]=> + string(16) "test.php@php.net" + } + ["hash"]=> + string(8) "b21872c1" + ["version"]=> + int(0) + ["signatureTypeSN"]=> + string(10) "RSA-SHA256" + ["signatureTypeLN"]=> + string(23) "sha256WithRSAEncryption" + ["signatureTypeNID"]=> + int(668) + ["attributes"]=> + array(8) { + ["streetAddress"]=> + string(0) "" + ["facsimileTelephoneNumber"]=> + string(0) "" + ["postalCode"]=> + string(3) "N11" + ["telephoneNumber"]=> + string(9) "012345678" + ["name"]=> + string(12) "Organisation" + ["1.3.6.1.4.1.11278.1150.2.1"]=> + string(8) "11112222" + ["1.3.6.1.4.1.11278.1150.2.2"]=> + string(8) "12345678" + ["emailAddress"]=> + string(16) "info@example.com" + } + ["extensions"]=> + array(1) { + ["basicConstraints"]=> + string(8) "CA:FALSE" + } +} diff --git a/ext/openssl/tests/parse.csr b/ext/openssl/tests/parse.csr new file mode 100644 index 0000000000000..7a9b5c94b53c0 --- /dev/null +++ b/ext/openssl/tests/parse.csr @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIDcDCCAlgCAQAwaDELMAkGA1UEBhMCVUsxEDAOBgNVBAgMB0VuZ2xhbmQxDzAN +BgNVBAcMBkxvbmRvbjEVMBMGA1UEAwwMdGVzdC5waHAubmV0MR8wHQYJKoZIhvcN +AQkBFhB0ZXN0LnBocEBwaHAubmV0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEArbUmVW1Y+rJzZRC3DYB0kdIgvk7MAday78ybGPPDhVlbAb4CjWbaPs4n +yUCTEt9KVG0H7pXHxDbWSsC2974zdvqlP0L2op1/M2SteTcGCBOdwGH2jORVAZL8 +/WbTOf9IpKAM77oN14scsyOlQBJqhh+xrLg8ksB2dOos54yDqo0Tq7R5tldV+alK +ZXWlJnqRCfFuxvqtfWI5nGTAedVZhvjQfLQQgujfXHoFWoGbXn2buzfwKGJEeqWP +bQOZF/FeOJPlgOBhhDb3BAFNVCtM3k71Rblj54pNd3yvq152xsgFd0o3s15fuSwZ +gerUjeEuw/wTK9k7vyp+MrIQHQmPdQIDAQABoIHCMAkGA1UECTECDAAwCQYDVQQX +MQIMADAMBgNVBBExBQwDTjExMBIGA1UEFDELDAkwMTIzNDU2NzgwFQYDVQQpMQ4M +DE9yZ2FuaXNhdGlvbjAZBgsrBgEEAdgOiH4CATEKDAgxMTExMjIyMjAZBgsrBgEE +AdgOiH4CAjEKDAgxMjM0NTY3ODAaBgkqhkiG9w0BCQ4xDTALMAkGA1UdEwQCMAAw +HwYJKoZIhvcNAQkBMRIWEGluZm9AZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQAD +ggEBAAoPI/sWY0QKPMEBuRp6MHcvWgSExwkkQfRJQZlYdepu6Tw0iZwYRTOR4sEn +Vz95qsrWqHp6QkXxdFG9FPHi4N66OX2Xb5TtHgDGMxrJTwbH+7VdsJiXLkWbeLuo +zKv8BsrhLRYiZkl+VWIrNyOcK7ao2sD+D3YkCBA4JK4OFhfhxY43D2sme7aEQVjr +S+UvEjuIALN0AP6gO2AMiUODPBrjsPI3NpN40VUvVU+Hsp1Tlqvth/AYASuGT2yt +M5YdcSm7JwaGAwIgOv8XPUQGem52yMEvzySRC4ZyTddfiZAkeTLmbh+SMVbHXXOk +UeEz+fvmQ4L+sc3RE8u+M8g31LM= +-----END CERTIFICATE REQUEST-----