RFC: Moving SwiftNIO SSL to BoringSSL

haha, @lukasa is just too fast for me :slight_smile:. Omg, totally forgot the QUIC issue: As @lukasa says to be able to support QUIC and therefore HTTP/3 we will need to do this anyway. Even if OpenSSL at some point supports the right hooks, then we would still need to rely on the Linux distribution where the binary is run to have that version of OpenSSL (which doesn't even exist yet). Given that Ubuntu LTS distributions (which are Swift's primary Linux target) go EOL only after five years, this would mean it would be at least five years from April 2019 (ie. April 2024) until we would be able to reliably support QUIC & HTTP/3...

You can see a similar effect today in Swift: Foundation's URLSession is at the moment built on top of libcurl. libcurl can only do HTTP/2 if it has been linked with nghttp2 and with a new enough version of OpenSSL (that supports ALPN which is necessary for HTTP/2). And that is reason that even today, URLSession on Linux does only sometimes support HTTP/2, depending on what distribution you run it on which is unacceptable. This will repeat (but worse because AFAIK BoringSSL is the only libssl that supports enough QUIC hooks today) with HTTP/3.

4 Likes

Thanks for your really thorough replies, @lukasa!

That's true, but I didn't suggest they should be. Today, if there is a CVE in OpenSSL users can apt-get. In the future they will have to wait until SwiftNIO ships a release including a fixed BoringSSL. That is not an improvement on the current situation.

Do BoringSSL already give prior notification to other non-Google downstream consumers about security fixes? Can @agl confirm they are happy to do this for SwiftNIO? Would SwiftNIO also be privately notifying their downstream consumers?

Agreed that 40% is not small, but overall I do agree with @johannesweiss that in the grand scheme of thing (which atm often include Ubuntu Docker images) this is not a blocker. I would accept the increased binary size for TLS 1.3 etc.

Do you plan for your BoringSSL-based version of swift-nio-ssl to contain the whole of BoringSSL including the bits of crypto that aren't used by swift-nio-ssl, or do you plan to strip out BoringSSL code that you aren't using?

Sorry for all the questions!

Yeah, this is a limitation of this approach: any CVE that affects both OpenSSL and BoringSSL will have an extra link in the chain towards shipping a release. Unfortunately, the space of options here is a bit tricky.

  1. Use only OpenSSL. The status quo, has the limitations discussed in the original document.
  2. Use BoringSSL, vendored. Accept the extra link in the chain and the associated risks.
  3. Use a Greenfield implementation. Aside from the time to build such a thing, which is not the point of the argument, the less widely-used the TLS implementation the more likely that there are undiscovered CVE-worthy bugs lurking.

Ultimately, here, there are no good choices, just a world of tradeoffs. Hence this discussion thread: we're interested in the kinds of tradeoffs the community would like to be making.

We plan to ship the entirety of BoringSSL. The goal here is to minimise the amount of customisation we do of the build, because the more we change the further we diverge from the mainline use of the project, and the further we diverge the more likely it is we introduce some kind of crippling bug.

No need to apologise! This proposal is very much not a fait accompli: if the community consensus is that we should not make this move unilaterally, we will not. As noted, at the very least for QUIC protocol development purposes we will end up doing something involving the use of BoringSSL, but we are open to discussing a wide range of alternatives. So questions are good: they help everyone get a feel for the shape of the problem, and what the trade offs are.

1 Like

Do BoringSSL already give prior notification to other non-Google downstream consumers about security fixes? Can @agl confirm they are happy to do this for SwiftNIO? Would SwiftNIO also be privately notifying their downstream consumers?

We have arranged a notification process for this. We cannot pre-commit to allowing all disclosures to be shared with arbitrary downstream consumers, rather these things are handled on a case-by-case basis. But we've never had a such a sensitive disclosure for BoringSSLā€”the vast majority of our security updates just note that we're unaffected by issues disclosed by OpenSSL.

Do you plan for your BoringSSL-based version of swift-nio-ssl to contain the whole of BoringSSL including the bits of crypto that aren't used by swift-nio-ssl, or do you plan to strip out BoringSSL code that you aren't using?

Using a linker script to control the dynamic symbol table plus the correct flags for function-sections, should allow the linker to discard all statically-unreachable code. Also, compiling with OPENSSL_SMALL (docs) will trim things a little more, at the cost of some performance.

2 Likes

Overall, +1 to this proposal. My one concern would be increased build time. @lukasa I saw you profiled executable size--did you also take note of any differences in build times? Maybe I missed that.

This is a major problem indeed.

I agree. This proposal doesn't prevent us from moving toward the goal of having a good, shared Crypto library for Swift on Server.

1 Like

I did not take note of build times, but I can get you some numbers and let you know.

1 Like

IMO this is really something SPM needs to solve (better caching of build fragments, e.g. it is not a big deal w/ swift-xcode which maintains a cache of the dependencies) and shouldn't really affect the decision here.

I'm wondering whether there is a linker level solution to this (not dylib, but ld/gold, when assembling the "libNIO.a"). Since NIO doesn't want to expose any of the SSL_ stuff, there is no real reason to keep the symbols in the "libNIO.a"? Or maybe rewrite them at the object level. :thinking:

It is a tough decision, I don't really have a good opinion on that either. All options seems to be bad in some way :slight_smile: I think my opinions are:

a) It does feel really wrong to me to not use the system library for that. Sorry, but I trust the server OS vendor I pay for dealing with those kinds of things waaay more than the server side project I happen to use :-)

b) If you do vendor BoringSSL it feels really wrong not to make it a top-level Swift package which can be consumed by everyone for other crypto functionality as well. Anyone who wants to do crypto is going to have all the exact same issues discussed here? Is everyone going to embed BoringSSL for the same reasons? :-) (Whether raw, or w/ a higher level Swift wrapper).

c) I can see the temptation wrt the QUIC support. NIO could be one of the few frameworks with a quality implementation right from the start! Lots of opportunities in that. On the other side, this is like b) on an even broader level. If the system doesn't ship w/ QUIC capable libraries, how is it supposed to become mainstream? Or is everyone going to do their own BoringSSL prefix-rename? That kind of future also sounds very wrong again :-)

If I would have to decide, I would probably opt going with the system libraries and rather stick to them. Maybe provide a special branch for bleeding edge stuff and keep it there for people who want to bleed, and otherwise wait for the system libraries to catch up. If it is a worthy standard, they have to and then everyone can benefit.

1 Like

The nature of a .a file as a simple collection of object files is such that we cannot remove these symbols. The only way to remove them would be to inline all of the code, which cannot be done without leading to a truly unacceptable binary size outcome.

This would require the ability to call out to arbitrary build commands at build time, a feature that the SwiftPM team are (understandably) reluctant to add.

Sure, I understand that.

My objections to doing this are present elsewhere in the thread, I defer to them, but again: I believe that if we refuse to solve a subset of the problem unless we can solve the whole problem, we shall all stay down at the bottom of this well together.

Yes, we should come to an ecosystem wide crypto solution, but thus far I donā€™t think anyone has seriously proposed what this should be or how it gets built. I personally donā€™t have the bandwidth to build and maintain it, I donā€™t think the SwiftNIO team as a whole does. So who?

The current state of affairs is that the QUIC implementers that exist today fall into the following categories:

  • implementers with their own mature TLS implementations that they control. Google and Mozilla fall into this camp, for example.
  • implementers with custom TLS libraries that solve this problem as a concrete specific item. Most of the CDNs do this, e.g. with Kazuho Okuā€™s picotls.
  • implementers that are vendoring BoringSSL or a hacked up OpenSSL.

Naturally, on the long time scale these features will ship in regular OpenSSL versions shipped with LTS distributions. But those timescales are long. TLS1.3 has shipped, and is used for a huge swathe of internet traffic today, yet is completely unavailable to anyone deploying on Ubuntu 18.04. That means that there will be applications in place in 2023 that still have no TLS1.3 implementation. Given that TLS 1.0 and 1.1 are being deprecated shortly, that leaves those implementations in a very lonely space in the protocol support tensor field.

This is the most likely compromise position. I am uncomfortable with it, because of the maintenance burden it raises. The APIs of these two stacks will drift, or substantial maintainer effort will need to be expended to keep them in check.

I would pose this another way: if the NIO team were to drop OpenSSL, would anyone fork the current implementation of swift-nio-ssl and maintain it? I think thatā€™s the real litmus test of whether this is actually important to the community: is anyone outside the NIO team interested in expending their own time and effort to keep supporting both?

1 Like

I highly doubt that this would happen, but it certainly wouldn't help the effort (i.e. I'd rather see people opting for completely different solutions then. Who uses an environment which can't even provide basic crypto functionality?).

(BTW I think this is a general problem w/ Swift, but on the server side you don't have the same platform lock-in to use the language.)

P.S.: Don't get me wrong, you do a great job on NIO with a very small team :+1: I understand that you absolutely don't need more work, but that doesn't solve the issue :slight_smile:

This isn't really an option, is it? Even the current version of swift-nio-ssl is using BoringSSL on macOS, just via brew.

It is:

If you target iOS/tv 12+ or macOS 10.14+ we recommend using swift-nio-transport-services which is SwiftNIO on top of Network.framework which supports TLS out of the box.

That's a different package, though. This post is about what swift-nio-ssl is supposed to do instead of what it is doing today.

It is simply an answer to your question. There is no reason to deploy swift-nio-ssl on macOS except for legacy platforms. Just in about half a year they will be v+2. If it is just for development purposes, brew is fine and the security concerns do not apply.
On the platforms where swift-nio-ssl should be used, a maintained system SSL library exists.

For the sake of posterity I should note that swift-nio-ssl doesnā€™t use BoringSSL on macOS at this time. It would be out of step with the desire to have a similar environment in dev and prod.

1 Like

Thanks. Just so I'm completely understanding the proposal :slight_smile:, although you will ship the entirety of BoringSSL you will only be using the bits of it needed to implement the public API of swift-nio-ssl, and BoringSSL itself will be inaccessible to users of swift-nio-ssl. In other words, people who depend on swift-nio-ssl in their Package.swift won't be able to import CNIOBoringSSL and call the mangled BoringSSL symbols. Correct?

Do you plan to remove CNIOSHA1 and use the implementation of SHA-1 from BoringSSL instead?

Correct.

No. CNIOSHA1 ships with the core NIO repository as it is required for websockets. We do not plan to make swift-nio-ssl a hard requirement of core NIO, nor do we intend to require that all NIO users download and compile BoringSSL when they are not using TLS.

2 Likes

As @lukasa has now opened a WIP PR for this I measured the build times I see:

swift-nio-ssl master:

real   0m20.852s
user   0m28.280s
sys    0m2.590s

BoringSSL branch:

real   0m39.140s
user   1m41.230s
sys    0m9.430s
1 Like