]> granicus.if.org Git - php/commitdiff
Add TLS ALPN extension support in crypto client/server streams
authorDaniel Lowrey <rdlowrey@php.net>
Sat, 28 Feb 2015 20:35:25 +0000 (15:35 -0500)
committerDaniel Lowrey <rdlowrey@php.net>
Sat, 28 Feb 2015 22:41:30 +0000 (17:41 -0500)
ext/openssl/xp_ssl.c
ext/standard/file.c
ext/standard/streamsfuncs.c

index 3afdb3c40928d6426fc6bd6e2e99a4d12ffd99f7..7429663a268feba7ff08666d9c85872aa1790f54 100644 (file)
@@ -73,6 +73,9 @@
 #if OPENSSL_VERSION_NUMBER >= 0x00908070L
 #define HAVE_TLS_SNI 1
 #endif
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L
+#define HAVE_TLS_ALPN 1
+#endif
 #endif
 
 
@@ -117,6 +120,14 @@ typedef struct _php_openssl_handshake_bucket_t {
        unsigned should_close;
 } php_openssl_handshake_bucket_t;
 
+#ifdef HAVE_TLS_ALPN
+/* Holds the available server ALPN protocols for negotiation */
+typedef struct _php_openssl_alpn_ctx_t {
+    unsigned char *data;
+    unsigned short len;
+} php_openssl_alpn_ctx;
+#endif
+
 /* This implementation is very closely tied to the that of the native
  * sockets implemented in the core.
  * Don't try this technique in other extensions!
@@ -133,6 +144,9 @@ typedef struct _php_openssl_netstream_data_t {
        php_openssl_handshake_bucket_t *reneg;
        php_openssl_sni_cert_t *sni_certs;
        unsigned sni_cert_count;
+#ifdef HAVE_TLS_ALPN
+       php_openssl_alpn_ctx *alpn_ctx;
+#endif
        char *url_name;
        unsigned state_set:1;
        unsigned _spare:31;
@@ -1405,6 +1419,67 @@ static void enable_client_sni(php_stream *stream, php_openssl_netstream_data_t *
 /* }}} */
 #endif
 
