Skip to content

Commit fd5f758

Browse files
mishaslavinmeta-codesync[bot]
authored andcommitted
openssl_x509_get_upn
Summary: add a function to be able to get UPN from an x509 certificate. Reviewed By: WizKid Differential Revision: D83392702 fbshipit-source-id: 6c9df4f1f91a8ffbe9f0186c96683cc973db2598
1 parent 532fa7c commit fd5f758

4 files changed

Lines changed: 204 additions & 33 deletions

File tree

hphp/runtime/ext/openssl/ext_openssl.cpp

Lines changed: 54 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,44 @@ static X509_STORE *setup_verify(const Array& calist) {
837837
return store;
838838
}
839839

840+
841+
static STACK_OF(GENERAL_NAME) *csr_get_subject_alt_names(X509_REQ *csr) {
842+
if (!csr) return nullptr;
843+
STACK_OF(X509_EXTENSION) *csr_exts = X509_REQ_get_extensions(csr);
844+
if (!csr_exts) return nullptr;
845+
return (STACK_OF(GENERAL_NAME) *)X509V3_get_d2i(csr_exts, NID_subject_alt_name,
846+
nullptr, nullptr);
847+
}
848+
849+
static STACK_OF(GENERAL_NAME) *x509_get_subject_alt_names(X509 *certificate) {
850+
if (!certificate) return nullptr;
851+
const STACK_OF(X509_EXTENSION) *cert_exts = X509_get0_extensions(certificate);
852+
if (!cert_exts) return nullptr;
853+
return (STACK_OF(GENERAL_NAME) *)X509V3_get_d2i(cert_exts, NID_subject_alt_name,
854+
nullptr, nullptr);
855+
}
856+
857+
static Optional<String> sk_SAN_get_upn(STACK_OF(GENERAL_NAME) *subject_alt_names) {
858+
if (!subject_alt_names) return std::nullopt;
859+
for (int i = 0; i < sk_GENERAL_NAME_num(subject_alt_names); i++){
860+
const GENERAL_NAME *general_name = sk_GENERAL_NAME_value(subject_alt_names, i);
861+
if (!general_name) continue;
862+
ASN1_OBJECT *oid = NULL;
863+
ASN1_TYPE *value = NULL;
864+
// seek an otherName
865+
if(!GENERAL_NAME_get0_otherName(general_name, &oid, &value)) continue;
866+
// compare its OID to MS UPN
867+
if (OBJ_obj2nid(oid) != NID_ms_upn) continue;
868+
// get the utf8string from the UPN
869+
if(ASN1_TYPE_get(value) == V_ASN1_UTF8STRING) {
870+
ASN1_UTF8STRING *upn = value->value.utf8string;
871+
if (!upn || !upn->data) continue;
872+
return String((char *)upn->data, upn->length, CopyString);
873+
}
874+
}
875+
return std::nullopt;
876+
}
877+
840878
///////////////////////////////////////////////////////////////////////////////
841879

