]> granicus.if.org Git - php/commitdiff
Added SAN matching during peer verification
authorDaniel Lowrey <rdlowrey@gmail.com>
Tue, 8 Oct 2013 16:37:44 +0000 (12:37 -0400)
committerDaniel Lowrey <rdlowrey@gmail.com>
Tue, 8 Oct 2013 16:37:44 +0000 (12:37 -0400)
ext/openssl/openssl.c
ext/openssl/tests/san-ca.pem [new file with mode: 0644]
ext/openssl/tests/san-cert.pem [new file with mode: 0644]
ext/openssl/tests/san-peer-matching.phpt [new file with mode: 0644]

index 90329d06caa6d24216a05caedb78d9d733c24f68..c77405762304bfa5cd549100771ca88392df8bab 100644 (file)
@@ -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 (file)
index 0000000..88682ba
--- /dev/null
@@ -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 (file)
index 0000000..923d490
--- /dev/null
@@ -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 (file)
index 0000000..4e6531d
--- /dev/null
@@ -0,0 +1,60 @@
+--TEST--
+Peer verification matches SAN names
+--SKIPIF--
+<?php 
+if (!extension_loaded("openssl")) die("skip");
+if (!function_exists('pcntl_fork')) die("skip no fork");
+--FILE--
+<?php
+$context = stream_context_create(array(
+       'ssl' => 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)