Binaries

Haskell code compiled will always produce the same binary file, since the compiler is deterministic (*).

(*) GHC is not fully deterministic yet, but if you keep to the following setup: stack + LTS - Data.Unique, you shouldn’t have any problems. For more info, please look into the ongoing work in order to make GHC support fully deterministic builds.

If a secure hash is applied to these binaries, in this case SHA-256, we can create what is called a reproducible build hash. For example:

28066d57da7328899c853a3f6c9ebc1bc7e0fa1a0ce0e9bf05de9c796911aa93

This hexadecimal number, could be denominated as a warranty seal since it certifies that a specific code will always produce the same binary.

As there is a link between code and binaries, this will allow the relevant authorities to testify that the application that is currently being executed comes from the source code and, in addition, to easily perform trustworthy audits to verify that the applications, really do what they were designed to do.

Here is an explanatory video showing how difficult it’s to make changes to a binary in order to try to recreate the initial 256 bit hash:

Distribution

When using Docker technology for the distribution of binaries, as it is a technology that does not give the same importance to determinism when it comes to recreating images or containers, it has been necessary to create algorithms that are capable of producing reproducible build hashes for both of images and containers, and therefore safeguard the guarantees offered by the use of Haskell.

#!/usr/bin/env bash

################################################################################
##
## Uniprocess, (c) 2018 SPISE MISU ApS, opensource.org/licenses/LGPL-3.0
##
################################################################################

clear

img="./tmp/img.tar"

docker save uniprocess > $img

echo "Docker Image Reproducible Build Hash:"

# exclude all opaque files
pat="*/*.wh..wh..opq"
# find all the layer.tar files
for f in $(tar -tf $img --wildcards "*/layer.tar"); do
    # for each of the layer files, SHA256 each the files
    for cf in $(tar -xf $img --to-stdout $f | tar --exclude="$pat" -tf -); do
	n=${cf##*/}
	if [ "" != "$n" ]; then
	    tar -xf $img --to-stdout $f | tar -xf - --to-stdout $cf |
		sha256sum | cut -d " " -f 1
	fi
    done
# Hash the content of each file, sort them and combine to a single hash
done | sort | sha256sum | cut -d " " -f 1

find ./tmp -name "img.tar" -delete

echo
#!/usr/bin/env bash

################################################################################
##
## Uniprocess, (c) 2018 SPISE MISU ApS, opensource.org/licenses/LGPL-3.0
##
################################################################################

clear

uid=$1
con="./tmp/con.tar"

docker export $uid > $con

echo "Docker Container Reproducible Build Hash:"

# exclude (running) docker container mount file
pat="etc/mtab"
# find all the layer.tar files
for cf in $(tar --exclude="$pat" -tf $con); do
    n=${cf##*/}
    if [ "" != "$n" ]; then
	tar -xf $con --to-stdout $cf |
	    sha256sum | cut -d " " -f 1
    fi
# Hash the content of each file, sort them and combine to a single hash
done | sort | sha256sum | cut -d " " -f 1

find ./tmp -name "con.tar" -delete

echo

The reason for the use of Docker, is that it allows to use base containers of a much smaller size if we compare it to a standard operating system. The base container used is fpco/haskell-scratch:integer-gmp of only 2 MB in size, producing container images of about 7.5 - 15 MB.

################################################################################
##
## Uniprocess, (c) 2018 SPISE MISU ApS, opensource.org/licenses/LGPL-3.0
##
################################################################################

FROM haskell:8.2

# Install dependecies needed to compile Haskell libraries
RUN apt-get update && apt-get install --yes \
    xz-utils \
    make

RUN stack --resolver lts-11.11 install base \
    # A time & space-efficient byte arrays (Word8)
    bytestring \
    # Date and time stamps
    time \
    # Linux's kernel random number generator
    random \
    # Berkeley sockets
    network \
    # Native implementation of TLS protocol for Server & Client
    tls \
    # (tls) Default values for TLS parameters
    data-default-class \
    # (tls) Specify hash for validation (SHA256)
    x509 \
    # (tls) Accessing and storing X.509 certificates
    x509-store \
    # (tls) Disable validation for non x509 v3 certificates
    x509-validation \
    # Validate input. Example: A name shouldn't be "42"
    parser-combinators \
    # serializer from and to JSON
    array \
    # serializer from and to JSON
    containers \
    # serializer from and to JSON
    mtl \
    # serializer from and to JSON
    syb

## References
# - Futtetennismo:
#   * Base: https://futtetennismo.me/posts/docker/
#   * File: 2017-11-24-docker-haskell-executables.html
################################################################################
##
## Uniprocess, (c) 2018 SPISE MISU ApS, opensource.org/licenses/LGPL-3.0
##
################################################################################

FROM haskell-base-builder as builder

WORKDIR "../"

# copy the contents of the current directory in the working directory
COPY . .

RUN stack --resolver lts-11.11 install && \
    strip /root/.local/bin/uniprocess

FROM fpco/haskell-scratch:integer-gmp

COPY --from=builder /root/.local/bin/uniprocess /bin/
COPY               ./der/dev/console            /dev/console
COPY               ./der/env/.dockerenv         /.dockerenv
COPY               ./der/etc/hostname           /etc/hostname
COPY               ./der/etc/hosts              /etc/hosts
COPY               ./der/etc/resolv.conf        /etc/resolv.conf
COPY               ./tls/cacert.pem             /tls/cacert.pem
COPY               ./tls/uniprocess.public.crt  /tls/uniprocess.public.crt
COPY               ./tls/uniprocess.secret.key  /tls/uniprocess.secret.key

# We provide the possibility to specify the port and an argument
# Examle: docker run -d -p 8443:8443 uniprocess 8443
# https://stackoverflow.com/a/40312311

# Cos we need root in order to server 80 and 443 in Linux, we use Tomcats 8080
# (HTTP) and 8443 (HTTPS) approach
#
# https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers

ENTRYPOINT [ "/bin/uniprocess" ]
CMD        [       "8443"      ] # Default value but can overriden at runtime

## References
# - Futtetennismo:
#   * Base: https://futtetennismo.me/posts/docker/
#   * File: 2017-11-24-docker-haskell-executables.html

And since the base container only includes Linux components to run Haskell applications, this will minimize the attack surface for hackers.