842880
static bool add_entries(X509_NAME *subj, const Array& items) {
@@ -1205,24 +1243,11 @@ Variant HHVM_FUNCTION(openssl_csr_sign, const Variant& csr,
12051243
return ret;
12061244
}
12071245

1208-
STACK_OF(GENERAL_NAME) *openssl_csr_get_subject_alt_names(X509_REQ *csr) {
1209-
if (!csr) return nullptr;
1210-
STACK_OF(X509_EXTENSION) *csr_exts = X509_REQ_get_extensions(csr);
1211-
if (!csr_exts) return nullptr;
1212-
return (STACK_OF(GENERAL_NAME) *)X509V3_get_d2i(
1213-
csr_exts,
1214-
NID_subject_alt_name,
1215-
NULL,
1216-
NULL // at least once
1217-
);
1218-
}
1219-
1220-
12211246
Array HHVM_FUNCTION(openssl_csr_get_dns_names, const Variant& csr) {
12221247
auto pcsr = CSRequest::Get(csr);
12231248
if (!pcsr) throw_openssl_exception("Could not parse CSR");
12241249
auto ret = Array::CreateVec();
1225-
auto *subject_alt_names = openssl_csr_get_subject_alt_names(pcsr->csr());
1250+
auto *subject_alt_names = csr_get_subject_alt_names(pcsr->csr());
12261251
if (!subject_alt_names) return ret;
12271252
// get DNS names from SANs
12281253
for (int i = 0; i < sk_GENERAL_NAME_num(subject_alt_names); i++){
@@ -1240,26 +1265,11 @@ Array HHVM_FUNCTION(openssl_csr_get_dns_names, const Variant& csr) {
12401265
String HHVM_FUNCTION(openssl_csr_get_upn, const Variant& csr) {
12411266
auto pcsr = CSRequest::Get(csr);
12421267
if (!pcsr) throw_openssl_exception("Could not parse CSR");
1243-
auto *subject_alt_names = openssl_csr_get_subject_alt_names(pcsr->csr());
1268+
auto *subject_alt_names = csr_get_subject_alt_names(pcsr->csr());
12441269
if (!subject_alt_names) throw_openssl_exception("CSR contains no SANs");
1245-
// loop through all the SANs
1246-
for (int i = 0; i < sk_GENERAL_NAME_num(subject_alt_names); i++){
1247-
const GENERAL_NAME *general_name = sk_GENERAL_NAME_value(subject_alt_names, i);
1248-
if (!general_name) continue;
1249-
ASN1_OBJECT *oid = NULL;
1250-
ASN1_TYPE *value = NULL;
1251-
// seek an otherName
1252-
if(!GENERAL_NAME_get0_otherName(general_name, &oid, &value)) continue;
1253-
// compare its OID to MS UPN
1254-
if (OBJ_obj2nid(oid) != NID_ms_upn) continue;
1255-
// get the utf8string from the UPN
1256-
if(ASN1_TYPE_get(value) == V_ASN1_UTF8STRING) {
1257-
ASN1_UTF8STRING *upn = value->value.utf8string;
1258-
if (!upn || !upn->data) continue;
1259-
return String((char *)upn->data, upn->length, CopyString);
1260-
}
1261-
}
1262-
throw_openssl_exception("No valid UPNs in CSR");
1270+
auto upn = sk_SAN_get_upn(subject_alt_names);
1271+
if (!upn.has_value()) throw_openssl_exception("No valid UPNs in CSR");
1272+
return upn.value();
12631273
}
12641274

12651275
Variant HHVM_FUNCTION(openssl_error_string) {
@@ -2102,6 +2112,16 @@ Variant HHVM_FUNCTION(openssl_pkey_new,
21022112
}
21032113
}
21042114

2115+
String HHVM_FUNCTION(openssl_x509_get_upn, const Variant& certificate) {
2116+
auto pcert = Certificate::Get(certificate);
2117+
if (!pcert) throw_openssl_exception("Could not parse certificate");
2118+
auto *subject_alt_names = x509_get_subject_alt_names(pcert->get());
2119+
if (!subject_alt_names) throw_openssl_exception("Certificate contains no SANs");
2120+
auto upn = sk_SAN_get_upn(subject_alt_names);
2121+
if (!upn.has_value()) throw_openssl_exception("No valid UPNs in certificate");
2122+
return upn.value();
2123+
}
2124+
21052125
bool HHVM_FUNCTION(openssl_private_decrypt, const String& data,
21062126
Variant& decrypted,
21072127
const Variant& key,
@@ -3510,6 +3530,7 @@ struct opensslExtension final : Extension {
35103530
HHVM_FE(openssl_verify);
35113531
HHVM_FE(openssl_x509_check_private_key);
35123532
HHVM_FE(openssl_x509_checkpurpose);
3533+
HHVM_FE(openssl_x509_get_upn);
35133534
HHVM_FE(openssl_x509_export_to_file);
35143535
HHVM_FE(openssl_x509_export);
35153536
HHVM_FE(openssl_x509_parse);

hphp/runtime/ext/openssl/ext_openssl.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,6 +617,15 @@ function openssl_x509_checkpurpose(mixed $x509cert,
617617
varray<string> $cainfo = vec[],
618618
string $untrustedfile = ""): mixed;
619619

620+
/* openssl_x509_get_upn() returns the User Principal Name from the given certificate.
621+
* @param mixed $certificate - An x509 certificate that can be loaded by openssl_get_publickey
622+
* @return string - Returns the UPN if the certificate contains a valid one.
623+
* @throws Exception if the certificate could not be parsed, or if the it does not
624+
* contain a valid UPN.
625+
*/
626+
<<__Native>>
627+
function openssl_x509_get_upn(mixed $certificate): string;
628+
620629
/* openssl_x509_export_to_file() stores x509 into a file named by outfilename
621630
* in a PEM encoded format.
622631
* @param mixed $x509 - See Key/Certificate parameters for a list of valid
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
<?hh
2+
<<__EntryPoint>> function main(): void {
3+
/*
4+
How to generate these test certificates from a CSR containing the desired extensions/values
5+
$ cat >openssl.cnf <<EOF
6+
[ ca ]
7+
default_ca = CA_default
8+
[ CA_default ]
9+
copy_extensions = copyall
10+
default_md = sha256
11+
default_days = 1
12+
new_certs_dir = .
13+
database = ca.idx
14+
serial = ca.srl
15+
policy = POLICY_any
16+
[ POLICY_any ]
17+
countryName = optional
18+
stateOrProvinceName = optional
19+
organizationName = optional
20+
organizationalUnitName = optional
21+
commonName = supplied
22+
emailAddress = optional
23+
EOF
24+
$ touch ca.idx
25+
$ echo 01 > ca.srl
26+
$ openssl genrsa -out hphp_test_ca_key.pem 4096
27+
$ openssl req -x509 -sha256 -new -nodes -key hphp_test_ca_key.pem -days 3650 -out hphp_test_ca_cert.pem
28+
$ openssl ca -config openssl.cnf -cert hphp_test_ca_cert.pem -keyfile hphp_test_ca_key.pem -out hphp_test_cert.pem -in csr.pem
29+
*/
30+
31+
$cert_with_dns_names_and_upn = <<<EOF
32+
-----BEGIN CERTIFICATE-----
33+
MIIEsTCCApmgAwIBAgIBATANBgkqhkiG9w0BAQsFADBRMQswCQYDVQQGEwJYWDEV
34+
MBMGA1UEBwwMRGVmYXVsdCBDaXR5MRwwGgYDVQQKDBNEZWZhdWx0IENvbXBhbnkg
35+
THRkMQ0wCwYDVQQDDARURVNUMB4XDTI1MDkyNzAyMzAxMloXDTI1MDkyODAyMzAx
36+
MlowYTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExHDAaBgNVBAoT
37+
E01ldGEgUGxhdGZvcm1zIEluYy4xDTALBgNVBAsTBFRFU1QxEDAOBgNVBAMTB21z
38+
bGF2aW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDmZ+0x94OB0qnD
39+
CjEUVjsjwElQ7+wmMJMTIe9GGs4e7oTo2LddNQKIH9jUTkZXZmhK5lgGRqyagZjh
40+
0Kl+A8XCVG6Xu0lCsOubIoKsi3iXykovEDAekNeIVc0VcWcE3JEqpTnFdatd59I/
41+
2Ikwr2tw8AEiS2x/XYq0e7VzCR6pYBGqdS3Cusch8PQ9K4sZ70Dr03dX8FLG/kmt
42+
1vIFsStUW0rP0BdW6vFaFyxx7OIxrtzNNfmUfIx6AMSLatF/RQ9msffDvvHLbqne
43+
0LEnxxRhT88FSpGd2zvMUpELbhD4GfolCLD6VhaOPA8VaRN8abDSNx76vAd37Ppz
44+
pN9eu8gNAgMBAAGjgYMwgYAwPgYDVR0RBDcwNYIDZm9vggNiYXKCB0FTREYxMzKg
45+
IAYKKwYBBAGCNxQCA6ASDBBtc2xhdmluQG1ldGEuY29tMB0GA1UdDgQWBBRHmn77
46+
9xr9pxtPxIAP45qn1slmfTAfBgNVHSMEGDAWgBTwdI1xiPvAcpmCmLiXrkfQUGzd
47+
PDANBgkqhkiG9w0BAQsFAAOCAgEAAn30tLxFMPwAy0i1ogAu1DoS2vo3LKgjUAjZ
48+
VXKbEPpsGHH5RG7h822ATdPoYwdECvpK7qBbUMYX/z1736cJTrateP/h1byeGpOz
49+
HrSn3j7dURWbEz3XD+ZDQDjBOTsHAaKueg3FHfHe1q9WY7PmJt35Sofrsz8GdcKb
50+
A/NhS9tCkmyWFsAYch5byiXxHlSfj20r/v+HEFbPEZ9UPwy2DWNF92Z2DXW98Fd4
51+
FW6DQCYK3jQ8Y9k+gWxtTBND4uRMJ3DY9tE/f066FVrFGs166NBgrlE1kE2w226s
52+
dNsdw4JxabMi1nP3M6TKiDtIKm06eDNG51EHN9GvJNpmfKSp1AquV9byN6J2V0iT
53+
Nbh4jw4WU5GX+QHoj3TCSB2HZ6V8G9tQfeHvbsnmDNuWhY+I8Kwiw9r/vUZPgh3A
54+
goKbp0sPD3M1Ye3twa566BOiLhI3WfY3XuWQNN63wljxlthMGkMYapbJ/df1buJP
55+
L/fXx02uUoFzG8G8ZS7mC41Wu+TaoPaGyhTAWKmJ2iAefzzdr+3KSm5X8KnKNIlf
56+
yXAbSBpanqkSdUv+GQOsAzL15Qt1vUEaQAY9Ldyg7E1dTyP6kf/5cQ/NswnEl2fF
57+
6moLdovHI4vpW/cPl0kJv7v3llEiid7e/6fbxT+Zg6dG+u34dbb08GAQ8rZauu5E
58+
lKDaMTI=
59+
-----END CERTIFICATE-----
60+
EOF;
61+
// UPN printed
62+
var_dump(openssl_x509_get_upn($cert_with_dns_names_and_upn));
63+
$cert_with_upn_without_dns_names = <<<EOF
64+
-----BEGIN CERTIFICATE-----
65+
MIIEnDCCAoSgAwIBAgIBATANBgkqhkiG9w0BAQsFADBRMQswCQYDVQQGEwJYWDEV
66+
MBMGA1UEBwwMRGVmYXVsdCBDaXR5MRwwGgYDVQQKDBNEZWZhdWx0IENvbXBhbnkg
67+
THRkMQ0wCwYDVQQDDARURVNUMB4XDTI1MDkyNzAyMzgxMVoXDTI1MDkyODAyMzgx
68+
MVowYTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExHDAaBgNVBAoT
69+
E01ldGEgUGxhdGZvcm1zIEluYy4xDTALBgNVBAsTBFRFU1QxEDAOBgNVBAMTB21z
70+
bGF2aW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaOjkmdJbTDZ8k
71+
KjEhheD0CildgEoXHRnkPR4gH5kd+UGSopKTujPlQan2yguA7k4Sfdq4GywtCJSP
72+
ZRwf6w+4UWH7UFUDGsxQ0iPE4sOrx+IhVQO9qLJuJaqcNn77Fxp2VBrXOjYVz8u5
73+
9MdHp3WuQpbLUZR0lAGoEiP9F6QtCV6ztmJkGSIZI60MnbXS3maqBkCG3PzFEUN6
74+
Gn2xeIj1E6GVOaCKkTKiXPN8Sm89ect0HpYjMan+mYUeriTZqVeDe4oXtYc8ekoe
75+
fw/EiaCnLJA0xml7R179iFlFILkQUdbQmR59peu4J2r6KRiF6w1QUoF1wKmfD/pR
76+
y3/hOBHlAgMBAAGjbzBtMCsGA1UdEQQkMCKgIAYKKwYBBAGCNxQCA6ASDBBtc2xh
77+
dmluQG1ldGEuY29tMB0GA1UdDgQWBBQKQLxHIkSDG7UHMgSZNZEBopuQgjAfBgNV
78+
HSMEGDAWgBTwdI1xiPvAcpmCmLiXrkfQUGzdPDANBgkqhkiG9w0BAQsFAAOCAgEA
79+
Kf+SDBLMUKs+VI1Uy3bJdgDOwz1v1P3ASNoLhpW1S413bkXFSmve7+wzEQR9wtxX
80+
lNdnrmZHFlhaM2g97BTPPA/agW4u3oQ90kePz1uFCq/+KJqne6MbnJ0bkAlwUKsM
81+
wSrZplqqKUnFX2SWRVaQgjXm7LxEKAl0XdAyTXNqen1Lhyb7OeBudtcwQ6DiLR9H
82+
j8aHNI/HNKQanvySYMIwFEu7qIWFC14igBziOGQAurX9gUsJvPuyMMq9BvQXNS9Q
83+
3gwrTZPTlwXAqvrhm0YfmViIJXrOqo8kqM3xC3aL2K4v+1rL3stgsMQwtU0H3Hgw
84+
haBHPzufLsrmznH7hXJSNEl9ELVu9pqaNfWeEqfcRYFNieoVaAtkUCev+6kB+jOf
85+
0l3UCwJkYZkuK6JrCLACxnce8G250we2GprcH7bPNOVhXZlRidQd1X+mudA67CSd
86+
ir7Ca/FJony7/b4/DTFUwLYhCwGZgezJblcVdUG/T7AEBCFIWpfxqmkcWNsYAUts
87+
cP8f3TyGhS1sZvIXt017gKdY6nqk7YoIQsYKQi7kZYJlCpIcYJj9NvT/gm5KDABQ
88+
qeHuCAOmkNeOGbvF9je2wT5ST0Q2R8zZuGljuIFQ1g4yAWES589YrhIe+tM9+q4R
89+
LXG5OhPW804mq+e+3si5GZ9DyMMQN83AiYpF5tXOzwU=
90+
-----END CERTIFICATE-----
91+
EOF;
92+
// UPN printed
93+
var_dump(openssl_x509_get_upn($cert_with_upn_without_dns_names));
94+
// get one of these
95+
$cert_with_dns_names_no_upn = <<<EOF
96+
-----BEGIN CERTIFICATE-----
97+
MIIEgTCCAmmgAwIBAgIBATANBgkqhkiG9w0BAQsFADBRMQswCQYDVQQGEwJYWDEV
98+
MBMGA1UEBwwMRGVmYXVsdCBDaXR5MRwwGgYDVQQKDBNEZWZhdWx0IENvbXBhbnkg
99+
THRkMQ0wCwYDVQQDDARURVNUMB4XDTI1MDkyNzAyNDEyM1oXDTI1MDkyODAyNDEy
100+
M1owYTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExHDAaBgNVBAoT
101+
E01ldGEgUGxhdGZvcm1zIEluYy4xDTALBgNVBAsTBFRFU1QxEDAOBgNVBAMTB21z
102+
bGF2aW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+3VQXmJtjSzvt
103+
QnAaykWEeT0F0LZc9Z5tBAZ+3JXZU6YUKcdqhfWjXqzCOguQ2cAssk1SVX/LUwKQ
104+
YzLn5vU+aB7FIAEm39hKqKl4s7JeLy4uzYo1Od44GHMGqn8Xsna+dctOu67CbSci
105+
t0XQ3kt7Ec/RLcu87qSNRMgGxSVB3vQ9ewTjSoCMS5sLqFf7XEQZApyMj82gMYz7
106+
ddgVZzW4Tna+B8B4MVpXq5VzJAuYNPr07CFV3GvIFkmdWftYXiXqmSa+YfKfdSYw
107+
+3EOwj8XAacHx0X2GMATkk7g5EBVoqCA3aqKLE0w5uxThDMz+JSVScHV9HCPE3Gr
108+
GqGekOuJAgMBAAGjVDBSMBAGA1UdEQQJMAeCBTEyMzQ1MB0GA1UdDgQWBBSco1+q
109+
Yojkw0Nrviw3NcoLOmu9nDAfBgNVHSMEGDAWgBTwdI1xiPvAcpmCmLiXrkfQUGzd
110+
PDANBgkqhkiG9w0BAQsFAAOCAgEAhiQXdVwcy6kztR1nNTMoIeEoZXWTAWRZzZZd
111+
cNoSmIvLIc9pxq+FMGIKJ5IAtlupg9lW3iXvD5FXw7TVedvCXq2SHIkj7viTzoYZ
112+
HAEsSG8n5N2wsdjjagtlubzDovzLFkfRaEuxCV4XgaLX49gfQfTI0feCEgPqWsRC
113+
u4BM1lyuTYEAh7txzXAtMIGDFKERr3Bq8BNbHBhIWashopx67lkMbXuSXOQ7kjBr
114+
CLiLdW6L6dQTLhwFFB/ncsunLIYLQvLgXwmp4HXnVevGWVgRlq2NDka/Lj9SjgGW
115+
aEd1km6c4/dFt05C/YqX4V4lgILw9LScVLQKqo0lNN7rOjcn3+c0R+rAus+wonXY
116+
1wG54VqXkzplg9wPzwG7YvSapWr33c1lkisv7s5ErT9nVevrtsDD2BgTlgO2EqV1
117+
ZnWToXgGhU9wUVfNPXP/h3oCK/2kIUFr4pSAGrXklm+vvWUFk2JrrQ5qRiaBGxQd
118+
dALGjdKKUaSWDmfLiQx2fQklzUMRSdHg5q00GCpxwL84nVJFlIQ0YcThmf98cBEU
119+
d6YDw3VRI3ZI3fVddmNzgpsYTY+JdMW5InHDiwembfW3JJbowkbXwhpllqeg9Ve8
120+
OovcG5JlLMro2K6yPkz5EQwsVkYLx3PLFERjfdqDSThTUuWZ/43GuXFfdDTCJXtR
121+
6lSsekU=
122+
-----END CERTIFICATE-----
123+
EOF;
124+
// no UPN in this certificate
125+
try {
126+
var_dump(openssl_x509_get_upn($cert_with_dns_names_no_upn));
127+
}
128+
catch (OpenSSLException $e) {
129+
echo $e->getMessage()."\n";
130+
}
131+
try {
132+
var_dump(openssl_x509_get_upn("not a certificate"));
133+
}
134+
catch (OpenSSLException $e) {
135+
echo $e->getMessage()."\n";
136+
}
137+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
string(16) "mslavin@meta.com"
2+
string(16) "mslavin@meta.com"
3+
No valid UPNs in certificate
4+
Could not parse certificate

0 commit comments

Comments
 (0)