Skip to content

Commit 0a04d6c

Browse files
committed
Extend x.509 parsing to include machine-readable info about subjectAlternativeNames
This adds an additional array element to the output of `openssl_x509_parse()` that includes detailed info about the subjectAlternativeName extension in an easily machine parsable format. Example: If the contents of the extension appear as this in the current openssl_x509_parse() output (wrapped for readability): ``` Array ( [name] => ... ... [extensions] => Array ( [subjectAltName] => DNS:www.good.org, email:good@good.org, IP Address:192.168.0.1, othername:<unsupported>, othername:SmtpUTF8Mailbox:test@test.example.com, URI:sip:6000@192.168.0.1, DirName/C=US/ST=CA/L=San Francisco/O=Example Company/OU=Example Company Unit/CN=Bob, Registered ID:1.2.3.4.5 ) ) ``` you would see a new top-level array element like this: ``` Array ( ... [subjectAlternativeName] => Array ( [0] => Array ( [type] => DNS [value] => www.good.org ) [1] => Array ( [type] => email [value] => good@good.org ) [2] => Array ( [type] => IP Address [value] => 192.168.0.1 ) [3] => Array ( [type] => othername [value] => Array ( [1.3.6.1.5.5.7.8.7] => foo@example.org ) ) [4] => Array ( [type] => othername [value] => Array ( [1.3.6.1.5.5.7.8.9] => test@test.example.com ) ) [5] => Array ( [type] => URI [value] => sip:6000@192.168.0.1 ) [6] => Array ( [type] => DirName [value] => Array ( [2.5.4.6] => US [2.5.4.8] => CA [2.5.4.7] => San Francisco [2.5.4.10] => Example Company [2.5.4.11] => Example Company Unit [2.5.4.3] => Bob ) ) [7] => Array ( [type] => Registered ID [value] => 1.2.3.4.5 ) ) ) ```
1 parent d86182f commit 0a04d6c

5 files changed

Lines changed: 321 additions & 21 deletions

File tree

ext/openssl/openssl.c

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1014,6 +1014,7 @@ PHP_FUNCTION(openssl_x509_parse)
10141014
char *str_serial;
10151015
char *hex_serial;
10161016
char buf[256];
1017+
zval *altname = NULL;
10171018

10181019
ZEND_PARSE_PARAMETERS_START(1, 2)
10191020
Z_PARAM_OBJ_OF_CLASS_OR_STR(cert_obj, php_openssl_certificate_ce, cert_str)
@@ -1033,15 +1034,17 @@ PHP_FUNCTION(openssl_x509_parse)
10331034
add_assoc_string(return_value, "name", cert_name);
10341035
OPENSSL_free(cert_name);
10351036

1036-
php_openssl_add_assoc_name_entry(return_value, "subject", subject_name, useshortnames);
1037+
php_openssl_add_assoc_name_entry(return_value, "subject", subject_name, useshortnames ?
1038+
PHP_OPENSSL_SHORT_NAME : PHP_OPENSSL_LONG_NAME);
10371039
/* hash as used in CA directories to lookup cert by subject name */
10381040
{
10391041
char buf[32];
10401042
snprintf(buf, sizeof(buf), "%08lx", X509_subject_name_hash(cert));
10411043
add_assoc_string(return_value, "hash", buf);
10421044
}
10431045

1044-
php_openssl_add_assoc_name_entry(return_value, "issuer", X509_get_issuer_name(cert), useshortnames);
1046+
php_openssl_add_assoc_name_entry(return_value, "issuer", X509_get_issuer_name(cert), useshortnames ?
1047+
PHP_OPENSSL_SHORT_NAME : PHP_OPENSSL_LONG_NAME);
10451048
add_assoc_long(return_value, "version", X509_get_version(cert));
10461049

