
TLS/SSL implementation in Haskell - dyoder
https://github.com/vincenthz/hs-tls
======
AaronFriel
A number of people are suggesting that this Haskell implementation must be
worse than OpenSSL. It probably is. Writing good crypto code is hard. There
are probably bugs.

Many are saying that one problem with Haskell is that you can't eliminate
side-channel attacks due to features of the language. I disagree. There is no
common language better than Haskell at encoding invariants in the type system.
One could, for example, implement a "biggishnum" library in Haskell using
large but fixed size integers and constant-time operations.

Free monads are a powerful idea in Haskell[1]. They allow one to easily
generalize "interpreters" over sequences of commands. In Haskell, more-so than
any other language I've ever used, one can decouple execution from algorithm
specification.

Free applicative functors generalize further[2]. They define a computational
structure that must be fixed a priori. That is, by definition a free
applicative functor cannot know the state of the data during its execution.

There are some problems with this. Applicative functors have an operation
which can lift regular functions into it. That operation would have to be
hidden, so that only a kernel was exposed that offered the ability to
initialize data, and then perform computations upon it.

But it's possible to do this. It is actually not a radical idea to imagine
this being done in Haskell. Making a library and a set of primitive operations
_that can be used by an end user safely_ , in provably constant time is
possible.

[1] [http://www.haskellforall.com/2012/06/you-could-have-
invented...](http://www.haskellforall.com/2012/06/you-could-have-invented-
free-monads.html) [2]
[http://paolocapriotti.com/assets/applicative.pdf](http://paolocapriotti.com/assets/applicative.pdf)

~~~
cynicalkane
I'm wondering about a situation where forgetting a strictness annotation is
the analogous Haskell situation of forgetting to bounds-check some input.
There are lots of ways in Haskell for something not to be evaluated. (No, the
simple fixes like 'use strict fields' do not cover all cases.) To put _all_ of
them behind a free monad seems like it would be giving up a lot.

In C you could have made some 'safe buffer' struct together with some safe set
of functions. But people didn't do that.

~~~
AnthonyMouse
> In C you could have made some 'safe buffer' struct together with some safe
> set of functions. But people didn't do that.

Exactly.

    
    
      struct buffer
      {
    	void* data;
    	size_t len;
      };
    
      void smemcpy(buffer* dst, buffer* src, size_t n)
      {
    	if(dst->len < n || src->len < n)
    		abort();
    	memcpy(dst->data, src->data, n);
      }
    

I mean how hard is that, honestly?

Buffer bugs in C don't come from the language, they come from the tendency of
C programmers to value performance highly. "I just checked that the buffer is
big enough, I don't need memcpy to waste cycles checking it again. I don't
need that length variable sucking up memory when I know how big the buffer
is." Except when you do.

~~~
sseveran
They come from the C language allowing it.

~~~
AnthonyMouse
All Turing complete languages allow you to do anything all Turing complete
languages allow you to do. You can allocate a huge block of memory in Java or
C# and then write your own malloc to allocate buffers from subsets of it. In
some cases that may have better performance. It also allows you to have buffer
overruns. You can run right off the end of one buffer and into the next and
the language won't stop you because it only knows that you haven't reached the
end of the array _it_ allocated.

~~~
lomnakkus
Do Turing machines have a concept of "undefined behavior" in the same way that
C does? AFAICT all behavior of a Turing machine is defined -- sure you might
not calculate what you want, but that doesn't admit arbitrary behvaior
_outside of the semantics of the machine_ as in the C language.

~~~
AnthonyMouse
> Do Turing machines have a concept of "undefined behavior" in the same way
> that C does?

Sure they do. For example, nothing in the Turing machine specification tells
you how fast they are. Whether a given program will run on a Turing machine in
a given amount of wall clock time is undefined.

Here's a real example with real machines. Suppose you have a program with two
threads. The first thread computes data and puts it on a queue for the second
thread, the second thread writes it to the filesystem. When the compute thread
has computed all of the data, it sleeps for 10 seconds and then terminates the
program without checking whether the write thread has actually completed
writing all the data to the filesystem.

That results in undefined behavior in every real language I've ever heard of.
Whether the data is all written depends on how fast the CPU is, how fast the
filesystem is, what other programs are scheduled on the machine that compete
for CPU and filesystem access at what priority, etc. Languages don't define
any of those things so you have undefined behavior.

~~~
dllthomas
That's a different abstraction than "Turing Machine". Turing machines do not
have '"undefined behavior" in the same way that C does' \- it would look like
a "in state 3, if you read a 0, move to an arbitrary state, write arbitrary
symbols to arbitrary locations on the tape, move head to an arbitrary position
on the tape, and possibly restructure the state machine in arbitrary ways."

~~~
AnthonyMouse
Turing machines have undefined behavior in exactly the same way that C does.
What do you suppose happens if a Turing machine reads from a location on the
tape that has never been written? The value the machine observes in that
circumstance is not required to be well-defined in order for the machine to be
able to do all computations required of a Turing machine. Moreover, the more
you add features of actual machines like parallelism and timers, the more
opportunities for undefined behavior you introduce. What do you suppose a
multiprocessor Turing machine does if you write concurrently to the tape
without locking?

~~~
lomnakkus
"What do you suppose happens if a Turing machine reads from a location on the
tape that has never been written?"

It reads the default symbol (see e.g. the description of a TM on Wikipedia).
It's semantics are well-defined.

The semantics of TMs are well-defined. The semantics of C's "undefined
behavior" is not well-defined.

~~~
lomnakkus
I should have said "blank symbol" not "default symbol".

------
chimeracoder
While Haskell mitigates or eliminates some classes of bugs common in C (such
as buffer overflow), it also makes it more difficult to guard against side-
channel attacks like timing attacks[0], because lazy evaluation makes it more
difficult to reason about the actual behavior of the code at runtime.

This isn't a dig at either Haskell or C; the point is that all programming
languages and environments have their "gotcha!" moments.

[0]
[https://en.wikipedia.org/wiki/Timing_attack](https://en.wikipedia.org/wiki/Timing_attack)

~~~
joeyh
[http://hackage.haskell.org/package/securemem
implements](http://hackage.haskell.org/package/securemem implements) constant-
time bytestring comparisons for haskell. With the bonus that the data is
scrubbed when GC'd. This is used by parts of the haskell TLS stack, for
example [http://hackage.haskell.org/package/cipher-
aes](http://hackage.haskell.org/package/cipher-aes)

I don't think that laziness makes things much harder. Compare a C function
with if foo return 0 in the middle and a Haskell function that lazily builds
thunks that don't end up being evaluated in a particular branch. There's
obvious things to do to both to make them run in constant time. The hard part
about dealing with a timing attack in both cases is recognising that the
function is used in such a context and needs to be constant time.

AFAIK, the haskell TLS stack has not yet been audited. For this reason,
there's some reluctance to use it in parts of the Haskell community. Although
http-conduit and http-client-tls do use it, so everyone writing programs that
hit https from haskell using conduit is using it already.

~~~
runeks
>
> [http://hackage.haskell.org/package/securemem](http://hackage.haskell.org/package/securemem)
> implements constant-time bytestring comparisons for haskell. With the bonus
> that the data is scrubbed when GC'd.

As far as I understand, this is not sufficient.

Some things that are needed in addition are (at least):

* constant time math operations (multiply, divide, exponentiation, etc.)

* no branching based on secrets (running the code results in no branch conditions based on secret data)

Here's an interesting article on implementing a form of Elliptic Curve
Cryptography without even local timing attacks:
[http://blog.cr.yp.to/20140323-ecdsa.html](http://blog.cr.yp.to/20140323-ecdsa.html)

~~~
dpatru
Why can't timing be handled external to the scrambling or descrambling
operations? Look at the time, do the crypto, then check the time and sleep as
necessary so that this particular operation takes about as much time as the
worse-case. Something like:

    
    
      cryptofunc x = do
        begintime <- gettime
        result <- purecryptofunc x
        endtime <- gettime
        sleep (worstcase - (endtime - begintime))
        return result

~~~
mantasm
Properly implemented crypto takes a constant number of instructions to
encrypt/decrypt, regardless of key, plaintext, etc.

Time to sleep() would vary across processors, os-es, and even load (!), so
it's not exactly a workable solution. Not to mention it would be grossly
inefficient (Context switch on every crypto primitive? No thanks).

------
johnbender
Possibly more interesting is a machine checked implementation.

[http://www.mitls.org/wsgi](http://www.mitls.org/wsgi)

~~~
jude-
From the website, it seems that they proved that their implementation is
correct with respect to their formalization of the interfaces in the RFCs.
That is, their implementation is _logically correct_.

However, this says nothing about whether or not the implementation is
_secure_. They admit that they don't model time in their proofs, so I doubt
their implementation is free of timing attacks. Moreover, its written in F#,
so you have to trust your CLI implementation to be bug-free as well.

~~~
ketralnis
> its written in F#, so you have to trust your CLI implementation to be bug-
> free as well

Is that any different to an implementation in C relying on the processor being
bug-free?

~~~
jude-
If you count up all of the possible states a processor can be in, as well as
the number of transitions between them, you'll come up with a very big number
of possible execution paths you'll need to verify work correctly. However, if
you do the same for the CLI (or any non-trivial piece of software), you'll
come up with a much, much, _MUCH_ bigger number. As in, each additional state
the CLI can enter potentially _doubles_ the number of possible execution paths
you'll need to check.

The number of states and transitions for a processor today is large, but not
so large that engineers can't formally and automatically verify that the
processor will behave correctly under all inputs. Also, the structure of the
processor and the way it is specified (i.e. Verilog) make it amenable to
formal verification.

This is not true for most software, not even things written in Haskell. You
can cover a lot of cases with automated software testing, but you'll find that
it's very, very, VERY hard to prove that you've covered every possible case.
Even if you can, modeling multiple instances of the system as they evolve in
time (i.e. any networked system or interactive system) means you have to
consider all possible _combinations_ of states they can be in.

To put into perspective how _hard_ formal verification of software is, I have
a story. A friend of mine did his masters thesis on modifying TCP to allow for
host mobility, and formally proving the correctness of his new TCP protocol.
Despite having a 100-node cluster of beefy (48GB RAM) compute nodes at his
disposal, it simply didn't have enough total RAM to verify the correctness of
his protocol beyond five rounds of communication between one client and one
server.

Unless the CLI developers add machine-checked but hand-crafted proofs of
correctness for each and every method, I trust the processor to be bug-free
_far more_ than any piece of software it runs. Now of course, if the NSA
tampers with either, then all bets are off :P

------
runeks
This seems interesting.

I'm completely ignorant about Haskell. I see there's some code in a
"Benchmarks" folder; I think it would be highly interesting to see a
comparison in speed between OpenSSL's SSL implementation and this one (the
operations that a web server would normally have to do).

Can anyone make that happen? I can't even figure out how to execute Haskell
code in Ubuntu 13.04.

Seems to me like if the code base is 20 times smaller than OpenSSL, _and_ we
can assess whether timing attacks are present or not -- and if they are,
replace the timing critical code with C code, perhaps -- that this would be a
real alternative to OpenSSL. Am I being unrealistic in thinking this? Not that
everyone will adopt it, mind you, but that adopting it would be a wise thing
to do?

~~~
kazagistar

        sudo apt-get install haskell-platform
    

You can install development packages through the "cabal" command line tool.
The REPL environment is "ghci".

On one hand, it is a very high performance language with tons of purity,
abstraction, and invariants built in to the type system and semantics. On the
other hand, certain aspects can be problematic to reason about.

~~~
runeks

        E: Unable to locate package haskell-platform
    

The package seems unavailable for Ubuntu 13.04. I think it's time to install
14.04 for me.

~~~
prakashk
[http://www.ubuntuask.com/q/answers-haskell-platform-
on-13-04...](http://www.ubuntuask.com/q/answers-haskell-platform-
on-13-04-298601.html)

------
jcurbo
If you're curious (like I was) if anything else in the Haskell ecosystem is
using this, this page lists packages that have dependencies on tls in Hackage
(the Haskell package repository). There are 26 packages that depend on tls.

[http://packdeps.haskellers.com/reverse/tls](http://packdeps.haskellers.com/reverse/tls)

Meanwhile, HsOpenSSL (Haskell bindings for OpenSSL) has 22 dependencies:

[http://packdeps.haskellers.com/reverse/HsOpenSSL](http://packdeps.haskellers.com/reverse/HsOpenSSL)

------
krick
Nice and everything, but I somehow cannot imagine people massively jump over
it. Maybe it's superstitious, I dunno…

On the other hand, I undoubtedly agree that we should start making and
deploying alternatives in more safe modern languages. In fact, I guess we
should start step-by-step rewriting _everything_ that's written in C/C++ and
OpenSSL is a good thing to start with.

I guess it's a good chance for Rust & friends.

~~~
gizmo686
Crypto poses a unique challenge with the threat of timing attacks. A major
benitif of using low level languages is that it is easier to assure that your
code takes constant time regardless of the input.

~~~
pmahoney
So what candidates are there? While C may make it possible to write code that
avoids timing attacks, a quick glance at [1] shows one must avoid memcmp and
array lookups based on secret data, neither of which would be prevented by the
compiler, and both probably similarly easy mistakes as heartbleed.

Can Rust compile to a shared lib the programs can use as easily as a C
library? (I think the answer is "not yet", but I'm not sure? At least, I've
seen projects for compiling Rust without any runtime library).

Today I learned this is possible with Ada [2], but it seems there is some
boilerplate to start/stop the runtime. Does Ada provide the necessary low-
level control to implement crypto?

Are there sufficiently strict dialects of C?

NaCl [1] says "There are an increasing number of cases where the C
implementations and assembly-language implementations are automatically
generated from code that was actually written in another language, such as CAO
or qhasm."

CAO is a "a domain specific language for describing cryptographic software"
[3], while qhasm [4] is a portable assembly seemingly not focused on safety.

[1] [http://nacl.cr.yp.to/internals.html](http://nacl.cr.yp.to/internals.html)

[2]
[http://gcc.gnu.org/onlinedocs/gcc-3.4.3/gnat_ugn_unw/Creatin...](http://gcc.gnu.org/onlinedocs/gcc-3.4.3/gnat_ugn_unw/Creating-
an-Ada-Library-to-be-Used-in-a-Non_002dAda-Context.html#Creating-an-Ada-
Library-to-be-Used-in-a-Non_002dAda-Context)

[3] [PDF]
[http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.113...](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.113.8971&rep=rep1&type=pdf)

[4] [http://cr.yp.to/qhasm.html](http://cr.yp.to/qhasm.html)

~~~
ehsanu1
_Can Rust compile to a shared lib the programs can use as easily as a C
library?_

Yes it can! You use the `#[no_std]` if you wish to be rid of the runtime, and
the `#[crate_type = "lib"]` for outputting shared libraries (or use the
`--crate-type` compiler flag).

Here's an older blog post on it, though the rust code there probably won't
compile as-is with current Rust:
[http://bluishcoder.co.nz/2013/08/08/linking_and_calling_rust...](http://bluishcoder.co.nz/2013/08/08/linking_and_calling_rust_functions_from_c.html)

~~~
jnbiche
Unfortunately, this means giving up the powerful Rust standard library and
threads. Fortunately, third-party devs are creating a "zero-runtime" standard
library that can be used in situations like this, and in embedded programming:

[https://github.com/thestinger/rust-core](https://github.com/thestinger/rust-
core)

Right now, rust-core only provides basic collections and io, but more may be
in the works. I suspect rust-core will eventually grow into a small but
capable standard library.

~~~
kibwen
rust-core has been coasting along in maintenance mode for quite a while now,
because it was only intended as a temporary stopgap. Nobody really wants to
fragment the community with a third-party standard library.

Instead, the Rust devs intend to restructure the standard library such that it
has multiple profiles, and that you can jettison parts of it (such as the
parts that require the runtime) without losing all of the other goodies.
Here's the most recent RFC for doing so, filed yesterday:
[https://github.com/rust-lang/rfcs/pull/40/files](https://github.com/rust-
lang/rfcs/pull/40/files)

~~~
jnbiche
Oh, very cool. I was hoping something like this would emerge.

------
kylemaxwell
The response to a subtle weakness in cryptographic software should not be to
reimplement the cryptographic implementation from scratch. This inevitably
introduces far more problems than it solves.

~~~
amalcon
Haskell is a particularly odd choice for a project like this, as lazy
evaluation makes it difficult to reason about the runtime of various
operations under various conditions.

Something with a bit more ML in its blood would be better, and for a library
that pretty much means OCaml.

Edit: I do not at all mean to imply that it is impossible to implement TLS
securely in Haskell. Only that there are more natural choices if one wants the
advantage of a strong algebraic type system.

~~~
quasque
Could you elaborate on how lazy evaluation is problematic for such a project?

~~~
amalcon
In cryptography, for most operations, you need to be sure that the operation
takes the same amount of time for all possible inputs. Otherwise, you leak
information.

The classic example of this is checking string equality with strncmp(): this
takes a different amount of time depending on how similar the strings are. If
one string is secret and the other is controlled by an attacker, the attacker
can use a clock and multiple attempts to discover the secret.

Obviously this particular one isn't relevant to SSL, but there are a number of
other possibilities to worry about in most languages, most obviously short-
circuiting operations like boolean AND/OR. Lazy evaluation makes every
operation short-circuit, so you need to worry about this in every operation.

It can be done, but it's harder than it needs to be.

~~~
automatthew
So we should choose the approach that gives us trivial attacks that reveal 64K
straight out of Compton to the approach that may be slightly harder to defend
against timing attacks?

~~~
amalcon
This is such an obvious false dichotomy that I'm sure most people will notice,
but I'm pointing it out anyway.

We could use something that gives both advantages, like the OCaml I already
mentioned. Or, we could take a hybrid approach, where something like Haskell
generates C code that provably can't have buffer problems. Or, we could
statically verify that the library is written in a known-memory-safe subset of
C++. Or, we could use a language like Rust, which (once it's eventually
complete) seems ideal for this sort of application.

~~~
dllthomas
I think the "define a strict, branchless DSL" approach is the right one, if
you're going with Haskell. Then use the type system to ensure that only that
stuff can touch key data. No problems with laziness or timing attacks, if the
core of that is implemented correctly.

------
aalpbalkan
TLS implementation in Go.
[http://golang.org/pkg/crypto/tls/](http://golang.org/pkg/crypto/tls/)

Go is probably better at this.

~~~
somethingnew
According to Adam Langley, a Go contributor, it can still be side-channeled.
[https://twitter.com/agl__/status/453370970552532992](https://twitter.com/agl__/status/453370970552532992)

~~~
tptacek
s/Go contributor/primary author of the Golang TLS package/g

(Also, he's one of Google's point people on TLS.)

JFWIW.

------
msie
How bad are timing/side-channel attacks, really? I think that half of the
people who talk about this are showing off. Some nerdy one-uppance.

~~~
wyager
>How bad are timing/side-channel attacks, really?

Bad. They are _not_ something to blow off. They may seem "out there", but they
are actually leveraged somewhat frequently. They are particularly dangerous in
certain shared-hardware environments (e.g. VPS services like DigitalOceal,
AWS, etc), and against physical devices that are supposed to resist data
extraction (like smart cards).

------
developer786
Totally off topic, but programmers, I REALLY need your help...
[https://news.ycombinator.com/item?id=7559067](https://news.ycombinator.com/item?id=7559067)

------
pekk
and you can still write bugs in Haskell.

~~~
chrisdone
I'll notify the Haskell people straight away, sir.

------
jacobwcarlson
With all due respect, I don't know that TLS/SSL implementation problems will
be largely solved by changing programming languages.

~~~
nine_k
If changing languages lets you reduce code size 20-fold, it's very much worth
it.

 _Reasoning_ about a smaller code base is nonlinearly easier. This is
especially important for security software where letting your reasoning slip
usually renders the whole thing useless.

~~~
chopin
Is that really the case? In Java (my native language, so to say) I quite
regularly browse through the language library implementation. I found it quite
easy to read and comprehend. I almost always learn something new by doing so.

Not so much for Haskell. When I started with it, I similarly tried to learn by
going through the language libraries. Even when I gained some more experience
(I am still a beginner and only casual user, though) I have a very hard time
to understand what's going on. The code is so dense that I find it hard to
understand.

~~~
nbouscal
You have an easy time understanding the libraries of your native language, but
a hard time understanding the libraries of a language in a completely
different paradigm that you're a beginner at? You don't say.

Seriously, the Haskell libraries are understandable and informative - once you
learn the language.

~~~
chopin
That's what I exactly tried to convey: I used that tactic when I was a
beginner at Java and hadn't many problems with it. I used the same tactic when
Generics came up. I hadn't any problems with it. The beginner can become a
better programmer by reading the code. Now I am in a state where I can say:
"this shouldn't be implemented that way". Which is completely different and
grounds in being Java my native language.

I have casual encounters with Haskell since two years now. Library code is
still hard to grok. Don't get me wrong: I like Haskell very much and I am
pretty sure that its a decent language to write security critical code in, but
I think this is still a problem for many beginners out there. I very rarely
stumble on something and say: "hey, that's cool, I will use that in my code".
But I admit that I give up after the third nested "lift-<whatever>" ;-).

I want the libraries help me understanding the language, not vice versa. But
of course, that's not their primary goal.

~~~
nbouscal
When you were a beginner at Java, did you by chance already know how to
program in an imperative or OO manner? I find it highly unlikely that anyone
unfamiliar with imperative/OO programming in general would get _any_ benefit
from reading Java libraries. If you want to get benefit from reading Haskell
libraries, just like in Java, you need to learn how to program in the
paradigm. Not just "have casual encounters" with it, not just reading things
about it, but actually learn the thing.

A direct analogy to what you're saying: When I was studying French, I was able
to read articles above my level and get a decently good idea of what they
said. Now I'm studying Chinese, and I can't tell at all what characters mean!
_This should not be surprising._ French is very similar to English, where
Chinese is very different. Of course picking things up in the latter will be
more difficult. It would be absolutely absurd to blame the Chinese language
for that.