+
+#ifdef HAVE_TLS_ALPN
+/*-
+ * alpn_protos_parse parses a comma separated list of strings into a string
+ * in a format suitable for passing to SSL_CTX_set_next_protos_advertised.
+ *   outlen: (output) set to the length of the resulting buffer on success.
+ *   err: (maybe NULL) on failure, an error message line is written to this BIO.
+ *   in: a NUL termianted string like "abc,def,ghi"
+ *
+ *   returns: an emalloced buffer or NULL on failure.
+ */
+static unsigned char *alpn_protos_parse(unsigned short *outlen, const char *in)
+{
+       size_t len;
+       unsigned char *out;
+       size_t i, start = 0;
+
+       len = strlen(in);
+       if (len >= 65535) {
+               return NULL;
+       }
+
+       out = emalloc(strlen(in) + 1);
+       if (!out) {
+               return NULL;
+       }
+
+       for (i = 0; i <= len; ++i) {
+               if (i == len || in[i] == ',') {
+                       if (i - start > 255) {
+                               efree(out);
+                               return NULL;
+                       }
+                       out[start] = i - start;
+                       start = i + 1;
+               } else {
+                       out[i + 1] = in[i];
+               }
+       }
+
+       *outlen = len + 1;
+
+       return out;
+}
+
+static int server_alpn_callback(SSL *ssl_handle, const unsigned char **out, unsigned char *outlen,
+                   const unsigned char *in, unsigned int inlen, void *arg)
+{
+       php_openssl_netstream_data_t *sslsock = arg;
+
+       if (SSL_select_next_proto
+               ((unsigned char **)out, outlen, sslsock->alpn_ctx->data, sslsock->alpn_ctx->len, in,
+                       inlen) != OPENSSL_NPN_NEGOTIATED) {
+               return SSL_TLSEXT_ERR_NOACK;
+       }
+
+       return SSL_TLSEXT_ERR_OK;
+}
+
+#endif
+
 int php_openssl_setup_crypto(php_stream *stream,
                php_openssl_netstream_data_t *sslsock,
                php_stream_xport_crypto_param *cparam
@@ -1414,6 +1489,7 @@ int php_openssl_setup_crypto(php_stream *stream,
        int ssl_ctx_options;
        int method_flags;
        char *cipherlist = NULL;
+       char *alpn_protocols = NULL;
        zval *val;
 
        if (sslsock->ssl_handle) {
@@ -1499,6 +1575,37 @@ int php_openssl_setup_crypto(php_stream *stream,
                }
        }
 
+       GET_VER_OPT_STRING("alpn_protocols", alpn_protocols);
+       if (alpn_protocols) {
+#ifdef HAVE_TLS_ALPN
+               {
+                       unsigned short alpn_len;
+                       unsigned char *alpn = alpn_protos_parse(&alpn_len, alpn_protocols);
+
+                       if (alpn == NULL) {
+                               php_error_docref(NULL, E_WARNING, "Failed parsing comma-separated TLS ALPN protocol string");
+                               SSL_CTX_free(sslsock->ctx);
+                               sslsock->ctx = NULL;
+                               return FAILURE;
+                       }
+
+                       if (sslsock->is_client) {
+                               SSL_CTX_set_alpn_protos(sslsock->ctx, alpn, alpn_len);
+                       } else {
+                               sslsock->alpn_ctx = (php_openssl_alpn_ctx *) emalloc(sizeof(php_openssl_alpn_ctx));
+                               sslsock->alpn_ctx->data = (unsigned char*)estrndup((const char*)alpn, alpn_len);
+                               sslsock->alpn_ctx->len = alpn_len;
+                               SSL_CTX_set_alpn_select_cb(sslsock->ctx, server_alpn_callback, sslsock);
+                       }
+
+                       efree(alpn);
+               }
+#else
+               php_error_docref(NULL, E_WARNING,
+                       "alpn_protocols support is not compiled into the OpenSSL library against which PHP is linked");
+#endif
+       }
+
        if (FAILURE == set_local_cert(sslsock->ctx, stream)) {
                return FAILURE;
        }
@@ -1612,6 +1719,8 @@ static int php_openssl_crypto_info(php_stream *stream,
        const SSL_CIPHER *cipher;
        char *cipher_name, *cipher_version, *crypto_protocol;
        int cipher_bits;
+       const unsigned char *alpn_proto = NULL;
+       unsigned int alpn_proto_len = 0;
        int needs_array_return;
 
        if (!sslsock->ssl_active) {
@@ -1622,6 +1731,18 @@ static int php_openssl_crypto_info(php_stream *stream,
        zresult = cparam->inputs.zresult;
        needs_array_return = (cparam->inputs.infotype == STREAM_CRYPTO_INFO_ALL) ? 1 : 0;
 
+       if (cparam->inputs.infotype & STREAM_CRYPTO_INFO_ALPN_PROTOCOL) {
+#ifdef HAVE_TLS_ALPN
+               SSL_get0_alpn_selected(sslsock->ssl_handle, &alpn_proto, &alpn_proto_len);
+#endif
+               if (!needs_array_return) {
+                       if (alpn_proto_len > 0) {
+                               ZVAL_STRINGL(zresult, alpn_proto, alpn_proto_len);
+                       }
+                       return SUCCESS;
+               }
+       }
+
        if (cparam->inputs.infotype & STREAM_CRYPTO_INFO_CIPHER) {
                cipher = SSL_get_current_cipher(sslsock->ssl_handle);
 
@@ -1680,6 +1801,9 @@ static int php_openssl_crypto_info(php_stream *stream,
        add_assoc_string(zresult, "cipher_name", cipher_name);
        add_assoc_long(zresult, "cipher_bits", cipher_bits);
        add_assoc_string(zresult, "cipher_version", cipher_version);
+       if (alpn_proto) {
+               add_assoc_stringl(zresult, "alpn_protocol", (char *) alpn_proto, alpn_proto_len);
+       }
        Z_ARR_P(zresult);
 
        return SUCCESS;
index b001fdd79b8433c91281a7e6de37c2960ac29d8a..96fb6ea35ed3a4aa113d048c6b139559992c9886 100644 (file)
@@ -235,6 +235,7 @@ PHP_MINIT_FUNCTION(file)
        REGISTER_LONG_CONSTANT("STREAM_CRYPTO_INFO_CIPHER_NAME",        STREAM_CRYPTO_INFO_CIPHER_NAME,         CONST_CS|CONST_PERSISTENT);
        REGISTER_LONG_CONSTANT("STREAM_CRYPTO_INFO_CIPHER_BITS",        STREAM_CRYPTO_INFO_CIPHER_BITS,         CONST_CS|CONST_PERSISTENT);
        REGISTER_LONG_CONSTANT("STREAM_CRYPTO_INFO_CIPHER_VERSION",     STREAM_CRYPTO_INFO_CIPHER_VERSION,              CONST_CS|CONST_PERSISTENT);
+       REGISTER_LONG_CONSTANT("STREAM_CRYPTO_INFO_ALPN_PROTOCOL",      STREAM_CRYPTO_INFO_ALPN_PROTOCOL,               CONST_CS|CONST_PERSISTENT);
 
        REGISTER_LONG_CONSTANT("STREAM_SHUT_RD",        STREAM_SHUT_RD,         CONST_CS|CONST_PERSISTENT);
        REGISTER_LONG_CONSTANT("STREAM_SHUT_WR",        STREAM_SHUT_WR,         CONST_CS|CONST_PERSISTENT);
index a344ed69dbc282196371574ad7d0957f778e7287..6a44c34da131aa3b3760a7df0b2afd3a33f4544a 100644 (file)
@@ -1508,6 +1508,7 @@ PHP_FUNCTION(stream_socket_crypto_info)
                        case STREAM_CRYPTO_INFO_CIPHER_VERSION:
                        case STREAM_CRYPTO_INFO_CIPHER:
                        case STREAM_CRYPTO_INFO_PROTOCOL:
+                       case STREAM_CRYPTO_INFO_ALPN_PROTOCOL:
                        case STREAM_CRYPTO_INFO_ALL:
                                break;
                        default: