
SwiftTLS – A TLS implementation in Swift - andreasley
https://github.com/nsc/SwiftTLS
======
pharaohgeek
Better crypto support in platform-agnostic Swift is definitely #1 on my
wishlist. On macOS/iOS you have Apple's libraries to provide cryptographic
support. However, for server-side Swift running on Linux, your options are
pretty much reduced to wrappers around OpenSSL. I'd love to see more formal
cryptographic and security libraries for Swift. While Java's JCE definitely
has its flaws from a design perspective, something along those lines for Swift
would greatly enhance its viability for enterprise software, or other
applications where security is of paramount concern.

------
wahern
Now refactor everything to support async/await to support asynchronous use in
addition to synchronous use. And then you'll have the answer as to why
async/await and stackless coroutines are a dead-end.

~~~
eridius
Did you comment on the right story?

~~~
wahern
TLS stacks are dead out of the water if they don't support non-blocking I/O.

The point of my post wasn't to criticize SwiftTLS, per se, but rather to make
a larger point that supporting asynchronous I/O will never become
substantially easier unless and until languages provide the correct
primitives. Case in point: try to refactor SwiftTLS (which otherwise may be a
laudable TLS implementation) to support Swift's async/await proposal. All the
headaches and compromises will say much more about the viability of
async/await than about the implementation quality of SwiftTLS. Nonetheless,
it's the end product (SwiftTLS, etc) that will be bear the blame for the added
complexity (or lack thereof if they abstain from supporting the model).

~~~
bsaul
your comment is really interesting to me, but i’m not familiar enough with tls
to understand what is impossible with adding usage of async/await on this
codebase. Could you give a specific example on the kind of problem it will run
into ? (a function from this project for example)

~~~
wahern
I'm not saying it's impossible. I'm saying that the process will be incredibly
intrusive. To support both synchronous and asynchronous models you often need
to go through contortions to split out as much common code as possible. That's
because you can only call async functions using specialized await invocations.
See [http://journal.stuffwithstuff.com/2015/02/01/what-color-
is-y...](http://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-
function/)

Alternatively, you need to make one or the other a 2nd class citizen (e.g.
write the core implementation using async/await and make the synchronous API a
wrapper), making that aspect of the implementation substandard in the eyes of
your library users.

There are alternative language primitives, like asymmetric stackful
coroutines, that provide much better semantics. You can continue sharing all
your code using the same function composition patterns because there's no
bifurcation in the function space--a coroutine looks and behaves and is
invoked like any other function.

Stackful coroutines aren't memory efficient if you try to maintain
compatibility with traditional C ABIs, especially on 32-bit platforms. To
reliably run C code musl libc, for example, has minimum stack sizes of 80KB.
By contrast, a stackful coroutine in Lua has an initial allocation size
(including stack) of less than 300 bytes, and the stack is dynamically resized
as needed. So stackful coroutines aren't a good fit for Rust which emphasize
seamless backward compatibility with existing C code.

But for a language like Swift I think the designers could have gone the other
way[1], and it's a shame they've committed themselves to a language primitive
that is a dead-end and only as popular as it is because of extrinsic
limitations--Rust's is backward compatibility with C, for JavaScript and
Python it's the fact that the existing engines intertwine the C stack with the
languages logical stack. (For use cases where async/await may be a preferable
paradigm, e.g. code documentation or ability to block suspension of the
caller, async/await can be implemented cleanly and cheaply atop the lower-
level primitive. Stackful coroutines are a strictly more elegant and capable
primitive.)

People debate these issues endlessly and many people would disagree with me.
But my point is that the real proof of my argument can be seen in the
difficulty of authoring and maintaining real world libraries like SwiftTLS.
Forget abstract arguments--look at the concrete use cases and not anecdotes or
proofs-of-concept. The async/await world brings significant code duplication
and decreased composability (i.e. ability to mix-and-match libraries and
toolkits). It's a hack that is rationalized post hoc.

[1] While Swift needs to support C, C++, and Objective-C interoperability,
Apple's long-term goal is a predominately Swift-based ecosystem. And all of
Apple's hardware is now 64-bit, so theoretically they could even support full
C ABI interoperability without an undo memory hit by sparsely allocating
stacks and extending them in-place on demand. Ultimately, I think part of the
calculus is that it's easier to fiddle with your languages type system than it
is to fix the low-level stack management of a pre-existing compiler (i.e.
LLVM) to support many dynamic stacks. Go could do this because they wrote (or
rewrote) their own compiler toolchain from scratch. Scheme and Lua can do this
because the runtime VM stack is kept distinct from the native C ABI stack used
by the engine; they effectively multiplex many VM stacks atop a single native
C stack. Similar story for Java's Loom project regarding JVM stack management.

~~~
dwaite
> To support both synchronous and asynchronous models you often need to go
> through contortions to split out as much common code as possible.

For TLS, examples of this core would be GSSAPI and the Java SSLEngine.

------
kodablah
I wonder how many times we'll impl tls, http 2, aes, etc or how many times
we'll leverage libs with C ffi. I'm not sure about Swift's CSP support, but
I'm beginning to think translating Go everywhere, except for some of the core
(or other asm-level pieces like constant time byte array actions) that may be
language specific, may be a better way to open up tons of libs for use. It's a
fairly simple and unchanging source language. I know Haxe has these kinds of
goals, but I figure the ecosystem is weaker.

~~~
Spartan-S63
I don't see Go as a panacea for opening up libraries for use. You're still
going to see languages that can export C interfaces for that. It's going to be
C, C++, and Rust that are likely to persist as the languages of choice for
massively consumable libraries. As long as they can export a C interface, you
can get low (or no) overhead interop. Go's biggest disadvantage is that it
ships by default with a runtime.

I hope I'm understanding your comment correctly.

~~~
kodablah
I meant translate the actual Go code to Swift code so it's pure Swift, but the
effort to implement the algorithm right is removed. But yes, to support Go's
features you will need at least a GC. Once Rust gets async/await down and
people run with it, a static lib in Rust w/ an exported C interface might
instead be the norm. But in the meantime, it's annoying that the options are
to either reuse a complicated/unsafe C/C++ impl or re-impl in each language
individually. Go has a terrible C export story with lots of limitations and
impedance mismatches so that's not a great approach imo (granted it's what
gomobile does iirc).

~~~
Spartan-S63
Ah, I understand now. Yes, translating from a Go implementation to a Swift one
would erase most of the complexity of reimplementing the algorithm (in a
potentially error prone way).

I agree that once Rust lands async/await and it matures, it could be the go-to
for safe, cross-platform libraries. That's my hope, too. I'm selfishly a Rust
evangelist.

------
moderation
All of the TLS 1.3 implementations and test servers (including SwiftTLS) are
listed at
[https://github.com/tlswg/tls13-spec/wiki/Implementations#imp...](https://github.com/tlswg/tls13-spec/wiki/Implementations#implementations)

