From 1970b964430a357d9c9acf01268849d86a99f4ec Mon Sep 17 00:00:00 2001 From: Daniel Lowrey Date: Tue, 8 Oct 2013 12:37:44 -0400 Subject: [PATCH 1/2] Added SAN matching during peer verification --- ext/openssl/openssl.c | 71 +++++++++++++++++------- ext/openssl/tests/san-ca.pem | 15 +++++ ext/openssl/tests/san-cert.pem | 31 +++++++++++ ext/openssl/tests/san-peer-matching.phpt | 60 ++++++++++++++++++++ 4 files changed, 158 insertions(+), 19 deletions(-) create mode 100644 ext/openssl/tests/san-ca.pem create mode 100644 ext/openssl/tests/san-cert.pem create mode 100644 ext/openssl/tests/san-peer-matching.phpt diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index 90329d06caa6d..c77405762304b 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -4951,7 +4951,7 @@ static int verify_callback(int preverify_ok, X509_STORE_CTX *ctx) /* {{{ */ } /* }}} */ -static zend_bool php_openssl_match_cn(const char *subjectname, const char *certname) +static int matches_wildcard_name(const char *subjectname, const char *certname) { char *wildcard; int prefix_len, suffix_len, subject_len; @@ -4983,12 +4983,55 @@ static zend_bool php_openssl_match_cn(const char *subjectname, const char *certn return 0; } +static int matches_san_list(X509 *peer, const char *subject_name) +{ + int is_match, i; + unsigned char *cert_name; + GENERAL_NAMES *alt_names = X509_get_ext_d2i(peer, NID_subject_alt_name, 0, 0); + int alt_name_count = sk_GENERAL_NAME_num(alt_names); + + for (i = 0; i < alt_name_count; i++) { + GENERAL_NAME *san = sk_GENERAL_NAME_value(alt_names, i); + + if (GEN_DNS == san->type) { + ASN1_STRING_to_UTF8(&cert_name, san->d.dNSName); + is_match = matches_wildcard_name(subject_name, cert_name); + OPENSSL_free(cert_name); + } + + if (is_match) { + break; + } + } + + return is_match; +} + +static int matches_common_name(X509 *peer, const char *subject_name) +{ + char buf[1024]; + X509_NAME *cert_name; + cert_name = X509_get_subject_name(peer); + int cert_name_len = X509_NAME_get_text_by_NID(cert_name, NID_commonName, buf, sizeof(buf)); + + if (cert_name_len == -1) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to locate peer certificate CN"); + return 0; + } else if (cert_name_len != strlen(buf)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Peer certificate CN=`%.*s' is malformed", cert_name_len, buf); + return 0; + } else if (matches_wildcard_name(subject_name, buf)) { + return 1; + } else { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Peer certificate CN=`%.*s' did not match expected CN=`%s'", cert_name_len, buf, subject_name); + return 0; + } +} + int php_openssl_apply_verification_policy(SSL *ssl, X509 *peer, php_stream *stream TSRMLS_DC) /* {{{ */ { zval **val = NULL; char *cnmatch = NULL; - X509_NAME *name; - char buf[1024]; int err; /* verification is turned off */ @@ -5030,24 +5073,14 @@ int php_openssl_apply_verification_policy(SSL *ssl, X509 *peer, php_stream *stre } } - name = X509_get_subject_name(peer); - - /* Does the common name match ? (used primarily for https://) */ GET_VER_OPT_STRING("CN_match", cnmatch); - if (cnmatch) { - int name_len = X509_NAME_get_text_by_NID(name, NID_commonName, buf, sizeof(buf)); - if (name_len == -1) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to locate peer certificate CN"); - return FAILURE; - } else if (name_len != strlen(buf)) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Peer certificate CN=`%.*s' is malformed", name_len, buf); - return FAILURE; - } - - if (!php_openssl_match_cn(cnmatch, buf)) { - /* didn't match */ - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Peer certificate CN=`%.*s' did not match expected CN=`%s'", name_len, buf, cnmatch); + if (cnmatch) { + if (matches_san_list(peer, cnmatch)) { + return SUCCESS; + } else if (matches_common_name(peer, cnmatch)) { + return SUCCESS; + } else { return FAILURE; } } diff --git a/ext/openssl/tests/san-ca.pem b/ext/openssl/tests/san-ca.pem new file mode 100644 index 0000000000000..88682ba2dcf65 --- /dev/null +++ b/ext/openssl/tests/san-ca.pem @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICYTCCAcqgAwIBAgIJAIaqxtY5dwjtMA0GCSqGSIb3DQEBBQUAMFMxCzAJBgNV +BAYTAlVTMQswCQYDVQQIEwJNTjEUMBIGA1UEBxMLTWlubmVhcG9saXMxITAfBgNV +BAsTGERvbWFpbiBDb250cm9sIFZhbGlkYXRlZDAeFw0xMzA5MjQwODA1NTFaFw0y +MTEyMTEwODA1NTFaMFMxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJNTjEUMBIGA1UE +BxMLTWlubmVhcG9saXMxITAfBgNVBAsTGERvbWFpbiBDb250cm9sIFZhbGlkYXRl +ZDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAsFGqfbU/8D+KjroQl4XMyt9m +dcSP7iZtqphOu9nVZxYAAqfaqj8FnC/pwYV3TU6ZHndLTQAllwYT3sQBQPPGmZQ9 +clSIMEL003t3pi4ZVXkttG6Vvr+Z9PBcHhlKLQ7WMHnn4qctllWXTSoyTQpkETF3 +Fc3mrG5G37BhoUno7NECAwEAAaM9MDswOQYDVR0RBDIwMIILZXhhbXBsZS5vcmeC +D3d3dy5leGFtcGxlLm9yZ4IQdGVzdC5leGFtcGxlLm9yZzANBgkqhkiG9w0BAQUF +AAOBgQBf/FZhzheIcQJ+dyTk8xQ/nJLvpmBhbd1LNtfwk/MsC9UHsz4QXs9sBw1k +rH0FjoqgM6avj7zKHJFTj6q7Rd+OX5V4HynYPhX67sWbN3KWEHffL98nGGd/bo3X +pSjNk5vnyKYiwdUUe11Ac9csh0HcSBbhOYjy0T/i9AlQcKbuCg== +-----END CERTIFICATE----- diff --git a/ext/openssl/tests/san-cert.pem b/ext/openssl/tests/san-cert.pem new file mode 100644 index 0000000000000..923d490e72fd3 --- /dev/null +++ b/ext/openssl/tests/san-cert.pem @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIICYTCCAcqgAwIBAgIJAIaqxtY5dwjtMA0GCSqGSIb3DQEBBQUAMFMxCzAJBgNV +BAYTAlVTMQswCQYDVQQIEwJNTjEUMBIGA1UEBxMLTWlubmVhcG9saXMxITAfBgNV +BAsTGERvbWFpbiBDb250cm9sIFZhbGlkYXRlZDAeFw0xMzA5MjQwODA1NTFaFw0y +MTEyMTEwODA1NTFaMFMxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJNTjEUMBIGA1UE +BxMLTWlubmVhcG9saXMxITAfBgNVBAsTGERvbWFpbiBDb250cm9sIFZhbGlkYXRl +ZDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAsFGqfbU/8D+KjroQl4XMyt9m +dcSP7iZtqphOu9nVZxYAAqfaqj8FnC/pwYV3TU6ZHndLTQAllwYT3sQBQPPGmZQ9 +clSIMEL003t3pi4ZVXkttG6Vvr+Z9PBcHhlKLQ7WMHnn4qctllWXTSoyTQpkETF3 +Fc3mrG5G37BhoUno7NECAwEAAaM9MDswOQYDVR0RBDIwMIILZXhhbXBsZS5vcmeC +D3d3dy5leGFtcGxlLm9yZ4IQdGVzdC5leGFtcGxlLm9yZzANBgkqhkiG9w0BAQUF +AAOBgQBf/FZhzheIcQJ+dyTk8xQ/nJLvpmBhbd1LNtfwk/MsC9UHsz4QXs9sBw1k +rH0FjoqgM6avj7zKHJFTj6q7Rd+OX5V4HynYPhX67sWbN3KWEHffL98nGGd/bo3X +pSjNk5vnyKYiwdUUe11Ac9csh0HcSBbhOYjy0T/i9AlQcKbuCg== +-----END CERTIFICATE----- +-----BEGIN PRIVATE KEY----- +MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALBRqn21P/A/io66 +EJeFzMrfZnXEj+4mbaqYTrvZ1WcWAAKn2qo/BZwv6cGFd01OmR53S00AJZcGE97E +AUDzxpmUPXJUiDBC9NN7d6YuGVV5LbRulb6/mfTwXB4ZSi0O1jB55+KnLZZVl00q +Mk0KZBExdxXN5qxuRt+wYaFJ6OzRAgMBAAECgYB11e5iWvqjPmQEZRdnnJU0VD8u +n7ItT+Nk6qtb4gY8Abj6DWIW+01th5vqqJ8FvGyartFVYa69kuM+srG/zevAZWeu +fGZtwiwZR4DRSyRcPp4rnNiksK3dkAZA6UewmRDPv8uyHJlXc5i+Ft1ILJ5Q5jgn +UkC4z3EJP5Se9KZywQJBAOO4lRq42wLsYr2SDrQDSs4leie3FKc2bgvjF7Djosh1 +ZYbf55F5b9w1zgnccmni2HkqOnyFu4SKarmXyCsYxrkCQQDGNvnUh7/zZswrdWZ/ +PMp9zVDTh/5Oc2B4ByNLw1ERDwYhjchKgPRlQvn4cp3Pwf3UYPQ/8XGXzzEJey3A +r0rZAkBf/tDEOgcBPXsGZQrTscuYCU5sbY5ESvqrAilbhSp7DJom+D5bIfEYyIm5 +uHd20Yzlzvpmwc1huyPwZt6X5FLpAkATDReoGMAXSesXxjnqwtIHk2NQYYLM0YQV +JUJ8NrKk/Bevw+vbVVeoH+7ctU97t36JGiR/vNoZKD3jVmaIXZDJAkEA4wJbwzIo +L32mu9VmZa7wjmfkraQEmXTPaA5D9lNC0AwRTgkj+x2Qe1vawNblNK9PPLBDdplQ +L//53ADq/wv5rA== +-----END PRIVATE KEY----- diff --git a/ext/openssl/tests/san-peer-matching.phpt b/ext/openssl/tests/san-peer-matching.phpt new file mode 100644 index 0000000000000..4e6531d6cc6b6 --- /dev/null +++ b/ext/openssl/tests/san-peer-matching.phpt @@ -0,0 +1,60 @@ +--TEST-- +Peer verification matches SAN names +--SKIPIF-- + array( + 'local_cert' => __DIR__ . '/san-cert.pem', + 'allow_self_signed' => true, + ), +)); + +$server = stream_socket_server('ssl://127.0.0.1:64321', $errno, $errstr, + STREAM_SERVER_BIND|STREAM_SERVER_LISTEN, $context); + + +$pid = pcntl_fork(); +if ($pid == -1) { + die('could not fork'); +} else if ($pid) { + $contextC = stream_context_create( + array( + 'ssl' => array( + 'verify_peer' => true, + 'cafile' => __DIR__ . '/san-ca.pem', + 'CN_match' => 'example.org', + ) + ) + ); + var_dump(stream_socket_client("ssl://127.0.0.1:64321", $errno, $errstr, 1, + STREAM_CLIENT_CONNECT, $contextC)); + + $contextC = stream_context_create(array( + 'ssl' => array( + 'verify_peer' => true, + 'cafile' => __DIR__ . '/san-ca.pem', + 'CN_match' => 'moar.example.org', + ) + )); + + var_dump(stream_socket_client("ssl://127.0.0.1:64321", $errno, $errstr, 1, + STREAM_CLIENT_CONNECT, $contextC)); + +} else { + @pcntl_wait($status); + @stream_socket_accept($server, 1); + @stream_socket_accept($server, 1); +} +--EXPECTF-- +resource(%d) of type (stream) + +Warning: stream_socket_client(): Unable to locate peer certificate CN in %s on line %d + +Warning: stream_socket_client(): Failed to enable crypto in %s on line %d + +Warning: stream_socket_client(): unable to connect to ssl://127.0.0.1:64321 (Unknown error) in %s on line %d +bool(false) From a40dd6e963e35ecc0708eebaa22a63700ade69e3 Mon Sep 17 00:00:00 2001 From: Daniel Lowrey Date: Wed, 9 Oct 2013 09:55:36 -0400 Subject: [PATCH 2/2] Changed return types to zend_bool, renamed test --- ext/openssl/openssl.c | 19 +++++++++++-------- ...r-matching.phpt => san_peer_matching.phpt} | 0 2 files changed, 11 insertions(+), 8 deletions(-) rename ext/openssl/tests/{san-peer-matching.phpt => san_peer_matching.phpt} (100%) diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index c77405762304b..43b3e0cadd285 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -4951,7 +4951,7 @@ static int verify_callback(int preverify_ok, X509_STORE_CTX *ctx) /* {{{ */ } /* }}} */ -static int matches_wildcard_name(const char *subjectname, const char *certname) +static zend_bool matches_wildcard_name(const char *subjectname, const char *certname) { char *wildcard; int prefix_len, suffix_len, subject_len; @@ -4983,10 +4983,12 @@ static int matches_wildcard_name(const char *subjectname, const char *certname) return 0; } -static int matches_san_list(X509 *peer, const char *subject_name) +static zend_bool matches_san_list(X509 *peer, const char *subject_name) { - int is_match, i; + int i; + zend_bool is_match = 0; unsigned char *cert_name; + GENERAL_NAMES *alt_names = X509_get_ext_d2i(peer, NID_subject_alt_name, 0, 0); int alt_name_count = sk_GENERAL_NAME_num(alt_names); @@ -5007,25 +5009,26 @@ static int matches_san_list(X509 *peer, const char *subject_name) return is_match; } -static int matches_common_name(X509 *peer, const char *subject_name) +static zend_bool matches_common_name(X509 *peer, const char *subject_name) { char buf[1024]; X509_NAME *cert_name; + zend_bool is_match = 0; + cert_name = X509_get_subject_name(peer); int cert_name_len = X509_NAME_get_text_by_NID(cert_name, NID_commonName, buf, sizeof(buf)); if (cert_name_len == -1) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to locate peer certificate CN"); - return 0; } else if (cert_name_len != strlen(buf)) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Peer certificate CN=`%.*s' is malformed", cert_name_len, buf); - return 0; } else if (matches_wildcard_name(subject_name, buf)) { - return 1; + is_match = 1; } else { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Peer certificate CN=`%.*s' did not match expected CN=`%s'", cert_name_len, buf, subject_name); - return 0; } + + return is_match; } int php_openssl_apply_verification_policy(SSL *ssl, X509 *peer, php_stream *stream TSRMLS_DC) /* {{{ */ diff --git a/ext/openssl/tests/san-peer-matching.phpt b/ext/openssl/tests/san_peer_matching.phpt similarity index 100% rename from ext/openssl/tests/san-peer-matching.phpt rename to ext/openssl/tests/san_peer_matching.phpt