[PATCH] ssl: SSL_get0_verified_chain is available for LibreSSL >= 3.3.6
Maxim Dounin
mdounin at mdounin.ru
Sat Dec 23 21:30:32 UTC 2023
Hello!
On Fri, Dec 22, 2023 at 05:53:30PM +0400, Sergey Kandaurov wrote:
>
> > On 21 Dec 2023, at 02:40, Maxim Dounin <mdounin at mdounin.ru> wrote:
> >
> > Hello!
> >
> > On Tue, Dec 19, 2023 at 10:44:00PM +0400, Sergey Kandaurov wrote:
> >
> >>
> >>> On 19 Dec 2023, at 12:58, Maxim Dounin <mdounin at mdounin.ru> wrote:
> >>>
> >>> Hello!
> >>>
> >>> On Tue, Dec 19, 2023 at 02:09:10AM +0400, Sergey Kandaurov wrote:
> >>>
> >>>>> On 24 Nov 2023, at 00:29, Ilya Shipitsin <chipitsine at gmail.com> wrote:
> >>>>>
> >>>>> # HG changeset patch
> >>>>> # User Ilya Shipitsin <chipitsine at gmail.com>
> >>>>> # Date 1700769135 -3600
> >>>>> # Thu Nov 23 20:52:15 2023 +0100
> >>>>> # Node ID 2001e73ce136d5bfc9bde27d338865b14b8ad436
> >>>>> # Parent 7ec761f0365f418511e30b82e9adf80bc56681df
> >>>>> ssl: SSL_get0_verified_chain is available for LibreSSL >= 3.3.6
> >>>>
> >>>> style: SSL prefix should be uppercase.
> >>>>
> >>>>>
> >>>>> diff -r 7ec761f0365f -r 2001e73ce136 src/event/ngx_event_openssl_stapling.c
> >>>>> --- a/src/event/ngx_event_openssl_stapling.c Thu Oct 26 23:35:09 2023 +0300
> >>>>> +++ b/src/event/ngx_event_openssl_stapling.c Thu Nov 23 20:52:15 2023 +0100
> >>>>> @@ -893,7 +893,8 @@
> >>>>> ocsp->cert_status = V_OCSP_CERTSTATUS_GOOD;
> >>>>> ocsp->conf = ocf;
> >>>>>
> >>>>> -#if (OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined LIBRESSL_VERSION_NUMBER)
> >>>>> +/* minimum OpenSSL 1.1.1 & LibreSSL 3.3.6 */
> >>>>> +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined LIBRESSL_VERSION_NUMBER) || (defined(LIBRESSL_VERSION_NUMBER) && (LIBRESSL_VERSION_NUMBER >= 0x3030600L))
> >>>>>
> >>>>> ocsp->certs = SSL_get0_verified_chain(c->ssl->connection);
> >>>>>
> >>>>
> >>>> Testing "defined(LIBRESSL_VERSION_NUMBER)" is superfluous.
> >>>> The macro test suffers from a very long line.
> >>>>
> >>>> The correct version test seems to be against LibreSSL 3.5.0, see
> >>>> https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-3.5.0-relnotes.txt
> >>>>
> >>>> So, the resulting change would be as follows:
> >>>>
> >>>> diff --git a/src/event/ngx_event_openssl_stapling.c b/src/event/ngx_event_openssl_stapling.c
> >>>> --- a/src/event/ngx_event_openssl_stapling.c
> >>>> +++ b/src/event/ngx_event_openssl_stapling.c
> >>>> @@ -893,7 +893,9 @@ ngx_ssl_ocsp_validate(ngx_connection_t *
> >>>> ocsp->cert_status = V_OCSP_CERTSTATUS_GOOD;
> >>>> ocsp->conf = ocf;
> >>>>
> >>>> -#if (OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined LIBRESSL_VERSION_NUMBER)
> >>>> +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L \
> >>>> + && !defined LIBRESSL_VERSION_NUMBER) \
> >>>> + || LIBRESSL_VERSION_NUMBER >= 0x3050000fL
> >>>
> >>> Rather,
> >>>
> >>> +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L \
> >>> + && (!defined LIBRESSL_VERSION_NUMBER \
> >>> + || LIBRESSL_VERSION_NUMBER >= 0x3050000fL))
> >>>
> >>
> >> Agree. For the sake of completeness:
> >>
> >> # HG changeset patch
> >> # User Sergey Kandaurov <pluknet at nginx.com>
> >> # Date 1703004490 -14400
> >> # Tue Dec 19 20:48:10 2023 +0400
> >> # Node ID 267cee796462f4f6bacf825c8fd24d13845d36f4
> >> # Parent 7a6d52990f2e2d88460a3dc6cc84aac89b7329ea
> >> SSL: using SSL_get0_verified_chain() with LibreSSL 3.5.0+.
> >>
> >> Prodded by Ilya Shipitsin.
> >>
> >> diff --git a/src/event/ngx_event_openssl_stapling.c b/src/event/ngx_event_openssl_stapling.c
> >> --- a/src/event/ngx_event_openssl_stapling.c
> >> +++ b/src/event/ngx_event_openssl_stapling.c
> >> @@ -893,7 +893,9 @@ ngx_ssl_ocsp_validate(ngx_connection_t *
> >> ocsp->cert_status = V_OCSP_CERTSTATUS_GOOD;
> >> ocsp->conf = ocf;
> >>
> >> -#if (OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined LIBRESSL_VERSION_NUMBER)
> >> +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L \
> >> + && (!defined LIBRESSL_VERSION_NUMBER \
> >> + || LIBRESSL_VERSION_NUMBER >= 0x3050000fL))
> >>
> >> ocsp->certs = SSL_get0_verified_chain(c->ssl->connection);
> >>
> >>
> >>>>
> >>>> ocsp->certs = SSL_get0_verified_chain(c->ssl->connection);
> >>>>
> >>>>
> >>>> On the other hand, I don't like the resulting style mudness.
> >>>> It may have sense just to drop old LibreSSL versions support:
> >>>> maintaining one or two most recent stable branches should be enough.
> >>>
> >>> +1 on this.
> >>>
> >>> We certainly don't want to maintain support for ancient LibreSSL
> >>> versions. IMO, just two branches is more than enough (and this is
> >>> what I use in my testing, which usually means latest development
> >>> release and latest stable release).
> >>>
> >>> At most, this probably can be three branches - which seems to
> >>> match LibreSSL support policy,
> >>> https://www.libressl.org/releases.html:
> >>>
> >>> : LibreSSL transitions to a new stable release branch every 6 months
> >>> : in coordination with the OpenBSD development schedule. LibreSSL
> >>> : stable branches are updated for 1 year after their corresponding
> >>> : OpenBSD branch is tagged for release. See below for the current
> >>> : stable release branches.
> >>>
> >>> In either case, LibreSSL versions below 3.5.0 are already not
> >>> supported. If I understand correctly, right now the oldest
> >>> supported branch is 3.7.x.
> >>
> >> Agree. Also, Repology shows that modern popular distributions,
> >> such as Alpine Linux and FreeBSD, have at least LibreSSL 3.5.x:
> >> https://repology.org/project/libressl/versions
> >>
> >>>
> >>>> But anyway, I don't see an obvious win over the existing code:
> >>>> the certificate chain is reconstructed if SSL_get0_verified_chain()
> >>>> is (detected to be) not present, which should be fine in most cases.
> >>>>
> >>>> That said, it doesn't seem to deserve introducing 3-line macro test,
> >>>> or (see OTOH note) breaking old LibreSSL support for no apparent reason.
> >>>
> >>> Reconstruction of the chain implies verification of signatures
> >>> along the chain and can be costly. As such, it certainly would be
> >>> better to use SSL_get0_verified_chain() as long as it is
> >>> available.
> >>
> >> Agree.
> >> My point is that not using SSL_get0_verified_chain() should not result
> >> in a broken functionality, as in the OCSP cert validation.
> >> So, intention to start using it in LibreSSL may look an insufficient
> >> argument per se to drop old LibreSSL versions.
> >> Though, dropping them may be orthogonal to SSL_get0_verified_chain().
> >>
> >>>
> >>> Also, removing the "!defined LIBRESSL_VERSION_NUMBER" check might
> >>> be seen as positive even without any additional benefits.
> >>>
> >>> Along with that, however, we might want to adjust the
> >>> LIBRESSL_VERSION_NUMBER check in the ngx_event_openssl.h file, so
> >>> OPENSSL_VERSION_NUMBER is set to a better value for old LibreSSL
> >>> versions - for example, to only set OPENSSL_VERSION_NUMBER to
> >>> 0x1010000fL for LibreSSL 3.5.0 or above.
> >>
> >> Sounds like a plan if we are fine to drop older LibreSSL versions.
> >>
> >>> This might allow to
> >>> preserve limited compatibility with ancient LibreSSL versions
> >>> without additional efforts (not tested though).
> >>>
> >>
> >> This won't build with any LibreSSL version in the [2.8.0, 3.5.0) range.
> >> Particularly, SSL_CTX_sess_set_get_cb() has got a const argument in
> >> LibreSSL 2.8.0, which is not backward compatible, see 7337:cab37803ebb3.
> >> Another reason is SSL was made opaque by default in 3.4.x.
> >
> > The const argument can be easily ignored by using
> > -Wno-error=incompatible-function-pointer-types (or just
> > -Wno-error), which seems to be reasonable when trying to build
> > things with ancient libraries. This makes it possible to build
> > with LibreSSL 2.8.0-3.3.6 with minimal efforts.
>
> Ok, that makes sense.
> For the record, GCC uses another warning option name:
>
> src/event/ngx_event_openssl.c:3770:43: error: passing argument 2 of 'SSL_CTX_sess_set_get_cb' from incompatible pointer type [-Werror=incompatible-pointer-type]
>
> There is a typo in GCC 12 diagnostics, the actual option name
> appears to be -Wno-incompatible-pointer-types (note "s").
> BTW, it appears to be a valid option name in Clang as well
> to suppress such warnings.
Yep, obviously enough required options might be different in
different compilers.
> > For LibreSSL 3.4.x, the dependency on the SSL internals can be
> > easily eliminated by testing SSL_OP_NO_CLIENT_RENEGOTIATION, which
> > anyway seems to be a reasonable change.
>
> Agree, this is really true as tested on various LibreSSL versions,
> from 2.5.0 to 3.8.2 (SSL_OP_NO_CLIENT_RENEGOTIATION appeared in 2.6.0).
>
> >
> >>
> >> (Others seem not to affect building on older versions if pick up 3.5.0:
> >> - X509_up_ref appeared in LibreSSL 2.6.0, X509 made opaque in 3.5.0;
> >> - X509_get0_notAfter appeared in 2.7.0, X509_get_notAfter still there.)
> >>
> >> Personally I'm fine to drop ancient LibreSSL versions, because it has
> >> to happen someday and we don't want to maintain them eternally.
> >> Alternative patch for your consideration:
> >>
> >> # HG changeset patch
> >> # User Sergey Kandaurov <pluknet at nginx.com>
> >> # Date 1703011348 -14400
> >> # Tue Dec 19 22:42:28 2023 +0400
> >> # Node ID 94d4ef4a2316da66fea084952913ff2b0f84827d
> >> # Parent 7a6d52990f2e2d88460a3dc6cc84aac89b7329ea
> >> SSL: removed compatibility with LibreSSL < 3.5.0.
> >>
> >> OPENSSL_VERSION_NUMBER is now redefined to 0x1010000fL for LibreSSL 3.5.0+.
> >> As older versions starting from LibreSSL 2.8.0 won't build with a lesser
> >> OPENSSL_VERSION_NUMBER value (see 7337:cab37803ebb3 for details), they are
> >> now explicitly unsupported.
> >>
> >> Besides that, this allows to start using SSL_get0_verified_chain()
> >> with LibreSSL without additional macro tests.
> >>
> >> diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h
> >> --- a/src/event/ngx_event_openssl.h
> >> +++ b/src/event/ngx_event_openssl.h
> >> @@ -45,10 +45,10 @@
> >>
> >> #if (defined LIBRESSL_VERSION_NUMBER && OPENSSL_VERSION_NUMBER == 0x20000000L)
> >> #undef OPENSSL_VERSION_NUMBER
> >> -#if (LIBRESSL_VERSION_NUMBER >= 0x2080000fL)
> >> +#if (LIBRESSL_VERSION_NUMBER >= 0x3050000fL)
> >> #define OPENSSL_VERSION_NUMBER 0x1010000fL
> >> #else
> >> -#define OPENSSL_VERSION_NUMBER 0x1000107fL
> >> +#error LibreSSL too old, need at least 3.5.0
> >> #endif
> >> #endif
> >
> > I'm certainly against the idea of explicitly rejecting old
> > versions. As demonstrated above, even versions affected by
> > various changes can be used with minimal efforts, such as
> > disabling -Werror.
> >
> > For the record, here is a patch I tested with:
> >
> > diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c
> > --- a/src/event/ngx_event_openssl.c
> > +++ b/src/event/ngx_event_openssl.c
> > @@ -1105,7 +1105,8 @@ ngx_ssl_info_callback(const ngx_ssl_conn
> > BIO *rbio, *wbio;
> > ngx_connection_t *c;
> >
> > -#ifndef SSL_OP_NO_RENEGOTIATION
> > +#if (!defined SSL_OP_NO_RENEGOTIATION \
> > + && !defined SSL_OP_NO_CLIENT_RENEGOTIATION)
> >
> > if ((where & SSL_CB_HANDSHAKE_START)
> > && SSL_is_server((ngx_ssl_conn_t *) ssl_conn))
> > @@ -1838,9 +1839,10 @@ ngx_ssl_handshake(ngx_connection_t *c)
> > c->read->ready = 1;
> > c->write->ready = 1;
> >
> > -#ifndef SSL_OP_NO_RENEGOTIATION
> > -#if OPENSSL_VERSION_NUMBER < 0x10100000L
> > -#ifdef SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS
> > +#if (!defined SSL_OP_NO_RENEGOTIATION \
> > + && !defined SSL_OP_NO_CLIENT_RENEGOTIATION \
> > + && defined SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS \
> > + && OPENSSL_VERSION_NUMBER < 0x10100000L)
>
> Keeping macro tests nested allows to add/remove them with minimal diff.
> I don't mind though to combine primaries into a single test, this is
> what nginx happens to use in the rest style. The only justified
> exceptions I found that use nested tests are "#if (NGX_GNU_HURD)"
> and recently introduced "#if (NGX_QUIC)" in ngx_event_openssl.h.
Here nesting is mostly historic due to different tests added over
time. Given there are 4 tests now, and SSL_OP_NO_RENEGOTIATION is
combined with SSL_OP_NO_CLIENT_RENEGOTIATION in other places,
combining all the tests looks like the way to go.
>
> >
> > /* initial handshake done, disable renegotiation (CVE-2009-3555) */
> > if (c->ssl->connection->s3 && SSL_is_server(c->ssl->connection)) {
> > @@ -1848,8 +1850,6 @@ ngx_ssl_handshake(ngx_connection_t *c)
> > }
> >
> > #endif
> > -#endif
> > -#endif
> >
> > #if (defined BIO_get_ktls_send && !NGX_WIN32)
> >
> > @@ -2483,7 +2483,8 @@ ngx_ssl_handle_recv(ngx_connection_t *c,
> > int sslerr;
> > ngx_err_t err;
> >
> > -#ifndef SSL_OP_NO_RENEGOTIATION
> > +#if (!defined SSL_OP_NO_RENEGOTIATION \
> > + && !defined SSL_OP_NO_CLIENT_RENEGOTIATION)
> >
> > if (c->ssl->renegotiation) {
> > /*
> > diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h
> > --- a/src/event/ngx_event_openssl.h
> > +++ b/src/event/ngx_event_openssl.h
> > @@ -45,7 +45,7 @@
> >
> > #if (defined LIBRESSL_VERSION_NUMBER && OPENSSL_VERSION_NUMBER == 0x20000000L)
> > #undef OPENSSL_VERSION_NUMBER
> > -#if (LIBRESSL_VERSION_NUMBER >= 0x2080000fL)
> > +#if (LIBRESSL_VERSION_NUMBER >= 0x3050000fL)
> > #define OPENSSL_VERSION_NUMBER 0x1010000fL
> > #else
> > #define OPENSSL_VERSION_NUMBER 0x1000107fL
> > diff --git a/src/event/ngx_event_openssl_stapling.c b/src/event/ngx_event_openssl_stapling.c
> > --- a/src/event/ngx_event_openssl_stapling.c
> > +++ b/src/event/ngx_event_openssl_stapling.c
> > @@ -893,7 +893,7 @@ ngx_ssl_ocsp_validate(ngx_connection_t *
> > ocsp->cert_status = V_OCSP_CERTSTATUS_GOOD;
> > ocsp->conf = ocf;
> >
> > -#if (OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined LIBRESSL_VERSION_NUMBER)
> > +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
>
> Not sure if brackets are needed here, since it's no longer combined.
> Currently, there are 17 occurrences in nginx that don't use brackets,
> and 3 simple cases that do.
I think that brackets are generally needed, but
OPENSSL_VERSION_NUMBER used to be checked without brackets in most
cases. As such, I don't object either variant. If needed,
brackets can be added to all affected #if's by a separate
style-only commit.
>
> >
> > ocsp->certs = SSL_get0_verified_chain(c->ssl->connection);
> >
>
> Overall, things look good to me.
> As discussed, broke this into two changes:
>
> # HG changeset patch
> # User Sergey Kandaurov <pluknet at nginx.com>
> # Date 1703252997 -14400
> # Fri Dec 22 17:49:57 2023 +0400
> # Node ID 7fc017b776047b26c7e42b355a1bf142cf968537
> # Parent 500071f3265d259eb1917cd8367828834ff0ae14
> SSL: disabled renegotiation checks with LibreSSL.
>
> Similar to 7356:e3ba4026c02d, as long as SSL_OP_NO_CLIENT_RENEGOTIATION
> is defined, it is the library responsibility to prevent renegotiation.
>
> Additionally, this allows to raise LibreSSL version used to redefine
> OPENSSL_VERSION_NUMBER to 0x1010000fL, such that this won't result in
> attempts to dereference SSL objects made opaque in LibreSSL 3.4.0.
>
> Patch by Maxim Dounin.
>
> diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c
> --- a/src/event/ngx_event_openssl.c
> +++ b/src/event/ngx_event_openssl.c
> @@ -1105,7 +1105,8 @@ ngx_ssl_info_callback(const ngx_ssl_conn
> BIO *rbio, *wbio;
> ngx_connection_t *c;
>
> -#ifndef SSL_OP_NO_RENEGOTIATION
> +#if (!defined SSL_OP_NO_RENEGOTIATION \
> + && !defined SSL_OP_NO_CLIENT_RENEGOTIATION)
>
> if ((where & SSL_CB_HANDSHAKE_START)
> && SSL_is_server((ngx_ssl_conn_t *) ssl_conn))
> @@ -1838,9 +1839,10 @@ ngx_ssl_handshake(ngx_connection_t *c)
> c->read->ready = 1;
> c->write->ready = 1;
>
> -#ifndef SSL_OP_NO_RENEGOTIATION
> -#if OPENSSL_VERSION_NUMBER < 0x10100000L
> -#ifdef SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS
> +#if (!defined SSL_OP_NO_RENEGOTIATION \
> + && !defined SSL_OP_NO_CLIENT_RENEGOTIATION \
> + && defined SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS \
> + && OPENSSL_VERSION_NUMBER < 0x10100000L)
>
> /* initial handshake done, disable renegotiation (CVE-2009-3555) */
> if (c->ssl->connection->s3 && SSL_is_server(c->ssl->connection)) {
> @@ -1848,8 +1850,6 @@ ngx_ssl_handshake(ngx_connection_t *c)
> }
>
> #endif
> -#endif
> -#endif
>
> #if (defined BIO_get_ktls_send && !NGX_WIN32)
>
> @@ -2483,7 +2483,8 @@ ngx_ssl_handle_recv(ngx_connection_t *c,
> int sslerr;
> ngx_err_t err;
>
> -#ifndef SSL_OP_NO_RENEGOTIATION
> +#if (!defined SSL_OP_NO_RENEGOTIATION \
> + && !defined SSL_OP_NO_CLIENT_RENEGOTIATION)
>
> if (c->ssl->renegotiation) {
> /*
> # HG changeset patch
> # User Sergey Kandaurov <pluknet at nginx.com>
> # Date 1703253041 -14400
> # Fri Dec 22 17:50:41 2023 +0400
> # Node ID 44266e0651c44f530c4aa66e68c1b9464a9acee7
> # Parent 7fc017b776047b26c7e42b355a1bf142cf968537
> SSL: reasonable version for LibreSSL adjusted.
>
> OPENSSL_VERSION_NUMBER is now redefined to 0x1010000fL for LibreSSL 3.5.0
> and above. Building with older LibreSSL versions, such as 2.8.0, may now
> produce warnings (see cab37803ebb3) and may require appropriate compiler
> options to suppress them.
>
> Notably, this allows to start using SSL_get0_verified_chain() appeared
> in OpenSSL 1.1.0 and LibreSSL 3.5.0, without additional macro tests.
>
> Prodded by Ilya Shipitsin.
>
> diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h
> --- a/src/event/ngx_event_openssl.h
> +++ b/src/event/ngx_event_openssl.h
> @@ -45,7 +45,7 @@
>
> #if (defined LIBRESSL_VERSION_NUMBER && OPENSSL_VERSION_NUMBER == 0x20000000L)
> #undef OPENSSL_VERSION_NUMBER
> -#if (LIBRESSL_VERSION_NUMBER >= 0x2080000fL)
> +#if (LIBRESSL_VERSION_NUMBER >= 0x3050000fL)
> #define OPENSSL_VERSION_NUMBER 0x1010000fL
> #else
> #define OPENSSL_VERSION_NUMBER 0x1000107fL
> diff --git a/src/event/ngx_event_openssl_stapling.c b/src/event/ngx_event_openssl_stapling.c
> --- a/src/event/ngx_event_openssl_stapling.c
> +++ b/src/event/ngx_event_openssl_stapling.c
> @@ -893,7 +893,7 @@ ngx_ssl_ocsp_validate(ngx_connection_t *
> ocsp->cert_status = V_OCSP_CERTSTATUS_GOOD;
> ocsp->conf = ocf;
>
> -#if (OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined LIBRESSL_VERSION_NUMBER)
> +#if OPENSSL_VERSION_NUMBER >= 0x10100000L
>
> ocsp->certs = SSL_get0_verified_chain(c->ssl->connection);
>
Looks good.
--
Maxim Dounin
http://mdounin.ru/
More information about the nginx-devel
mailing list