10471050
asn1_serial = X509_get_serialNumber(cert);
@@ -1133,7 +1136,7 @@ PHP_FUNCTION(openssl_x509_parse)
11331136
goto err_subitem;
11341137
}
11351138
if (nid == NID_subject_alt_name) {
1136-
if (openssl_x509v3_subjectAltName(bio_out, extension) == 0) {
1139+
if (openssl_x509v3_subjectAltName(bio_out, extension, &altname) == 0) {
11371140
BIO_get_mem_ptr(bio_out, &bio_buf);
11381141
add_assoc_stringl(&subitem, extname, bio_buf->data, bio_buf->length);
11391142
} else {
@@ -1150,6 +1153,9 @@ PHP_FUNCTION(openssl_x509_parse)
11501153
BIO_free(bio_out);
11511154
}
11521155
add_assoc_zval(return_value, "extensions", &subitem);
1156+
if (altname != NULL) {
1157+
add_assoc_zval(return_value, "subjectAlternativeName", altname);
1158+
}
11531159
if (cert_str) {
11541160
X509_free(cert);
11551161
}
@@ -1953,7 +1959,8 @@ PHP_FUNCTION(openssl_csr_get_subject)
19531959
subject = X509_REQ_get_subject_name(csr);
19541960

19551961
array_init(return_value);
1956-
php_openssl_add_assoc_name_entry(return_value, NULL, subject, use_shortnames);
1962+
php_openssl_add_assoc_name_entry(return_value, NULL, subject, use_shortnames ?
1963+
PHP_OPENSSL_SHORT_NAME : PHP_OPENSSL_LONG_NAME);
19571964

19581965
if (csr_str) {
19591966
X509_REQ_free(csr);

ext/openssl/openssl_backend_common.c

Lines changed: 172 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@
2525

2626
/* Common */
2727
#include <time.h>
28+
#ifdef PHP_WIN32
29+
# include <Ws2tcpip.h>
30+
#else
31+
# include <arpa/inet.h>
32+
#endif
2833

2934
#if (defined(PHP_WIN32) && defined(_MSC_VER))
3035
#define timezone _timezone /* timezone is called _timezone in LibC */
@@ -35,12 +40,14 @@
3540
/* true global; readonly after module startup */
3641
static char default_ssl_conf_filename[MAXPATHLEN];
3742

38-
void php_openssl_add_assoc_name_entry(zval * val, char * key, X509_NAME * name, int shortname)
43+
void php_openssl_add_assoc_name_entry(zval * val, char * key, X509_NAME * name,
44+
enum php_openssl_name_type nametype)
3945
{
4046
zval *data;
4147
zval subitem, tmp;
4248
int i;
4349
char *sname;
50+
char oname[1024];
4451
int nid;
4552
X509_NAME_ENTRY * ne;
4653
ASN1_STRING * str = NULL;
@@ -59,12 +66,20 @@ void php_openssl_add_assoc_name_entry(zval * val, char * key, X509_NAME * name,
5966

6067
ne = X509_NAME_get_entry(name, i);
6168
obj = X509_NAME_ENTRY_get_object(ne);
62-
nid = OBJ_obj2nid(obj);
6369

64-
if (shortname) {
65-
sname = (char *) OBJ_nid2sn(nid);
66-
} else {
67-
sname = (char *) OBJ_nid2ln(nid);
70+
switch (nametype) {
71+
case PHP_OPENSSL_SHORT_NAME:
72+
nid = OBJ_obj2nid(obj);
73+
sname = (char *) OBJ_nid2sn(nid);
74+
break;
75+
case PHP_OPENSSL_LONG_NAME:
76+
nid = OBJ_obj2nid(obj);
77+
sname = (char *) OBJ_nid2ln(nid);
78+
break;
79+
case PHP_OPENSSL_OID:
80+
OBJ_obj2txt(oname, sizeof(oname)-1, obj, 1);
81+
sname = oname;
82+
break;
6883
}
6984

7085
str = X509_NAME_ENTRY_get_data(ne);
@@ -613,16 +628,61 @@ zend_string* php_openssl_x509_fingerprint(X509 *peer, const char *method, bool r
613628
return ret;
614629
}
615630

631+
/* proto void print_asn1_type(BIO *bio, ASN1_TYPE *ptr)
632+
Print the value of an ASN1_TYPE to a BIO */
633+
static void print_asn1_type(BIO *bio, ASN1_TYPE *ptr)
634+
{
635+
char objbuf[1024];
636+
637+
// Collect the things from the ASN1_TYPE structure
638+
switch (ptr->type) {
639+
case V_ASN1_BOOLEAN:
640+
BIO_puts(bio, ptr->value.boolean ? "true" : "false");
641+
break;
642+
643+
case V_ASN1_INTEGER:
644+
BIO_puts(bio, i2s_ASN1_INTEGER(NULL, ptr->value.integer));
645+
break;
646+
647+
case V_ASN1_ENUMERATED:
648+
BIO_puts(bio, i2s_ASN1_INTEGER(NULL, ptr->value.enumerated));
649+
break;
650+
651+
case V_ASN1_NULL:
652+
BIO_puts(bio, "NULL");
653+
break;
654+
655+
case V_ASN1_UTCTIME:
656+
ASN1_UTCTIME_print(bio, ptr->value.utctime);
657+
break;
658+
659+
case V_ASN1_GENERALIZEDTIME:
660+
ASN1_GENERALIZEDTIME_print(bio, ptr->value.generalizedtime);
661+
break;
662+
663+
case V_ASN1_OBJECT:
664+
OBJ_obj2txt(objbuf, sizeof(objbuf), ptr->value.object, 1);
665+
BIO_puts(bio, objbuf);
666+
break;
667+
668+
default :
669+
ASN1_STRING_print_ex(bio, ptr->value.visiblestring,
670+
ASN1_STRFLGS_DUMP_UNKNOWN);
671+
break;
672+
}
673+
}
674+
616675
/* Special handling of subjectAltName, see CVE-2013-4073
617676
* Christian Heimes
618677
*/
619-
int openssl_x509v3_subjectAltName(BIO *bio, X509_EXTENSION *extension)
678+
int openssl_x509v3_subjectAltName(BIO *bio, X509_EXTENSION *extension, zval **altname)
620679
{
621680
GENERAL_NAMES *names;
622681
const X509V3_EXT_METHOD *method = NULL;
623682
ASN1_OCTET_STRING *extension_data;
624683
long i, length, num;
625684
const unsigned char *p;
685+
zend_ulong index = 0;
626686

627687
method = X509V3_EXT_get(extension);
628688
if (method == NULL) {
@@ -644,39 +704,136 @@ int openssl_x509v3_subjectAltName(BIO *bio, X509_EXTENSION *extension)
644704
}
645705

646706
num = sk_GENERAL_NAME_num(names);
707+
if (altname != NULL) {
708+
if (*altname == NULL) {
709+
*altname = (zval *)safe_emalloc(1, sizeof(zval), 0);
710+
}
711+
array_init(*altname);
712+
}
647713
for (i = 0; i < num; i++) {
648714
GENERAL_NAME *name;
649715
ASN1_STRING *as;
716+
zval entry;
717+
array_init(&entry);
650718
name = sk_GENERAL_NAME_value(names, i);
651719
switch (name->type) {
652720
case GEN_EMAIL:
653721
BIO_puts(bio, "email:");
654722
as = name->d.rfc822Name;
655723
BIO_write(bio, ASN1_STRING_get0_data(as),
656724
ASN1_STRING_length(as));
725+
if (altname != NULL) {
726+
add_assoc_string(&entry, "type", "email");
727+
php_openssl_add_assoc_asn1_string(&entry, "value", as);
728+
add_index_zval(*altname, index++, &entry);
729+
}
657730
break;
658731
case GEN_DNS:
659732
BIO_puts(bio, "DNS:");
660733
as = name->d.dNSName;
661734
BIO_write(bio, ASN1_STRING_get0_data(as),
662735
ASN1_STRING_length(as));
736+
if (altname != NULL) {
737+
add_assoc_string(&entry, "type", "DNS");
738+
php_openssl_add_assoc_asn1_string(&entry, "value", as);
739+
add_index_zval(*altname, index++, &entry);
740+
}
663741
break;
664742
case GEN_URI:
665743
BIO_puts(bio, "URI:");
666744
as = name->d.uniformResourceIdentifier;
667745
BIO_write(bio, ASN1_STRING_get0_data(as),
668746
ASN1_STRING_length(as));
747+
if (altname != NULL) {
748+
add_assoc_string(&entry, "type", "URI");
749+
php_openssl_add_assoc_asn1_string(&entry, "value", as);
750+
add_index_zval(*altname, index++, &entry);
751+
}
752+
break;
753+
case GEN_DIRNAME:
754+
GENERAL_NAME_print(bio, name);
755+
if (altname != NULL) {
756+
add_assoc_string(&entry, "type", "DirName");
757+
php_openssl_add_assoc_name_entry(&entry, "value", name->d.dirn, PHP_OPENSSL_OID);
758+
add_index_zval(*altname, index++, &entry);
759+
}
760+
break;
761+
case GEN_RID:
762+
GENERAL_NAME_print(bio, name);
763+
if (altname != NULL) {
764+
char buf[1024];
765+
OBJ_obj2txt(buf, sizeof(buf)-1, name->d.rid, 1);
766+
add_assoc_string(&entry, "type", "Registered ID");
767+
add_assoc_string(&entry, "value", buf);
768+
add_index_zval(*altname, index++, &entry);
769+
}
770+
break;
771+
case GEN_IPADD:
772+
GENERAL_NAME_print(bio, name);
773+
if (altname != NULL) {
774+
char buf[1024];
775+
if (name->d.ip->length == 4) {
776+
inet_ntop(AF_INET, name->d.ip->data, buf, sizeof(buf)-1);
777+
} else if (name->d.ip->length == 16) {
778+
inet_ntop(AF_INET6, name->d.ip->data, buf, sizeof(buf)-1);
779+
} else {
780+
sprintf(buf, "<invalid>");
781+
}
782+
add_assoc_string(&entry, "type", "IP Address");
783+
add_assoc_string(&entry, "value", buf);
784+
add_index_zval(*altname, index++, &entry);
785+
}
786+
break;
787+
case GEN_OTHERNAME:
788+
GENERAL_NAME_print(bio, name);
789+
if (altname != NULL) {
790+
char oid[1024];
791+
zval value;
792+
array_init(&value);
793+
794+
OBJ_obj2txt(oid, sizeof(oid)-1, name->d.otherName->type_id, 1);
795+
796+
BIO *bio_out;
797+
BUF_MEM *bio_buf;
798+
bio_out = BIO_new(BIO_s_mem());
799+
print_asn1_type(bio_out, name->d.otherName->value);
800+
BIO_get_mem_ptr(bio_out, &bio_buf);
801+
802+
add_assoc_stringl(&value, oid, bio_buf->data, bio_buf->length);
803+
add_assoc_string(&entry, "type", "othername");
804+
add_assoc_zval(&entry, "value", &value);
805+
add_index_zval(*altname, index++, &entry);
806+
BIO_free(bio_out);
807+
}
669808
break;
670809
default:
671-
/* use builtin print for GEN_OTHERNAME, GEN_X400,
672-
* GEN_EDIPARTY, GEN_DIRNAME, GEN_IPADD and GEN_RID
673-
*/
674810
GENERAL_NAME_print(bio, name);
675-
}
676-
/* trailing ', ' except for last element */
677-
if (i < (num - 1)) {
678-
BIO_puts(bio, ", ");
679-
}
811+
if (altname != NULL) {
812+
BIO *bio_out;
813+
BUF_MEM *bio_buf;
814+
bio_out = BIO_new(BIO_s_mem());
815+
GENERAL_NAME_print(bio_out, name);
816+
BIO_get_mem_ptr(bio_out, &bio_buf);
817+
switch (name->type) {
818+
case GEN_X400:
819+
add_assoc_string(&entry, "type", "X400Name");
820+
break;
821+
case GEN_EDIPARTY:
822+
add_assoc_string(&entry, "type", "EdiPartyName");
823+
break;
824+
default:
825+
add_assoc_string(&entry, "type", "Unknown");
826+
break;
827+
}
828+
add_assoc_stringl(&entry, "value", bio_buf->data, bio_buf->length);
829+
add_index_zval(*altname, index++, &entry);
830+
BIO_free(bio_out);
831+
}
832+
}
833+
/* trailing ', ' except for last element */
834+
if (i < (num - 1)) {
835+
BIO_puts(bio, ", ");
836+
}
680837
}
681838
sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free);
682839

ext/openssl/php_openssl_backend.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,13 @@ enum php_openssl_cipher_type {
9999
PHP_OPENSSL_CIPHER_DEFAULT = PHP_OPENSSL_CIPHER_AES_128_CBC
100100
};
101101

102+
/* Name display options for php_openssl_add_assoc_name_entry */
103+
enum php_openssl_name_type {
104+
PHP_OPENSSL_LONG_NAME,
105+
PHP_OPENSSL_SHORT_NAME,
106+
PHP_OPENSSL_OID
107+
};
108+
102109
/* Add some encoding rules. This is normally handled through filters
103110
* in the OpenSSL code, but we will do that part as if we were one
104111
* of the OpenSSL binaries along the lines of -outform {DER|CMS|PEM}
@@ -168,7 +175,8 @@ struct php_x509_request {
168175
const EVP_CIPHER * priv_key_encrypt_cipher;
169176
};
170177

171-
void php_openssl_add_assoc_name_entry(zval * val, char * key, X509_NAME * name, int shortname);
178+
void php_openssl_add_assoc_name_entry(zval * val, char * key, X509_NAME * name,
179+
enum php_openssl_name_type nametype);
172180
void php_openssl_add_assoc_asn1_string(zval * val, char * key, ASN1_STRING * str);
173181
time_t php_openssl_asn1_time_to_time_t(ASN1_UTCTIME * timestr);
174182
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(
266274

267275
zend_string* php_openssl_x509_fingerprint(X509 *peer, const char *method, bool raw);
268276

269-
int openssl_x509v3_subjectAltName(BIO *bio, X509_EXTENSION *extension);
277+
int openssl_x509v3_subjectAltName(BIO *bio, X509_EXTENSION *extension, zval **altname);
270278

271279
STACK_OF(X509) *php_openssl_load_all_certs_from_file(
272280
char *cert_file, size_t cert_file_len, uint32_t arg_num);
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDRzCCAuygAwIBAgIUDcQjrtk7F/g4Mdm+NiAzKpInXDEwCgYIKoZIzj0EAwIw
3+
ezELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNh
4+
biBGcmFuY2lzY28xKTARBgNVBAoMCk15IENvbXBhbnkwFAYDVQQLDA1NeSBEZXBh
5+
cnRtZW50MRQwEgYDVQQDDAtleGFtcGxlLmNvbTAeFw0yNTExMDMxOTEzNDBaFw0y
6+
NjExMDMxOTEzNDBaMHsxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlh
7+
MRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMSkwEQYDVQQKDApNeSBDb21wYW55MBQG
8+
A1UECwwNTXkgRGVwYXJ0bWVudDEUMBIGA1UEAwwLZXhhbXBsZS5jb20wWTATBgcq
9+
hkjOPQIBBggqhkjOPQMBBwNCAAQ+riFshYe8HnWt1avx6OuNajipU1ZW6BgW0+D/
10+
EtDDSYeQg9ngO8qyo5M6cyh7ORtKZVUy7DP1+W+eocaZC+a6o4IBTDCCAUgwggEl
11+
BgNVHREEggEcMIIBGIILZXhhbXBsZS5jb22CD3d3dy5leGFtcGxlLmNvbYIVc3Vi
12+
ZG9tYWluLmV4YW1wbGUuY29thwTAqAEBhxAmB/DQEAIAUQAAAAAAAAAEgRFhZG1p
13+
bkBleGFtcGxlLmNvbaROMEwxETAPBgNVBAMMCEpvaG4gRG9lMSowDgYDVQQLDAdU
14+
ZXN0aW5nMBgGA1UECgwRRXhhbXBsZSBPcmcsIEluYy4xCzAJBgNVBAYTAlVToCMG
15+
CSqGSIb3DQEJAqAWDBRVSURfdW5zdHJ1Y3R1cmVkTmFtZaAfBgkqhkiG9w0BCRSg
16+
EhYQVUlEX2ZyaWVuZGx5TmFtZYgDKgMEhhtodHRwOi8vZXhhbXBsZS5jb20vcmVz
17+
b3VyY2UwHQYDVR0OBBYEFICesJGN6QyOP89fyTVAmhL28E0NMAoGCCqGSM49BAMC
18+
A0kAMEYCIQDah0YhiFdhHPZIMkHS91QsN2A9HZ4YwZi0wObHcB8r5gIhAJc+Szee
19+
2PfEQatjoWj/K1xZBV8ZNF6UtjzdY/5VD52z
20+
-----END CERTIFICATE-----

0 commit comments

Comments
 (0)