diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index 2c09b89e31200..a332e152da186 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -1014,6 +1014,7 @@ PHP_FUNCTION(openssl_x509_parse) char *str_serial; char *hex_serial; char buf[256]; + zval altname; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_OBJ_OF_CLASS_OR_STR(cert_obj, php_openssl_certificate_ce, cert_str) @@ -1033,7 +1034,8 @@ PHP_FUNCTION(openssl_x509_parse) add_assoc_string(return_value, "name", cert_name); OPENSSL_free(cert_name); - php_openssl_add_assoc_name_entry(return_value, "subject", subject_name, useshortnames); + php_openssl_add_assoc_name_entry(return_value, "subject", subject_name, useshortnames ? + PHP_OPENSSL_SHORT_NAME : PHP_OPENSSL_LONG_NAME); /* hash as used in CA directories to lookup cert by subject name */ { char buf[32]; @@ -1041,7 +1043,8 @@ PHP_FUNCTION(openssl_x509_parse) add_assoc_string(return_value, "hash", buf); } - php_openssl_add_assoc_name_entry(return_value, "issuer", X509_get_issuer_name(cert), useshortnames); + php_openssl_add_assoc_name_entry(return_value, "issuer", X509_get_issuer_name(cert), useshortnames ? + PHP_OPENSSL_SHORT_NAME : PHP_OPENSSL_LONG_NAME); add_assoc_long(return_value, "version", X509_get_version(cert)); asn1_serial = X509_get_serialNumber(cert); @@ -1115,8 +1118,7 @@ PHP_FUNCTION(openssl_x509_parse) add_assoc_zval(return_value, "purposes", &subitem); array_init(&subitem); - - + array_init(&altname); for (i = 0; i < X509_get_ext_count(cert); i++) { int nid; extension = X509_get_ext(cert, i); @@ -1133,7 +1135,7 @@ PHP_FUNCTION(openssl_x509_parse) goto err_subitem; } if (nid == NID_subject_alt_name) { - if (openssl_x509v3_subjectAltName(bio_out, extension) == 0) { + if (openssl_x509v3_subjectAltName(bio_out, extension, &altname) == 0) { BIO_get_mem_ptr(bio_out, &bio_buf); add_assoc_stringl(&subitem, extname, bio_buf->data, bio_buf->length); } else { @@ -1150,6 +1152,12 @@ PHP_FUNCTION(openssl_x509_parse) BIO_free(bio_out); } add_assoc_zval(return_value, "extensions", &subitem); + zend_long altcount = zend_hash_num_elements(Z_ARRVAL_P(&altname)); + if (altcount > 0) { + add_assoc_zval(return_value, "subjectAlternativeName", &altname); + } else { + zval_ptr_dtor(&altname); + } if (cert_str) { X509_free(cert); } @@ -1157,6 +1165,7 @@ PHP_FUNCTION(openssl_x509_parse) err_subitem: zval_ptr_dtor(&subitem); + zval_ptr_dtor(&altname); err: zend_array_destroy(Z_ARR_P(return_value)); if (cert_str) { @@ -1953,7 +1962,8 @@ PHP_FUNCTION(openssl_csr_get_subject) subject = X509_REQ_get_subject_name(csr); array_init(return_value); - php_openssl_add_assoc_name_entry(return_value, NULL, subject, use_shortnames); + php_openssl_add_assoc_name_entry(return_value, NULL, subject, use_shortnames ? + PHP_OPENSSL_SHORT_NAME : PHP_OPENSSL_LONG_NAME); if (csr_str) { X509_REQ_free(csr); diff --git a/ext/openssl/openssl_backend_common.c b/ext/openssl/openssl_backend_common.c index 611359cccaba6..8dccd7c417650 100644 --- a/ext/openssl/openssl_backend_common.c +++ b/ext/openssl/openssl_backend_common.c @@ -25,6 +25,11 @@ /* Common */ #include +#ifdef PHP_WIN32 +# include +#else +# include +#endif #if (defined(PHP_WIN32) && defined(_MSC_VER)) #define timezone _timezone /* timezone is called _timezone in LibC */ @@ -35,12 +40,14 @@ /* true global; readonly after module startup */ static char default_ssl_conf_filename[MAXPATHLEN]; -void php_openssl_add_assoc_name_entry(zval * val, char * key, X509_NAME * name, int shortname) +void php_openssl_add_assoc_name_entry(zval * val, char * key, X509_NAME * name, + enum php_openssl_name_type nametype) { zval *data; zval subitem, tmp; int i; - char *sname; + char *sname = NULL; + char oname[1024]; int nid; X509_NAME_ENTRY * ne; ASN1_STRING * str = NULL; @@ -59,12 +66,20 @@ void php_openssl_add_assoc_name_entry(zval * val, char * key, X509_NAME * name, ne = X509_NAME_get_entry(name, i); obj = X509_NAME_ENTRY_get_object(ne); - nid = OBJ_obj2nid(obj); - if (shortname) { - sname = (char *) OBJ_nid2sn(nid); - } else { - sname = (char *) OBJ_nid2ln(nid); + switch (nametype) { + case PHP_OPENSSL_SHORT_NAME: + nid = OBJ_obj2nid(obj); + sname = (char *) OBJ_nid2sn(nid); + break; + case PHP_OPENSSL_LONG_NAME: + nid = OBJ_obj2nid(obj); + sname = (char *) OBJ_nid2ln(nid); + break; + case PHP_OPENSSL_OID: + OBJ_obj2txt(oname, sizeof(oname)-1, obj, 1); + sname = oname; + break; } str = X509_NAME_ENTRY_get_data(ne); @@ -613,16 +628,60 @@ zend_string* php_openssl_x509_fingerprint(X509 *peer, const char *method, bool r return ret; } +/* proto void print_asn1_type(BIO *bio, ASN1_TYPE *ptr) + Print the value of an ASN1_TYPE to a BIO */ +static void print_asn1_type(BIO *bio, ASN1_TYPE *ptr) +{ + char objbuf[1024]; + + switch (ptr->type) { + case V_ASN1_BOOLEAN: + BIO_puts(bio, ptr->value.boolean ? "true" : "false"); + break; + + case V_ASN1_INTEGER: + BIO_puts(bio, i2s_ASN1_INTEGER(NULL, ptr->value.integer)); + break; + + case V_ASN1_ENUMERATED: + BIO_puts(bio, i2s_ASN1_INTEGER(NULL, ptr->value.enumerated)); + break; + + case V_ASN1_NULL: + BIO_puts(bio, "NULL"); + break; + + case V_ASN1_UTCTIME: + ASN1_UTCTIME_print(bio, ptr->value.utctime); + break; + + case V_ASN1_GENERALIZEDTIME: + ASN1_GENERALIZEDTIME_print(bio, ptr->value.generalizedtime); + break; + + case V_ASN1_OBJECT: + OBJ_obj2txt(objbuf, sizeof(objbuf), ptr->value.object, 1); + BIO_puts(bio, objbuf); + break; + + default : + ASN1_STRING_print_ex(bio, ptr->value.visiblestring, + ASN1_STRFLGS_DUMP_UNKNOWN); + break; + } +} + /* Special handling of subjectAltName, see CVE-2013-4073 * Christian Heimes */ -int openssl_x509v3_subjectAltName(BIO *bio, X509_EXTENSION *extension) +int openssl_x509v3_subjectAltName(BIO *bio, X509_EXTENSION *extension, zval *altname) { GENERAL_NAMES *names; const X509V3_EXT_METHOD *method = NULL; ASN1_OCTET_STRING *extension_data; long i, length, num; const unsigned char *p; + zend_ulong index = 0; method = X509V3_EXT_get(extension); if (method == NULL) { @@ -647,6 +706,8 @@ int openssl_x509v3_subjectAltName(BIO *bio, X509_EXTENSION *extension) for (i = 0; i < num; i++) { GENERAL_NAME *name; ASN1_STRING *as; + zval entry; + array_init(&entry); name = sk_GENERAL_NAME_value(names, i); switch (name->type) { case GEN_EMAIL: @@ -654,29 +715,118 @@ int openssl_x509v3_subjectAltName(BIO *bio, X509_EXTENSION *extension) as = name->d.rfc822Name; BIO_write(bio, ASN1_STRING_get0_data(as), ASN1_STRING_length(as)); + if (altname != NULL) { + add_assoc_string(&entry, "type", "email"); + php_openssl_add_assoc_asn1_string(&entry, "value", as); + add_index_zval(altname, index++, &entry); + } break; case GEN_DNS: BIO_puts(bio, "DNS:"); as = name->d.dNSName; BIO_write(bio, ASN1_STRING_get0_data(as), ASN1_STRING_length(as)); + if (altname != NULL) { + add_assoc_string(&entry, "type", "DNS"); + php_openssl_add_assoc_asn1_string(&entry, "value", as); + add_index_zval(altname, index++, &entry); + } break; case GEN_URI: BIO_puts(bio, "URI:"); as = name->d.uniformResourceIdentifier; BIO_write(bio, ASN1_STRING_get0_data(as), ASN1_STRING_length(as)); + if (altname != NULL) { + add_assoc_string(&entry, "type", "URI"); + php_openssl_add_assoc_asn1_string(&entry, "value", as); + add_index_zval(altname, index++, &entry); + } + break; + case GEN_DIRNAME: + GENERAL_NAME_print(bio, name); + if (altname != NULL) { + add_assoc_string(&entry, "type", "DirName"); + php_openssl_add_assoc_name_entry(&entry, "value", name->d.dirn, PHP_OPENSSL_OID); + add_index_zval(altname, index++, &entry); + } + break; + case GEN_RID: + GENERAL_NAME_print(bio, name); + if (altname != NULL) { + char buf[1024]; + OBJ_obj2txt(buf, sizeof(buf)-1, name->d.rid, 1); + add_assoc_string(&entry, "type", "Registered ID"); + add_assoc_string(&entry, "value", buf); + add_index_zval(altname, index++, &entry); + } + break; + case GEN_IPADD: + GENERAL_NAME_print(bio, name); + if (altname != NULL) { + char buf[1024]; + if (name->d.ip->length == 4) { + inet_ntop(AF_INET, name->d.ip->data, buf, sizeof(buf)-1); + } else if (name->d.ip->length == 16) { + inet_ntop(AF_INET6, name->d.ip->data, buf, sizeof(buf)-1); + } else { + sprintf(buf, ""); + } + add_assoc_string(&entry, "type", "IP Address"); + add_assoc_string(&entry, "value", buf); + add_index_zval(altname, index++, &entry); + } + break; + case GEN_OTHERNAME: + GENERAL_NAME_print(bio, name); + if (altname != NULL) { + char oid[1024]; + zval value; + array_init(&value); + + OBJ_obj2txt(oid, sizeof(oid)-1, name->d.otherName->type_id, 1); + + BIO *bio_out; + BUF_MEM *bio_buf; + bio_out = BIO_new(BIO_s_mem()); + print_asn1_type(bio_out, name->d.otherName->value); + BIO_get_mem_ptr(bio_out, &bio_buf); + + add_assoc_stringl(&value, oid, bio_buf->data, bio_buf->length); + add_assoc_string(&entry, "type", "othername"); + add_assoc_zval(&entry, "value", &value); + add_index_zval(altname, index++, &entry); + BIO_free(bio_out); + } break; default: - /* use builtin print for GEN_OTHERNAME, GEN_X400, - * GEN_EDIPARTY, GEN_DIRNAME, GEN_IPADD and GEN_RID - */ GENERAL_NAME_print(bio, name); - } - /* trailing ', ' except for last element */ - if (i < (num - 1)) { - BIO_puts(bio, ", "); - } + if (altname != NULL) { + BIO *bio_out; + BUF_MEM *bio_buf; + bio_out = BIO_new(BIO_s_mem()); + GENERAL_NAME_print(bio_out, name); + BIO_get_mem_ptr(bio_out, &bio_buf); + switch (name->type) { + case GEN_X400: + add_assoc_string(&entry, "type", "X400Name"); + break; + case GEN_EDIPARTY: + add_assoc_string(&entry, "type", "EdiPartyName"); + break; + default: + add_assoc_string(&entry, "type", "Unknown"); + break; + } + add_assoc_stringl(&entry, "value", bio_buf->data, bio_buf->length); + add_index_zval(altname, index++, &entry); + BIO_free(bio_out); + } + } + /* trailing ', ' except for last element */ + if (i < (num - 1)) { + BIO_puts(bio, ", "); + } } sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free); diff --git a/ext/openssl/php_openssl_backend.h b/ext/openssl/php_openssl_backend.h index 00da5e74fc1b8..f9b300a8ee67c 100644 --- a/ext/openssl/php_openssl_backend.h +++ b/ext/openssl/php_openssl_backend.h @@ -99,6 +99,13 @@ enum php_openssl_cipher_type { PHP_OPENSSL_CIPHER_DEFAULT = PHP_OPENSSL_CIPHER_AES_128_CBC }; +/* Name display options for php_openssl_add_assoc_name_entry */ +enum php_openssl_name_type { + PHP_OPENSSL_LONG_NAME, + PHP_OPENSSL_SHORT_NAME, + PHP_OPENSSL_OID +}; + /* Add some encoding rules. This is normally handled through filters * in the OpenSSL code, but we will do that part as if we were one * of the OpenSSL binaries along the lines of -outform {DER|CMS|PEM} @@ -168,7 +175,8 @@ struct php_x509_request { const EVP_CIPHER * priv_key_encrypt_cipher; }; -void php_openssl_add_assoc_name_entry(zval * val, char * key, X509_NAME * name, int shortname); +void php_openssl_add_assoc_name_entry(zval * val, char * key, X509_NAME * name, + enum php_openssl_name_type nametype); void php_openssl_add_assoc_asn1_string(zval * val, char * key, ASN1_STRING * str); time_t php_openssl_asn1_time_to_time_t(ASN1_UTCTIME * timestr); int php_openssl_config_check_syntax(const char * section_label, const char * config_filename, const char * section, CONF *config); @@ -266,7 +274,7 @@ X509 *php_openssl_x509_from_zval( zend_string* php_openssl_x509_fingerprint(X509 *peer, const char *method, bool raw); -int openssl_x509v3_subjectAltName(BIO *bio, X509_EXTENSION *extension); +int openssl_x509v3_subjectAltName(BIO *bio, X509_EXTENSION *extension, zval *altname); STACK_OF(X509) *php_openssl_load_all_certs_from_file( char *cert_file, size_t cert_file_len, uint32_t arg_num); diff --git a/ext/openssl/tests/subjectAlternativeName.crt b/ext/openssl/tests/subjectAlternativeName.crt new file mode 100644 index 0000000000000..0d4a9b9ecfb40 --- /dev/null +++ b/ext/openssl/tests/subjectAlternativeName.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDRzCCAuygAwIBAgIUDcQjrtk7F/g4Mdm+NiAzKpInXDEwCgYIKoZIzj0EAwIw +ezELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNh +biBGcmFuY2lzY28xKTARBgNVBAoMCk15IENvbXBhbnkwFAYDVQQLDA1NeSBEZXBh +cnRtZW50MRQwEgYDVQQDDAtleGFtcGxlLmNvbTAeFw0yNTExMDMxOTEzNDBaFw0y +NjExMDMxOTEzNDBaMHsxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlh +MRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMSkwEQYDVQQKDApNeSBDb21wYW55MBQG +A1UECwwNTXkgRGVwYXJ0bWVudDEUMBIGA1UEAwwLZXhhbXBsZS5jb20wWTATBgcq +hkjOPQIBBggqhkjOPQMBBwNCAAQ+riFshYe8HnWt1avx6OuNajipU1ZW6BgW0+D/ +EtDDSYeQg9ngO8qyo5M6cyh7ORtKZVUy7DP1+W+eocaZC+a6o4IBTDCCAUgwggEl +BgNVHREEggEcMIIBGIILZXhhbXBsZS5jb22CD3d3dy5leGFtcGxlLmNvbYIVc3Vi +ZG9tYWluLmV4YW1wbGUuY29thwTAqAEBhxAmB/DQEAIAUQAAAAAAAAAEgRFhZG1p +bkBleGFtcGxlLmNvbaROMEwxETAPBgNVBAMMCEpvaG4gRG9lMSowDgYDVQQLDAdU +ZXN0aW5nMBgGA1UECgwRRXhhbXBsZSBPcmcsIEluYy4xCzAJBgNVBAYTAlVToCMG +CSqGSIb3DQEJAqAWDBRVSURfdW5zdHJ1Y3R1cmVkTmFtZaAfBgkqhkiG9w0BCRSg +EhYQVUlEX2ZyaWVuZGx5TmFtZYgDKgMEhhtodHRwOi8vZXhhbXBsZS5jb20vcmVz +b3VyY2UwHQYDVR0OBBYEFICesJGN6QyOP89fyTVAmhL28E0NMAoGCCqGSM49BAMC +A0kAMEYCIQDah0YhiFdhHPZIMkHS91QsN2A9HZ4YwZi0wObHcB8r5gIhAJc+Szee +2PfEQatjoWj/K1xZBV8ZNF6UtjzdY/5VD52z +-----END CERTIFICATE----- diff --git a/ext/openssl/tests/subjectAlternativeName.phpt b/ext/openssl/tests/subjectAlternativeName.phpt new file mode 100644 index 0000000000000..86165bec040ff --- /dev/null +++ b/ext/openssl/tests/subjectAlternativeName.phpt @@ -0,0 +1,108 @@ +--TEST-- +openssl_x509_parse() subjectAlternativeName test +--EXTENSIONS-- +openssl +--SKIPIF-- += 0x30200000) die('skip For OpenSSL < 3.2'); +?> +--FILE-- + + array(2) { + ["type"]=> + string(3) "DNS" + ["value"]=> + string(11) "example.com" + } + [1]=> + array(2) { + ["type"]=> + string(3) "DNS" + ["value"]=> + string(15) "www.example.com" + } + [2]=> + array(2) { + ["type"]=> + string(3) "DNS" + ["value"]=> + string(21) "subdomain.example.com" + } + [3]=> + array(2) { + ["type"]=> + string(10) "IP Address" + ["value"]=> + string(11) "192.168.1.1" + } + [4]=> + array(2) { + ["type"]=> + string(10) "IP Address" + ["value"]=> + string(20) "2607:f0d0:1002:51::4" + } + [5]=> + array(2) { + ["type"]=> + string(5) "email" + ["value"]=> + string(17) "admin@example.com" + } + [6]=> + array(2) { + ["type"]=> + string(7) "DirName" + ["value"]=> + array(4) { + ["2.5.4.3"]=> + string(8) "John Doe" + ["2.5.4.11"]=> + string(7) "Testing" + ["2.5.4.10"]=> + string(17) "Example Org, Inc." + ["2.5.4.6"]=> + string(2) "US" + } + } + [7]=> + array(2) { + ["type"]=> + string(9) "othername" + ["value"]=> + array(1) { + ["1.2.840.113549.1.9.2"]=> + string(20) "UID_unstructuredName" + } + } + [8]=> + array(2) { + ["type"]=> + string(9) "othername" + ["value"]=> + array(1) { + ["1.2.840.113549.1.9.20"]=> + string(16) "UID_friendlyName" + } + } + [9]=> + array(2) { + ["type"]=> + string(13) "Registered ID" + ["value"]=> + string(7) "1.2.3.4" + } + [10]=> + array(2) { + ["type"]=> + string(3) "URI" + ["value"]=> + string(27) "http://example.com/resource" + } +}