From fdd472e08c292d21fbafd846577206e4d0e4869b Mon Sep 17 00:00:00 2001 From: Jannik Hoelling Date: Tue, 18 Jan 2022 19:59:20 +0000 Subject: [PATCH 1/2] thesis: evaluation of libraries and servers --- evaluation-libraries/.gitignore | 4 + evaluation-libraries/README.md | 42 + evaluation-libraries/baseimage/Dockerfile | 28 + .../baseimage/Dockerfile-archlinux | 17 + .../baseimage/Dockerfile-debian | 26 + evaluation-libraries/baseimage/build.sh | 6 + .../baseimage/certs/generate-ca.sh | 71 ++ evaluation-libraries/baseimage/client.sh | 57 + evaluation-libraries/bearssl/CMakeLists.txt | 7 + evaluation-libraries/bearssl/Dockerfile | 37 + evaluation-libraries/bearssl/LICENSE | 21 + evaluation-libraries/bearssl/README.md | 13 + evaluation-libraries/bearssl/build.sh | 1 + .../bearssl/client/CMakeLists.txt | 12 + evaluation-libraries/bearssl/client/client.c | 165 +++ evaluation-libraries/bearssl/client/client.h | 183 +++ .../bearssl/docker-compose.yml | 21 + evaluation-libraries/bearssl/run.sh | 2 + .../bearssl/server/CMakeLists.txt | 12 + evaluation-libraries/bearssl/server/server.c | 163 +++ evaluation-libraries/bearssl/server/server.h | 356 ++++++ evaluation-libraries/botan/CMakeLists.txt | 16 + evaluation-libraries/botan/Dockerfile | 24 + evaluation-libraries/botan/LICENSE | 24 + evaluation-libraries/botan/README.md | 13 + evaluation-libraries/botan/build.sh | 1 + .../botan/client/CMakeLists.txt | 9 + evaluation-libraries/botan/client/client.cpp | 334 ++++++ .../botan/cmake/FindBotan2.cmake | 130 +++ evaluation-libraries/botan/docker-compose.yml | 21 + evaluation-libraries/botan/run.sh | 2 + .../botan/server/CMakeLists.txt | 9 + evaluation-libraries/botan/server/server.cpp | 328 ++++++ evaluation-libraries/botan/server/server.h | 84 ++ evaluation-libraries/build-everything.sh | 5 + evaluation-libraries/gnutls/CMakeLists.txt | 16 + evaluation-libraries/gnutls/Dockerfile | 38 + evaluation-libraries/gnutls/LICENSE | 23 + evaluation-libraries/gnutls/build.sh | 1 + .../gnutls/client/CMakeLists.txt | 9 + evaluation-libraries/gnutls/client/client.c | 182 +++ evaluation-libraries/gnutls/client/examples.h | 26 + evaluation-libraries/gnutls/client/tcp.c | 82 ++ .../gnutls/cmake/FindGnuTLS.cmake | 84 ++ .../cmake/FindPackageHandleStandardArgs.cmake | 605 ++++++++++ .../gnutls/cmake/FindPackageMessage.cmake | 48 + .../gnutls/docker-compose.yml | 21 + evaluation-libraries/gnutls/readme.md | 11 + evaluation-libraries/gnutls/run.sh | 3 + .../gnutls/server/CMakeLists.txt | 9 + evaluation-libraries/gnutls/server/server.c | 168 +++ evaluation-libraries/gnutls/server/server.h | 41 + evaluation-libraries/golang/Dockerfile | 14 + evaluation-libraries/golang/LICENSE | 116 ++ evaluation-libraries/golang/README.md | 12 + evaluation-libraries/golang/build.sh | 1 + evaluation-libraries/golang/client/client.go | 93 ++ evaluation-libraries/golang/client/go.mod | 3 + .../golang/docker-compose.yml | 21 + evaluation-libraries/golang/run.sh | 2 + evaluation-libraries/golang/server/go.mod | 3 + evaluation-libraries/golang/server/server.go | 112 ++ evaluation-libraries/java/Dockerfile | 15 + evaluation-libraries/java/LICENSE-java-getopt | 481 ++++++++ evaluation-libraries/java/README.md | 9 + evaluation-libraries/java/build.sh | 1 + evaluation-libraries/java/client/README.md | 1 + .../java/client/lib/java-getopt-1.0.14.jar | Bin 0 -> 194939 bytes evaluation-libraries/java/client/manifest | 3 + .../java/client/src/Client.java | 148 +++ evaluation-libraries/java/docker-compose.yml | 21 + evaluation-libraries/java/run.sh | 2 + evaluation-libraries/java/server/README.md | 1 + .../java/server/lib/java-getopt-1.0.14.jar | Bin 0 -> 194939 bytes evaluation-libraries/java/server/manifest | 3 + .../java/server/src/Server.java | 115 ++ evaluation-libraries/mbedtls/CMakeLists.txt | 5 + evaluation-libraries/mbedtls/Dockerfile | 18 + evaluation-libraries/mbedtls/LICENSE | 202 ++++ evaluation-libraries/mbedtls/README.md | 10 + evaluation-libraries/mbedtls/build.sh | 1 + .../mbedtls/client/CMakeLists.txt | 23 + evaluation-libraries/mbedtls/client/client.c | 208 ++++ .../mbedtls/docker-compose.yml | 21 + evaluation-libraries/mbedtls/run.sh | 2 + .../mbedtls/server/CMakeLists.txt | 23 + evaluation-libraries/mbedtls/server/server.c | 235 ++++ evaluation-libraries/openssl/.gitignore | 19 + evaluation-libraries/openssl/CMakeLists.txt | 5 + .../openssl/Dockerfile-boringssl | 21 + .../openssl/Dockerfile-openssl | 26 + evaluation-libraries/openssl/LICENSE | 124 ++ evaluation-libraries/openssl/README.md | 13 + evaluation-libraries/openssl/build.sh | 2 + .../openssl/client/CMakeLists.txt | 8 + evaluation-libraries/openssl/client/client.c | 241 ++++ evaluation-libraries/openssl/client/client.h | 13 + .../openssl/docker-compose-boringssl.yml | 36 + .../openssl/docker-compose.yml | 36 + evaluation-libraries/openssl/run.sh | 3 + .../openssl/server/CMakeLists.txt | 7 + evaluation-libraries/openssl/server/server.c | 226 ++++ evaluation-libraries/openssl/server/server.h | 19 + evaluation-libraries/run-everything.sh | 48 + evaluation-libraries/rustls/Cargo.toml | 21 + evaluation-libraries/rustls/Dockerfile | 15 + evaluation-libraries/rustls/LICENSE | 201 ++++ evaluation-libraries/rustls/README.md | 12 + evaluation-libraries/rustls/build.sh | 1 + evaluation-libraries/rustls/client/client.rs | 412 +++++++ .../rustls/docker-compose.yml | 21 + evaluation-libraries/rustls/run.sh | 2 + evaluation-libraries/rustls/server/server.rs | 463 ++++++++ evaluation-libraries/wolfssl/CMakeLists.txt | 5 + evaluation-libraries/wolfssl/Dockerfile | 19 + evaluation-libraries/wolfssl/LICENSE | 339 ++++++ evaluation-libraries/wolfssl/README.md | 12 + evaluation-libraries/wolfssl/build.sh | 1 + .../wolfssl/client/CMakeLists.txt | 11 + evaluation-libraries/wolfssl/client/client.c | 182 +++ evaluation-libraries/wolfssl/client/client.h | 36 + .../wolfssl/docker-compose.yml | 21 + evaluation-libraries/wolfssl/run.sh | 2 + .../wolfssl/server/CMakeLists.txt | 11 + evaluation-libraries/wolfssl/server/server.c | 199 ++++ evaluation-libraries/wolfssl/server/server.h | 24 + evaluation-servers/.gitignore | 4 + evaluation-servers/README.md | 35 + evaluation-servers/apache/Dockerfile | 5 + evaluation-servers/apache/apache.conf | 45 + evaluation-servers/apache/build.sh | 1 + evaluation-servers/apache/docker-compose.yml | 13 + evaluation-servers/apache/run.sh | 2 + evaluation-servers/build.sh | 4 + evaluation-servers/courier/Dockerfile | 40 + evaluation-servers/courier/build.sh | 2 + evaluation-servers/courier/docker-compose.yml | 13 + evaluation-servers/courier/imapd-ssl | 331 ++++++ evaluation-servers/courier/run.sh | 2 + evaluation-servers/courier/smtpd.conf | 26 + evaluation-servers/courier/start.sh | 9 + evaluation-servers/cyrus/Dockerfile | 40 + evaluation-servers/cyrus/build.sh | 2 + evaluation-servers/cyrus/cyrus.asc | 17 + evaluation-servers/cyrus/cyrus.conf | 55 + evaluation-servers/cyrus/docker-compose.yml | 13 + evaluation-servers/cyrus/imapd.conf | 130 +++ evaluation-servers/cyrus/run.sh | 2 + evaluation-servers/dovecot/Dockerfile | 6 + evaluation-servers/dovecot/build.sh | 1 + evaluation-servers/dovecot/docker-compose.yml | 13 + evaluation-servers/dovecot/run.sh | 2 + evaluation-servers/exim/Dockerfile | 11 + evaluation-servers/exim/build.sh | 1 + evaluation-servers/exim/docker-compose.yml | 14 + evaluation-servers/exim/exim.conf | 1034 +++++++++++++++++ evaluation-servers/exim/run.sh | 2 + .../filezilla-server/Dockerfile | 27 + evaluation-servers/filezilla-server/build.sh | 1 + .../filezilla-server/docker-compose.yml | 13 + evaluation-servers/filezilla-server/run.sh | 2 + evaluation-servers/lighttpd/Dockerfile | 20 + .../lighttpd/Dockerfile-mbedtls | 20 + evaluation-servers/lighttpd/build.sh | 2 + .../lighttpd/docker-compose.yml | 20 + .../lighttpd/lighttpd-mbedtls.conf | 53 + evaluation-servers/lighttpd/lighttpd.conf | 51 + evaluation-servers/lighttpd/run.sh | 2 + evaluation-servers/nginx/Dockerfile | 16 + evaluation-servers/nginx/build.sh | 1 + evaluation-servers/nginx/docker-compose.yml | 13 + evaluation-servers/nginx/nginx.conf | 20 + evaluation-servers/nginx/run.sh | 2 + evaluation-servers/opensmtpd/Dockerfile | 6 + evaluation-servers/opensmtpd/build.sh | 1 + .../opensmtpd/docker-compose.yml | 13 + evaluation-servers/opensmtpd/run.sh | 2 + evaluation-servers/opensmtpd/smtpd.conf | 26 + evaluation-servers/postfix/Dockerfile | 6 + evaluation-servers/postfix/build.sh | 1 + evaluation-servers/postfix/docker-compose.yml | 13 + evaluation-servers/postfix/main.cf | 686 +++++++++++ evaluation-servers/postfix/master.cf | 139 +++ evaluation-servers/postfix/run.sh | 2 + evaluation-servers/proftpd/Dockerfile | 22 + evaluation-servers/proftpd/build.sh | 1 + evaluation-servers/proftpd/docker-compose.yml | 13 + evaluation-servers/proftpd/proftpd.conf | 55 + evaluation-servers/proftpd/run.sh | 2 + evaluation-servers/pure-ftpd/Dockerfile | 17 + evaluation-servers/pure-ftpd/build.sh | 1 + .../pure-ftpd/docker-compose.yml | 13 + evaluation-servers/pure-ftpd/run.sh | 2 + evaluation-servers/sendmail/Dockerfile | 39 + evaluation-servers/sendmail/build.sh | 1 + .../sendmail/docker-compose.yml | 16 + evaluation-servers/sendmail/run.sh | 2 + evaluation-servers/sendmail/start.sh | 11 + evaluation-servers/vsftpd/Dockerfile | 25 + evaluation-servers/vsftpd/build.sh | 1 + evaluation-servers/vsftpd/docker-compose.yml | 13 + evaluation-servers/vsftpd/run.sh | 2 + evaluation-servers/vsftpd/start.sh | 7 + evaluation-servers/vsftpd/vsftpd.conf | 136 +++ 204 files changed, 11805 insertions(+) create mode 100644 evaluation-libraries/.gitignore create mode 100644 evaluation-libraries/README.md create mode 100644 evaluation-libraries/baseimage/Dockerfile create mode 100644 evaluation-libraries/baseimage/Dockerfile-archlinux create mode 100644 evaluation-libraries/baseimage/Dockerfile-debian create mode 100755 evaluation-libraries/baseimage/build.sh create mode 100755 evaluation-libraries/baseimage/certs/generate-ca.sh create mode 100755 evaluation-libraries/baseimage/client.sh create mode 100644 evaluation-libraries/bearssl/CMakeLists.txt create mode 100644 evaluation-libraries/bearssl/Dockerfile create mode 100644 evaluation-libraries/bearssl/LICENSE create mode 100644 evaluation-libraries/bearssl/README.md create mode 100755 evaluation-libraries/bearssl/build.sh create mode 100644 evaluation-libraries/bearssl/client/CMakeLists.txt create mode 100644 evaluation-libraries/bearssl/client/client.c create mode 100644 evaluation-libraries/bearssl/client/client.h create mode 100644 evaluation-libraries/bearssl/docker-compose.yml create mode 100755 evaluation-libraries/bearssl/run.sh create mode 100644 evaluation-libraries/bearssl/server/CMakeLists.txt create mode 100644 evaluation-libraries/bearssl/server/server.c create mode 100644 evaluation-libraries/bearssl/server/server.h create mode 100644 evaluation-libraries/botan/CMakeLists.txt create mode 100644 evaluation-libraries/botan/Dockerfile create mode 100644 evaluation-libraries/botan/LICENSE create mode 100644 evaluation-libraries/botan/README.md create mode 100755 evaluation-libraries/botan/build.sh create mode 100644 evaluation-libraries/botan/client/CMakeLists.txt create mode 100644 evaluation-libraries/botan/client/client.cpp create mode 100644 evaluation-libraries/botan/cmake/FindBotan2.cmake create mode 100644 evaluation-libraries/botan/docker-compose.yml create mode 100755 evaluation-libraries/botan/run.sh create mode 100644 evaluation-libraries/botan/server/CMakeLists.txt create mode 100644 evaluation-libraries/botan/server/server.cpp create mode 100644 evaluation-libraries/botan/server/server.h create mode 100755 evaluation-libraries/build-everything.sh create mode 100644 evaluation-libraries/gnutls/CMakeLists.txt create mode 100644 evaluation-libraries/gnutls/Dockerfile create mode 100644 evaluation-libraries/gnutls/LICENSE create mode 100755 evaluation-libraries/gnutls/build.sh create mode 100644 evaluation-libraries/gnutls/client/CMakeLists.txt create mode 100644 evaluation-libraries/gnutls/client/client.c create mode 100644 evaluation-libraries/gnutls/client/examples.h create mode 100644 evaluation-libraries/gnutls/client/tcp.c create mode 100644 evaluation-libraries/gnutls/cmake/FindGnuTLS.cmake create mode 100644 evaluation-libraries/gnutls/cmake/FindPackageHandleStandardArgs.cmake create mode 100644 evaluation-libraries/gnutls/cmake/FindPackageMessage.cmake create mode 100644 evaluation-libraries/gnutls/docker-compose.yml create mode 100644 evaluation-libraries/gnutls/readme.md create mode 100755 evaluation-libraries/gnutls/run.sh create mode 100644 evaluation-libraries/gnutls/server/CMakeLists.txt create mode 100644 evaluation-libraries/gnutls/server/server.c create mode 100644 evaluation-libraries/gnutls/server/server.h create mode 100644 evaluation-libraries/golang/Dockerfile create mode 100644 evaluation-libraries/golang/LICENSE create mode 100644 evaluation-libraries/golang/README.md create mode 100755 evaluation-libraries/golang/build.sh create mode 100755 evaluation-libraries/golang/client/client.go create mode 100644 evaluation-libraries/golang/client/go.mod create mode 100644 evaluation-libraries/golang/docker-compose.yml create mode 100755 evaluation-libraries/golang/run.sh create mode 100644 evaluation-libraries/golang/server/go.mod create mode 100644 evaluation-libraries/golang/server/server.go create mode 100644 evaluation-libraries/java/Dockerfile create mode 100644 evaluation-libraries/java/LICENSE-java-getopt create mode 100644 evaluation-libraries/java/README.md create mode 100755 evaluation-libraries/java/build.sh create mode 100644 evaluation-libraries/java/client/README.md create mode 100644 evaluation-libraries/java/client/lib/java-getopt-1.0.14.jar create mode 100644 evaluation-libraries/java/client/manifest create mode 100644 evaluation-libraries/java/client/src/Client.java create mode 100644 evaluation-libraries/java/docker-compose.yml create mode 100755 evaluation-libraries/java/run.sh create mode 100644 evaluation-libraries/java/server/README.md create mode 100644 evaluation-libraries/java/server/lib/java-getopt-1.0.14.jar create mode 100644 evaluation-libraries/java/server/manifest create mode 100644 evaluation-libraries/java/server/src/Server.java create mode 100644 evaluation-libraries/mbedtls/CMakeLists.txt create mode 100644 evaluation-libraries/mbedtls/Dockerfile create mode 100644 evaluation-libraries/mbedtls/LICENSE create mode 100644 evaluation-libraries/mbedtls/README.md create mode 100755 evaluation-libraries/mbedtls/build.sh create mode 100644 evaluation-libraries/mbedtls/client/CMakeLists.txt create mode 100644 evaluation-libraries/mbedtls/client/client.c create mode 100644 evaluation-libraries/mbedtls/docker-compose.yml create mode 100755 evaluation-libraries/mbedtls/run.sh create mode 100644 evaluation-libraries/mbedtls/server/CMakeLists.txt create mode 100644 evaluation-libraries/mbedtls/server/server.c create mode 100644 evaluation-libraries/openssl/.gitignore create mode 100644 evaluation-libraries/openssl/CMakeLists.txt create mode 100644 evaluation-libraries/openssl/Dockerfile-boringssl create mode 100644 evaluation-libraries/openssl/Dockerfile-openssl create mode 100644 evaluation-libraries/openssl/LICENSE create mode 100644 evaluation-libraries/openssl/README.md create mode 100755 evaluation-libraries/openssl/build.sh create mode 100644 evaluation-libraries/openssl/client/CMakeLists.txt create mode 100644 evaluation-libraries/openssl/client/client.c create mode 100644 evaluation-libraries/openssl/client/client.h create mode 100644 evaluation-libraries/openssl/docker-compose-boringssl.yml create mode 100644 evaluation-libraries/openssl/docker-compose.yml create mode 100755 evaluation-libraries/openssl/run.sh create mode 100644 evaluation-libraries/openssl/server/CMakeLists.txt create mode 100644 evaluation-libraries/openssl/server/server.c create mode 100644 evaluation-libraries/openssl/server/server.h create mode 100755 evaluation-libraries/run-everything.sh create mode 100644 evaluation-libraries/rustls/Cargo.toml create mode 100644 evaluation-libraries/rustls/Dockerfile create mode 100644 evaluation-libraries/rustls/LICENSE create mode 100644 evaluation-libraries/rustls/README.md create mode 100755 evaluation-libraries/rustls/build.sh create mode 100644 evaluation-libraries/rustls/client/client.rs create mode 100644 evaluation-libraries/rustls/docker-compose.yml create mode 100755 evaluation-libraries/rustls/run.sh create mode 100644 evaluation-libraries/rustls/server/server.rs create mode 100644 evaluation-libraries/wolfssl/CMakeLists.txt create mode 100644 evaluation-libraries/wolfssl/Dockerfile create mode 100644 evaluation-libraries/wolfssl/LICENSE create mode 100644 evaluation-libraries/wolfssl/README.md create mode 100755 evaluation-libraries/wolfssl/build.sh create mode 100644 evaluation-libraries/wolfssl/client/CMakeLists.txt create mode 100644 evaluation-libraries/wolfssl/client/client.c create mode 100644 evaluation-libraries/wolfssl/client/client.h create mode 100644 evaluation-libraries/wolfssl/docker-compose.yml create mode 100755 evaluation-libraries/wolfssl/run.sh create mode 100644 evaluation-libraries/wolfssl/server/CMakeLists.txt create mode 100644 evaluation-libraries/wolfssl/server/server.c create mode 100644 evaluation-libraries/wolfssl/server/server.h create mode 100644 evaluation-servers/.gitignore create mode 100644 evaluation-servers/README.md create mode 100644 evaluation-servers/apache/Dockerfile create mode 100644 evaluation-servers/apache/apache.conf create mode 100755 evaluation-servers/apache/build.sh create mode 100644 evaluation-servers/apache/docker-compose.yml create mode 100755 evaluation-servers/apache/run.sh create mode 100755 evaluation-servers/build.sh create mode 100644 evaluation-servers/courier/Dockerfile create mode 100755 evaluation-servers/courier/build.sh create mode 100644 evaluation-servers/courier/docker-compose.yml create mode 100644 evaluation-servers/courier/imapd-ssl create mode 100755 evaluation-servers/courier/run.sh create mode 100644 evaluation-servers/courier/smtpd.conf create mode 100644 evaluation-servers/courier/start.sh create mode 100644 evaluation-servers/cyrus/Dockerfile create mode 100755 evaluation-servers/cyrus/build.sh create mode 100644 evaluation-servers/cyrus/cyrus.asc create mode 100644 evaluation-servers/cyrus/cyrus.conf create mode 100644 evaluation-servers/cyrus/docker-compose.yml create mode 100644 evaluation-servers/cyrus/imapd.conf create mode 100755 evaluation-servers/cyrus/run.sh create mode 100644 evaluation-servers/dovecot/Dockerfile create mode 100755 evaluation-servers/dovecot/build.sh create mode 100644 evaluation-servers/dovecot/docker-compose.yml create mode 100755 evaluation-servers/dovecot/run.sh create mode 100644 evaluation-servers/exim/Dockerfile create mode 100755 evaluation-servers/exim/build.sh create mode 100644 evaluation-servers/exim/docker-compose.yml create mode 100644 evaluation-servers/exim/exim.conf create mode 100755 evaluation-servers/exim/run.sh create mode 100644 evaluation-servers/filezilla-server/Dockerfile create mode 100755 evaluation-servers/filezilla-server/build.sh create mode 100644 evaluation-servers/filezilla-server/docker-compose.yml create mode 100755 evaluation-servers/filezilla-server/run.sh create mode 100644 evaluation-servers/lighttpd/Dockerfile create mode 100644 evaluation-servers/lighttpd/Dockerfile-mbedtls create mode 100755 evaluation-servers/lighttpd/build.sh create mode 100644 evaluation-servers/lighttpd/docker-compose.yml create mode 100644 evaluation-servers/lighttpd/lighttpd-mbedtls.conf create mode 100644 evaluation-servers/lighttpd/lighttpd.conf create mode 100755 evaluation-servers/lighttpd/run.sh create mode 100644 evaluation-servers/nginx/Dockerfile create mode 100755 evaluation-servers/nginx/build.sh create mode 100644 evaluation-servers/nginx/docker-compose.yml create mode 100644 evaluation-servers/nginx/nginx.conf create mode 100755 evaluation-servers/nginx/run.sh create mode 100644 evaluation-servers/opensmtpd/Dockerfile create mode 100755 evaluation-servers/opensmtpd/build.sh create mode 100644 evaluation-servers/opensmtpd/docker-compose.yml create mode 100755 evaluation-servers/opensmtpd/run.sh create mode 100644 evaluation-servers/opensmtpd/smtpd.conf create mode 100644 evaluation-servers/postfix/Dockerfile create mode 100755 evaluation-servers/postfix/build.sh create mode 100644 evaluation-servers/postfix/docker-compose.yml create mode 100644 evaluation-servers/postfix/main.cf create mode 100644 evaluation-servers/postfix/master.cf create mode 100755 evaluation-servers/postfix/run.sh create mode 100644 evaluation-servers/proftpd/Dockerfile create mode 100755 evaluation-servers/proftpd/build.sh create mode 100644 evaluation-servers/proftpd/docker-compose.yml create mode 100644 evaluation-servers/proftpd/proftpd.conf create mode 100755 evaluation-servers/proftpd/run.sh create mode 100644 evaluation-servers/pure-ftpd/Dockerfile create mode 100755 evaluation-servers/pure-ftpd/build.sh create mode 100644 evaluation-servers/pure-ftpd/docker-compose.yml create mode 100755 evaluation-servers/pure-ftpd/run.sh create mode 100644 evaluation-servers/sendmail/Dockerfile create mode 100755 evaluation-servers/sendmail/build.sh create mode 100644 evaluation-servers/sendmail/docker-compose.yml create mode 100755 evaluation-servers/sendmail/run.sh create mode 100644 evaluation-servers/sendmail/start.sh create mode 100644 evaluation-servers/vsftpd/Dockerfile create mode 100755 evaluation-servers/vsftpd/build.sh create mode 100644 evaluation-servers/vsftpd/docker-compose.yml create mode 100755 evaluation-servers/vsftpd/run.sh create mode 100644 evaluation-servers/vsftpd/start.sh create mode 100644 evaluation-servers/vsftpd/vsftpd.conf diff --git a/evaluation-libraries/.gitignore b/evaluation-libraries/.gitignore new file mode 100644 index 0000000..e4161f6 --- /dev/null +++ b/evaluation-libraries/.gitignore @@ -0,0 +1,4 @@ +*.vscode +build/ +java/client/bin/Client.class +java/server/bin/Server.class diff --git a/evaluation-libraries/README.md b/evaluation-libraries/README.md new file mode 100644 index 0000000..8b7b943 --- /dev/null +++ b/evaluation-libraries/README.md @@ -0,0 +1,42 @@ +# evaluation-libraries +TLS-library examples with strict SNI and strict ALPN implemented to prevent the cross-protocol attacks demonstrated in the [ALPACA-Attack](https://alpaca-attack.com/index.html). + +DISCLAIMER: The implementations only focused on the ALPN&SNI TLS-Extensions, i can't guarantee that they are otherwise securely implemented. + +## Containers +Each library example starts the following containers +- ``server`` with SNI=tls-server.com , ALPN=http/1.1 written in the library +- ``server-openssl-wrong-cn`` with SNI=tls-server.com , ALPN=http/1.1 and a certificate that has a wrong common name +- ``server-openssl-malicious-alpn`` with SNI=tls-server.com and always sends back ALPN=invalid +- ``client`` runs a bash script that does the following tests + +## Tests +1. send correct SNI and ALPN to ``server`` and send application data +2. send wrong SNI to ``server`` (tests SNI on server) +3. send wrong ALPN to ``server`` (tests ALPN on server) +4. send correct SNI and ALPN to ``server-openssl-wrong-cn`` (tests strict SNI on client) +5. send correct SNI and ALPN to ``server-openssl-malicious-alpn`` (tests strict ALPN on client) + +The first test needs to succeed and every other tests needs to return a non-null value. + +## How to run +Requires docker, docker-compose and easy-rsa + +This builds all containers, runs all test and puts the results in a file called ``results`` +``` +./run-everything.sh +``` + +---------------- +### Running single libraries +First build the baseimage and the openssl image. (The openssl image is required for tests 4 and 5) +``` +cd baseimage && ./build.sh && cd .. +cd openssl && ./build.sh && cd .. +``` + +Then go into any of the library folders and start the tests +``` +./run.sh +``` + diff --git a/evaluation-libraries/baseimage/Dockerfile b/evaluation-libraries/baseimage/Dockerfile new file mode 100644 index 0000000..2591709 --- /dev/null +++ b/evaluation-libraries/baseimage/Dockerfile @@ -0,0 +1,28 @@ +ARG VERSION=3.15 +FROM alpine:${VERSION} +RUN apk add \ + git \ + linux-headers \ + cmake \ + make \ + wget \ + bash \ + autoconf \ + automake \ + coreutils \ + patch \ + gettext-dev \ + gperf \ + pkgconf \ + libtool \ + g++ \ + gcc \ + perl \ + python3 \ + go +COPY ./certs/ca.crt /etc/ssl/certs/ +COPY ./certs /etc/ssl/cert-data +COPY client.sh /client.sh +RUN mkdir /src +RUN mkdir /build +WORKDIR /src/ diff --git a/evaluation-libraries/baseimage/Dockerfile-archlinux b/evaluation-libraries/baseimage/Dockerfile-archlinux new file mode 100644 index 0000000..959490b --- /dev/null +++ b/evaluation-libraries/baseimage/Dockerfile-archlinux @@ -0,0 +1,17 @@ +FROM archlinux:base-devel +RUN pacman-key --init +RUN pacman-key --populate archlinux +RUN pacman -Syu --noconfirm +RUN pacman -S git --noconfirm + +#create build user that has root access because archlinux doesn't allow makepkg to be run as root +RUN useradd --no-create-home --shell=/bin/false build && usermod -L build +RUN echo "build ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers +RUN echo "root ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers + +COPY ./certs/ca.crt /etc/ssl/certs/ +COPY ./certs /etc/ssl/cert-data +COPY client.sh /client.sh +RUN mkdir /src +WORKDIR /src/ +RUN chown build /src \ No newline at end of file diff --git a/evaluation-libraries/baseimage/Dockerfile-debian b/evaluation-libraries/baseimage/Dockerfile-debian new file mode 100644 index 0000000..fec0501 --- /dev/null +++ b/evaluation-libraries/baseimage/Dockerfile-debian @@ -0,0 +1,26 @@ +ARG VERSION=bullseye +FROM debian:${VERSION} +RUN apt-get update && apt-get install -y \ + git \ + cmake \ + make \ + wget \ + bash \ + autoconf \ + automake \ + coreutils \ + patch \ + gperf \ + pkgconf \ + libtool \ + g++ \ + gcc \ + perl \ + python3 \ + golang +COPY ./certs/ca.crt /etc/ssl/certs/ +COPY ./certs /etc/ssl/cert-data +COPY client.sh /client.sh +RUN mkdir /src +RUN mkdir /build +WORKDIR /src/ diff --git a/evaluation-libraries/baseimage/build.sh b/evaluation-libraries/baseimage/build.sh new file mode 100755 index 0000000..588061d --- /dev/null +++ b/evaluation-libraries/baseimage/build.sh @@ -0,0 +1,6 @@ +(cd certs + ./generate-ca.sh); + +docker build -t tls-baseimage . +docker build -t tls-baseimagedebian -f Dockerfile-debian . +docker build -t tls-baseimage-archlinux -f Dockerfile-archlinux . diff --git a/evaluation-libraries/baseimage/certs/generate-ca.sh b/evaluation-libraries/baseimage/certs/generate-ca.sh new file mode 100755 index 0000000..309decf --- /dev/null +++ b/evaluation-libraries/baseimage/certs/generate-ca.sh @@ -0,0 +1,71 @@ +DIR="`pwd`/`dirname "$0"`/" + +echo $DIR + +if [ "$OS" = "Darwin" ]; then + brew install easy-rsa +else + apt-get install -y easy-rsa +fi + +path="/usr/share/easy-rsa/" +if [ "$OS" = "Darwin" ]; then + path="" + DIR_MAC="/usr/local/etc/" +fi +echo -e "${GREEN}[CERT] Creating PKI${NC}" +${path}easyrsa init-pki --pki-dir = "$DIR/pki" +cat << EOF > "$DIR/pki/vars" +set_var EASYRSA_DN "cn_only" +set_var EASYRSA_DIGEST "sha512" +set_var EASYRSA_BATCH "1" +set_var EASYRSA_REQ_CN "alpaca.poc" +EOF +dd if=/dev/urandom of="$DIR/pki/.rnd" bs=256 count=1 2> /dev/null +echo -e "${GREEN}[CERT] Build CA${NC}" +${path}easyrsa build-ca nopass + +echo -e "${GREEN}[CERT] Generating Certificates${NC}" +${path}easyrsa --req-cn="tls-server.com" gen-req tls-server.com nopass +${path}easyrsa sign-req server tls-server.com + +${path}easyrsa --req-cn="wrong-cn.com" gen-req wrong-cn.com nopass +${path}easyrsa sign-req server wrong-cn.com + +#copy certs +cp "$DIR/pki/issued/tls-server.com.crt" "$DIR" +cp "$DIR/pki/private/tls-server.com.key" "$DIR" +cp "$DIR/pki/issued/wrong-cn.com.crt" "$DIR" +cp "$DIR/pki/private/wrong-cn.com.key" "$DIR" +cp "$DIR/pki/ca.crt" "$DIR" + +#generate chains +cat "$DIR/tls-server.com.crt" >> "$DIR/tls-server.com-chain.crt" +cat "$DIR/ca.crt" >> "$DIR/tls-server.com-chain.crt" + +#generate chains +cat "$DIR/wrong-cn.com.crt" >> "$DIR/wrong-cn.com-chain.crt" +cat "$DIR/ca.crt" >> "$DIR/wrong-cn.com-chain.crt" + +#generate p12 +openssl pkcs12 -export -in "$DIR/tls-server.com.crt" -inkey "$DIR/tls-server.com.key" -out "$DIR/tls-server.com.p12" -password pass:123456 +openssl pkcs12 -export -in "$DIR/wrong-cn.com.crt" -inkey "$DIR/wrong-cn.com.key" -out "$DIR/wrong-cn.com.p12" -password pass:123456 + +openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in tls-server.com.key -out tls-server.com.pkcs8.key +openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in wrong-cn.com.key -out wrong-cn.com.pkcs8.key + +#if [ "$OS" = "Darwin" ]; then +# DIR_MAC="/usr/local/etc" +#else +# DIR_MAC=${DIR} +#fi +# +#mkdir -p "$DIR/servers/files/cert/" 2> /dev/null +#cp "$DIR_MAC/pki/issued/attacker.com.crt" "$DIR/servers/files/cert/" +#cp "$DIR_MAC/pki/private/attacker.com.key" "$DIR/servers/files/cert/" +# +#${path}easyrsa --req-cn="target.com" gen-req target.com nopass +#${path}easyrsa sign-req server target.com +# +#cp "$DIR_MAC/pki/issued/target.com.crt" "$DIR/servers/files/cert/" +#cp "$DIR_MAC/pki/private/target.com.key" "$DIR/servers/files/cert/" diff --git a/evaluation-libraries/baseimage/client.sh b/evaluation-libraries/baseimage/client.sh new file mode 100755 index 0000000..82acc71 --- /dev/null +++ b/evaluation-libraries/baseimage/client.sh @@ -0,0 +1,57 @@ +#!/bin/bash +#$1 command to run +#$2 server1 to connect +#$3 server2 to connect +#$4 openssl-malicious-alpn server +#$5 wait seconds before starting + +results=() + +sleep $5 + +echo "------------ Test 1: SNI=tls-server.com ALPN=http/1.1 ------------------" +$1 -h $2 -s tls-server.com -a http/1.1 +results+=($?) + +echo "------------ Test 2: SNI=example.com ALPN=http/1.1 ------------------" +/openssl-client -h $2 -s example.com -a http/1.1 +results+=($?) + +echo "------------ Test 3: SNI=tls-server.com ALPN=invalid ------------------" +/openssl-client -h $2 -s tls-server.com -a invalid +results+=($?) + +echo "------------ Test 4: wrong certificate by server ------------------" +$1 -h $3 -s tls-server.com -a http/1.1 +results+=($?) + +echo "------------ Test 5: server sends wrong alpn ------------------" +$1 -h $4 -s tls-server.com -a http/1.1 +results+=($?) + +RED='\033[0;31m ' +GREEN='\033[0;32m ' +NC='\033[0m' # No Color + +echo "" > results + +for i in "${!results[@]}"; do + test=$((i+1)) + if [ $i = "0" ]; then #first test needs to return 0 + if [ ${results[$i]} = "0" ]; + then + echo -e "${GREEN}Test$test success! exitcode:${results[$i]}" >> results; + else + echo -e "${RED}Test$test FAILED! exitcode:${results[$i]}" >> results; + fi + else #every other test needs to return non-zero value + if [ ${results[$i]} = "0" ]; + then + echo -e "${RED}Test$test FAILED! exitcode:${results[$i]}" >> results; + else + echo -e "${GREEN}Test$test success! exitcode:${results[$i]}" >> results; + fi + fi +done + +cat results \ No newline at end of file diff --git a/evaluation-libraries/bearssl/CMakeLists.txt b/evaluation-libraries/bearssl/CMakeLists.txt new file mode 100644 index 0000000..300646c --- /dev/null +++ b/evaluation-libraries/bearssl/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.0.0) +project(alpaca-bearssl VERSION 0.1.0) + + + +add_subdirectory(client) +add_subdirectory(server) diff --git a/evaluation-libraries/bearssl/Dockerfile b/evaluation-libraries/bearssl/Dockerfile new file mode 100644 index 0000000..369ea63 --- /dev/null +++ b/evaluation-libraries/bearssl/Dockerfile @@ -0,0 +1,37 @@ +# syntax=docker/dockerfile:1 +FROM tls-openssl as tls-bearssl +ARG VERSION=0.6 + +RUN apk add sed + +WORKDIR /build +RUN git clone --depth=1 --branch=v${VERSION} https://www.bearssl.org/git/BearSSL +WORKDIR /build/BearSSL +RUN make + + + + +WORKDIR /build +ADD server /build/server +ADD client /build/client +ADD CMakeLists.txt /build/CMakeLists.txt + +# generate c code from private keys and certs +RUN ls /build/server/ +RUN /build/BearSSL/build/brssl chain /etc/ssl/cert-data/tls-server.com-chain.crt | tail -n +2 >> /build/server/server.h +RUN /build/BearSSL/build/brssl skey -C /etc/ssl/cert-data/tls-server.com.key | tail -n +2 >> /build/server/server.h + +# wrong-cn.com key&cert need different variable names +RUN /build/BearSSL/build/brssl chain /etc/ssl/cert-data/wrong-cn.com-chain.crt | tail -n +2 | sed "s/\(\(CERT[01]\)\|\(CHAIN\(_LEN\)\?\)\|\(RSA\(_[DIPQ]\*\)\?\)\)/WRONG_\1/g" >> /build/server/server.h +RUN /build/BearSSL/build/brssl skey -C /etc/ssl/cert-data/wrong-cn.com.key | tail -n +2 | sed "s/\(\(CERT[01]\)\|\(CHAIN\(_LEN\)\?\)\|\(RSA\(_[DIPQ]\*\)\?\)\)/WRONG_\1/g" >> /build/server/server.h + + +RUN cmake . .. && make +RUN mv /build/server/server / +RUN mv /build/client/client / +COPY --from=tls-openssl /openssl-client /openssl-client + + +WORKDIR / +CMD ["/server"] \ No newline at end of file diff --git a/evaluation-libraries/bearssl/LICENSE b/evaluation-libraries/bearssl/LICENSE new file mode 100644 index 0000000..f5cf444 --- /dev/null +++ b/evaluation-libraries/bearssl/LICENSE @@ -0,0 +1,21 @@ +Copyright (c) 2016 Thomas Pornin + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/evaluation-libraries/bearssl/README.md b/evaluation-libraries/bearssl/README.md new file mode 100644 index 0000000..e7c789b --- /dev/null +++ b/evaluation-libraries/bearssl/README.md @@ -0,0 +1,13 @@ +# bearssl example with strict sni and strict alpn + +Tested with bearSSL 0.6 + +needs tls-baseimage already in docker + +Based on BearSSL/tools/server.c and client.c + +```bash +./run.sh +``` + + diff --git a/evaluation-libraries/bearssl/build.sh b/evaluation-libraries/bearssl/build.sh new file mode 100755 index 0000000..8a1b48c --- /dev/null +++ b/evaluation-libraries/bearssl/build.sh @@ -0,0 +1 @@ +docker build --build-arg VERSION=0.6 . -t tls-bearssl -f Dockerfile diff --git a/evaluation-libraries/bearssl/client/CMakeLists.txt b/evaluation-libraries/bearssl/client/CMakeLists.txt new file mode 100644 index 0000000..2eff2b0 --- /dev/null +++ b/evaluation-libraries/bearssl/client/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.0.0) +project(client VERSION 0.1.0) + +#include_directories(${CMAKE_SOURCE_DIR}/BearSSL/inc) +add_library(brssl STATIC IMPORTED) +set_target_properties(brssl PROPERTIES + IMPORTED_LOCATION "${CMAKE_SOURCE_DIR}/BearSSL/build/libbearssl.a" + INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_SOURCE_DIR}/BearSSL/inc" + ) +add_executable(client client.c) +target_link_libraries(client brssl) +target_compile_options(client PRIVATE -Wall -Wextra) diff --git a/evaluation-libraries/bearssl/client/client.c b/evaluation-libraries/bearssl/client/client.c new file mode 100644 index 0000000..73dce75 --- /dev/null +++ b/evaluation-libraries/bearssl/client/client.c @@ -0,0 +1,165 @@ +#include "client.h" + +int main(int argc, char *argv[]) { + // Disable buffering on stdout so docker output is shown + setbuf(stdout, NULL); + + const char *host = "127.0.0.1"; + const char *alpn = "http/1.1"; + const char *servername = "tls-server.com"; + const char *port = "4433"; + + int opt; + while ((opt = getopt(argc, argv, "a:s:h:p:")) != -1) { + switch (opt) { + case 'a': + alpn = optarg; + break; + case 's': + servername = optarg; + break; + case 'h': + host = optarg; + break; + /*case 'c': + cert = optarg; + break;*/ + case 'p': + port = optarg; + break; + default: + fprintf(stderr, "Usage: %s [-a alpn] [-s servername] [-h ip] [-p port] \n", + argv[0]); + exit(EXIT_FAILURE); + } + } + printf("Parameters alpn=%s servername=%s cert=ca.crt host=%s \n", alpn, servername, host); + + int err; + int fd; + br_ssl_client_context sc; + br_x509_minimal_context xc; + unsigned char iobuf[BR_SSL_BUFSIZE_BIDI]; + br_sslio_context ioc; + + /* + * Open the socket to the target server. + */ + fd = host_connect(host, port); + if (fd < 0) { + return EXIT_FAILURE; + } + + /* + * Initialise the client context: + * -- Use the "full" profile (all supported algorithms). + * -- The provided X.509 validation engine is initialised, with + * the hardcoded trust anchor. + */ + br_ssl_client_init_full(&sc, &xc, TAs, TAs_NUM); + + /* + * Set the I/O buffer to the provided array. We allocated a + * buffer large enough for full-duplex behaviour with all + * allowed sizes of SSL records, hence we set the last argument + * to 1 (which means "split the buffer into separate input and + * output areas"). + */ + br_ssl_engine_set_buffer(&sc.eng, iobuf, sizeof iobuf, 1); + + /* Set TLS 1.2 */ + br_ssl_engine_set_versions(&sc.eng, BR_TLS12, BR_TLS12); + + /* set ALPN */ + const char **alpn_ptr; + alpn_ptr = malloc(sizeof(char) * strlen(alpn)); + alpn_ptr[0] = alpn; + br_ssl_engine_set_protocol_names(&sc.eng, alpn_ptr, 1); + br_ssl_engine_add_flags(&sc.eng, BR_OPT_FAIL_ON_ALPN_MISMATCH); + + /* + * Reset the client context, for a new handshake. We provide the + * target host name: it will be used for the SNI extension. The + * last parameter is 0: we are not trying to resume a session. + */ + br_ssl_client_reset(&sc, servername, 0); + + /* + * Initialise the simplified I/O wrapper context, to use our + * SSL client context, and the two callbacks for socket I/O. + */ + br_sslio_init(&ioc, &sc.eng, sock_read, &fd, sock_write, &fd); + br_sslio_flush(&ioc); + + const char *alpn_received = br_ssl_engine_get_selected_protocol(&sc.eng); + printf("ALPN negotiatiated: %s\n", alpn_received); + + // send message to server + //const char *message = "Hello from Client!"; + //br_sslio_write(&ioc, message, strlen(message)); + + for (;;) { + // send message to server + const char *message = "Hello from Client!"; + br_sslio_write(&ioc, message, strlen(message)); + br_sslio_flush(&ioc); + + // get message from server + unsigned char tmp[512]; + int rlen = br_sslio_read(&ioc, tmp, sizeof tmp); + if (rlen < 0) { + break; + } + if (rlen > 0) { + printf("%s \n", tmp); + break; + } + } + // Close the SSL connection + br_sslio_close(&ioc); + /* + * Close the socket. + */ + close(fd); + + /* + * Check whether we closed properly or not. If the engine is + * closed, then its error status allows to distinguish between + * a normal closure and a SSL error. + * + * If the engine is NOT closed, then this means that the + * underlying network socket was closed or failed in some way. + * Note that many Web servers out there do not properly close + * their SSL connections (they don't send a close_notify alert), + * which will be reported here as "socket closed without proper + * SSL termination". + */ + if (br_ssl_engine_current_state(&sc.eng) == BR_SSL_CLOSED) { + err = br_ssl_engine_last_error(&sc.eng); + if (err == 0) { + fprintf(stderr, "SSL closed.\n"); + return EXIT_SUCCESS; + } else if (err == BR_ERR_X509_BAD_SERVER_NAME) { + fprintf(stderr, "ERROR BR_ERR_X509_BAD_SERVER_NAME\n"); + return 112; + } else if (err == BR_ALERT_BAD_CERTIFICATE) { + fprintf(stderr, "ERROR BR_ALERT_BAD_CERTIFICATE\n"); + } else if (err == BR_ALERT_CERTIFICATE_UNKNOWN) { + fprintf(stderr, "ERROR BR_ALERT_CERTIFICATE_UNKNOWN\n"); + } else if (err == BR_ALERT_NO_APPLICATION_PROTOCOL) { + fprintf(stderr, "ERROR BR_ALERT_NO_APPLICATION_PROTOCOL\n"); + return 120; + } else if (err == BR_OPT_FAIL_ON_ALPN_MISMATCH) { + fprintf(stderr, "ERROR BR_OPT_FAIL_ON_ALPN_MISMATCH\n"); + return 120; + } else if (err == BR_ERR_BAD_SNI) { + fprintf(stderr, "ERROR BR_ERR_BAD_SNI\n"); + } else { + fprintf(stderr, "SSL ERROR %d\n", err); + } + return err; + } else { + fprintf(stderr, "SSL socket closed without proper termination\n"); + return EXIT_FAILURE; + } +} \ No newline at end of file diff --git a/evaluation-libraries/bearssl/client/client.h b/evaluation-libraries/bearssl/client/client.h new file mode 100644 index 0000000..6c712f0 --- /dev/null +++ b/evaluation-libraries/bearssl/client/client.h @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2016 Thomas Pornin + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* certs/ca.crt + Code generated by running "brssl ta ca.crt" +*/ +static const unsigned char TA0_DN[] = { + 0x30, 0x14, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0C, + 0x09, 0x54, 0x68, 0x65, 0x73, 0x69, 0x73, 0x20, 0x43, 0x41}; + +static const unsigned char TA0_RSA_N[] = { + 0xD2, 0x20, 0x8A, 0x5D, 0x20, 0x05, 0x4B, 0x15, 0x3D, 0x00, 0x29, 0x4A, + 0xFB, 0x95, 0x0F, 0x7A, 0x3E, 0x04, 0x61, 0xC2, 0x95, 0x85, 0x80, 0xAD, + 0xD9, 0xA8, 0xA3, 0x07, 0x22, 0xB1, 0x60, 0xA2, 0x1C, 0xA0, 0x90, 0xA0, + 0x14, 0x30, 0x45, 0x3D, 0xF6, 0xC6, 0x26, 0x5D, 0xA3, 0xE7, 0x05, 0x6A, + 0xFC, 0x5C, 0x3F, 0x8B, 0xE4, 0xF1, 0xB1, 0xD1, 0xCF, 0x43, 0x7C, 0x82, + 0x39, 0xEB, 0x81, 0xC5, 0xF9, 0x55, 0x03, 0x7E, 0x68, 0x1C, 0x6A, 0x52, + 0x1C, 0x29, 0x0B, 0x15, 0x43, 0x4B, 0x0D, 0xA7, 0x99, 0xCA, 0xBA, 0x7E, + 0xFD, 0x19, 0xB6, 0xA4, 0x00, 0xFD, 0x64, 0xE9, 0xBC, 0x87, 0xA1, 0x48, + 0xBE, 0x3F, 0x0D, 0xE0, 0xF1, 0xD7, 0xE6, 0x31, 0x99, 0x81, 0xE2, 0xC3, + 0x4B, 0x21, 0xFE, 0x6C, 0x70, 0x57, 0x9F, 0x86, 0x61, 0xA3, 0x95, 0x6A, + 0xC9, 0x0E, 0x1E, 0xE1, 0x66, 0x9F, 0x5D, 0xD2, 0xE0, 0x65, 0x6D, 0xB7, + 0xE5, 0x45, 0x93, 0xE0, 0xCA, 0x9E, 0xA5, 0x2E, 0x94, 0x9D, 0x1F, 0x1A, + 0x96, 0x02, 0xCF, 0x7B, 0xE6, 0x39, 0x6C, 0x0C, 0x34, 0xA4, 0xA1, 0x7E, + 0xB3, 0x38, 0x5F, 0x5D, 0x46, 0x40, 0x90, 0xAF, 0x8C, 0x56, 0x60, 0xEC, + 0xB9, 0x86, 0x78, 0xF6, 0x36, 0x38, 0x35, 0x28, 0x88, 0xC0, 0xFA, 0x57, + 0x9D, 0xFE, 0x94, 0x97, 0x2F, 0x0A, 0x31, 0x41, 0x02, 0xE6, 0xFA, 0x03, + 0x72, 0x98, 0x64, 0x71, 0x28, 0x6D, 0xFB, 0x12, 0x88, 0x7B, 0x41, 0xA7, + 0x8E, 0xBB, 0x6C, 0x16, 0x70, 0x86, 0x58, 0x55, 0x58, 0xF3, 0xE8, 0x60, + 0x24, 0xBF, 0x0D, 0x9C, 0x78, 0x8B, 0x0B, 0xCB, 0xD5, 0xA8, 0x8E, 0x3E, + 0x9F, 0x71, 0x46, 0x2A, 0x5A, 0x16, 0xE8, 0xE8, 0x63, 0xBC, 0x5E, 0x0A, + 0x5D, 0xE9, 0xF0, 0x99, 0xAB, 0x49, 0x8E, 0x44, 0xB7, 0x36, 0xEF, 0xC6, + 0x42, 0xC1, 0xC3, 0x71}; + +static const unsigned char TA0_RSA_E[] = { + 0x01, 0x00, 0x01}; + +static const br_x509_trust_anchor TAs[1] = { + {{(unsigned char *)TA0_DN, sizeof TA0_DN}, + BR_X509_TA_CA, + {BR_KEYTYPE_RSA, + {.rsa = { + (unsigned char *)TA0_RSA_N, + sizeof TA0_RSA_N, + (unsigned char *)TA0_RSA_E, + sizeof TA0_RSA_E, + }}}}}; +//only one certificate +#define TAs_NUM 1 + +/* + * Connect to the specified host and port. The connected socket is + * returned, or -1 on error. + */ +static int +host_connect(const char *host, const char *port) { + struct addrinfo hints, *si, *p; + int fd; + int err; + + memset(&hints, 0, sizeof hints); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + err = getaddrinfo(host, port, &hints, &si); + if (err != 0) { + fprintf(stderr, "ERROR: getaddrinfo(): %s\n", + gai_strerror(err)); + return -1; + } + fd = -1; + for (p = si; p != NULL; p = p->ai_next) { + struct sockaddr *sa; + void *addr; + char tmp[INET6_ADDRSTRLEN + 50]; + + sa = (struct sockaddr *)p->ai_addr; + if (sa->sa_family == AF_INET) { + addr = &((struct sockaddr_in *)sa)->sin_addr; + } else if (sa->sa_family == AF_INET6) { + addr = &((struct sockaddr_in6 *)sa)->sin6_addr; + } else { + addr = NULL; + } + if (addr != NULL) { + inet_ntop(p->ai_family, addr, tmp, sizeof tmp); + } else { + sprintf(tmp, "", + (int)sa->sa_family); + } + //fprintf(stderr, "connecting to: %s\n", tmp); + fd = socket(p->ai_family, p->ai_socktype, p->ai_protocol); + if (fd < 0) { + perror("socket()"); + continue; + } + if (connect(fd, p->ai_addr, p->ai_addrlen) < 0) { + perror("connect()"); + close(fd); + continue; + } + break; + } + if (p == NULL) { + freeaddrinfo(si); + fprintf(stderr, "ERROR: failed to connect\n"); + return -1; + } + freeaddrinfo(si); + //fprintf(stderr, "connected.\n"); + return fd; +} + +/* + * Low-level data read callback for the simplified SSL I/O API. + */ +static int +sock_read(void *ctx, unsigned char *buf, size_t len) { + for (;;) { + ssize_t rlen; + + rlen = read(*(int *)ctx, buf, len); + if (rlen <= 0) { + if (rlen < 0 && errno == EINTR) { + continue; + } + return -1; + } + return (int)rlen; + } +} + +/* + * Low-level data write callback for the simplified SSL I/O API. + */ +static int +sock_write(void *ctx, const unsigned char *buf, size_t len) { + for (;;) { + ssize_t wlen; + + wlen = write(*(int *)ctx, buf, len); + if (wlen <= 0) { + if (wlen < 0 && errno == EINTR) { + continue; + } + return -1; + } + return (int)wlen; + } +} \ No newline at end of file diff --git a/evaluation-libraries/bearssl/docker-compose.yml b/evaluation-libraries/bearssl/docker-compose.yml new file mode 100644 index 0000000..f87f30f --- /dev/null +++ b/evaluation-libraries/bearssl/docker-compose.yml @@ -0,0 +1,21 @@ +version: "3.9" +networks: + default: + name: tls-network + internal: true +services: + bearssl-server: + image: tls-bearssl + openssl-server-wrong-cn: + image: tls-openssl + command: [ "/openssl-server", "-k", "/etc/ssl/cert-data/wrong-cn.com.key", "-c" , "/etc/ssl/cert-data/wrong-cn.com-chain.crt"] + openssl-malicious-alpn: + image: tls-openssl + command: [ "/openssl-server", "-m"] + bearssl-client: + image: tls-bearssl + command: [ "./client.sh", "/client", "bearssl-server", "openssl-server-wrong-cn", "openssl-malicious-alpn" ,"1"] + depends_on: + - bearssl-server + - openssl-server-wrong-cn + - openssl-malicious-alpn diff --git a/evaluation-libraries/bearssl/run.sh b/evaluation-libraries/bearssl/run.sh new file mode 100755 index 0000000..538a631 --- /dev/null +++ b/evaluation-libraries/bearssl/run.sh @@ -0,0 +1,2 @@ +./build.sh +docker-compose up --abort-on-container-exit --exit-code-from bearssl-client --remove-orphans diff --git a/evaluation-libraries/bearssl/server/CMakeLists.txt b/evaluation-libraries/bearssl/server/CMakeLists.txt new file mode 100644 index 0000000..d1737a4 --- /dev/null +++ b/evaluation-libraries/bearssl/server/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.0.0) +project(server VERSION 0.1.0) + +#include_directories(${CMAKE_SOURCE_DIR}/BearSSL/inc) +add_library(brssl STATIC IMPORTED) +set_target_properties(brssl PROPERTIES + IMPORTED_LOCATION "${CMAKE_SOURCE_DIR}/BearSSL/build/libbearssl.a" + INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_SOURCE_DIR}/BearSSL/inc" + ) +add_executable(server server.c) +target_link_libraries(server brssl) +target_compile_options(server PRIVATE -Wall -Wextra) \ No newline at end of file diff --git a/evaluation-libraries/bearssl/server/server.c b/evaluation-libraries/bearssl/server/server.c new file mode 100644 index 0000000..9f4b74d --- /dev/null +++ b/evaluation-libraries/bearssl/server/server.c @@ -0,0 +1,163 @@ +#include "server.h" + +#define CHECK(x) x; + +const char *servername = "tls-server.com"; + +int main(int argc, char *argv[]) { + // Disable buffering on stdout so docker output is shown + setbuf(stdout, NULL); + + const char *alpn = "http/1.1"; + const char *port = "4433"; + int wrong_certificate = 0; + /* Get commandline arguments */ + int opt; + while ((opt = getopt(argc, argv, "a:s:w")) != -1) { + switch (opt) { + case 'a': + alpn = optarg; + break; + case 's': + servername = optarg; + break; + case 'w': + wrong_certificate = 1; + break; + /*case 'k': + key = optarg; + break;*/ + default: + fprintf(stderr, "Usage: %s [-a alpn] [-s servername] [-w uses wrong cert] \n", argv[0]); + return EXIT_FAILURE; + } + } + printf("Parameters alpn=%s servername=%s use_wrong_cert=%d ", alpn, servername, wrong_certificate); + + int fd; + + const char **alpn_ptr; + alpn_ptr = malloc(sizeof(char) * strlen(alpn)); + alpn_ptr[0] = alpn; + + unsigned char tmp[512]; + const char *message = "Hello from Server!"; + + int cfd; + br_ssl_server_context sc; + unsigned char iobuf[BR_SSL_BUFSIZE_BIDI]; + br_sslio_context ioc; + int err; + + /* + * Open the server socket. + */ + fd = host_bind(NULL, port); + if (fd < 0) { + return EXIT_FAILURE; + } + + /* + * Process each client, one at a time. + */ + for (;;) { + cfd = accept_client(fd); + if (cfd < 0) { + return EXIT_FAILURE; + } + + /* + * Choose certificate + * CHAIN=tls-server.com-chain.crt + * WRONG_CHAIN=wrong-cn.com-chain.crt + */ + if (wrong_certificate == 1) { + br_ssl_server_init_full_rsa(&sc, WRONG_CHAIN, WRONG_CHAIN_LEN, &WRONG_RSA); + } else { + br_ssl_server_init_full_rsa(&sc, CHAIN, CHAIN_LEN, &RSA); + } + + /* from bearssl-0.6/src/ssl/br_ssl_server_set_single_rsa.c + normally gets called by br_ssl_server_init_full_rsa that then calls br_ssl_server_set_single_rsa + + change policy handler to a custom one + + sr_choose overrides the method that processes the ClientHello and chooses certificates, cipher suited etc. + */ + static const br_ssl_server_policy_class sr_policy_vtable = { + sizeof(br_ssl_server_policy_rsa_context), + choose, + do_keyx, + do_sign}; + (&sc)->chain_handler.single_rsa.vtable = &sr_policy_vtable; + (&sc)->policy_vtable = &(&sc)->chain_handler.single_rsa.vtable; + + /* Set TLS 1.2 */ + br_ssl_engine_set_versions(&sc.eng, BR_TLS12, BR_TLS12); + + /* + * Set the I/O buffer to the provided array. We + * allocated a buffer large enough for full-duplex + * behaviour with all allowed sizes of SSL records, + * hence we set the last argument to 1 (which means + * "split the buffer into separate input and output + * areas"). + */ + br_ssl_engine_set_buffer(&sc.eng, iobuf, sizeof iobuf, 1); + + /* set ALPN */ + + br_ssl_engine_set_protocol_names(&sc.eng, alpn_ptr, 1); + + // Enable strict ALPN + br_ssl_engine_add_flags(&sc.eng, BR_OPT_FAIL_ON_ALPN_MISMATCH); + + /* + * Reset the server context, for a new handshake. + */ + br_ssl_server_reset(&sc); + + /* + * Initialise the simplified I/O wrapper context. + */ + br_sslio_init(&ioc, &sc.eng, sock_read, &cfd, sock_write, &cfd); + br_sslio_flush(&ioc); + + for (;;) { + // get message from client + int rlen = br_sslio_read(&ioc, tmp, sizeof tmp); + if (rlen < 0) { + break; + } else { + printf("%s \n", tmp); + // send message to server + br_sslio_write(&ioc, message, strlen(message)); + br_sslio_close(&ioc); + break; + } + } + + // Check if SSL closed correctly + err = br_ssl_engine_last_error(&sc.eng); + if (err == 0) { + fprintf(stderr, "SSL closed.\n"); + } else if (err == BR_ERR_X509_BAD_SERVER_NAME) { + fprintf(stderr, "ERROR BR_ERR_X509_BAD_SERVER_NAME\n"); + } else if (err == BR_ERR_IO) { + fprintf(stderr, "BR_ERR_IO\n"); + } else if (err == BR_ALERT_BAD_CERTIFICATE) { + fprintf(stderr, "ERROR BR_ALERT_BAD_CERTIFICATE\n"); + } else if (err == BR_ALERT_CERTIFICATE_UNKNOWN) { + fprintf(stderr, "ERROR BR_ALERT_CERTIFICATE_UNKNOWN\n"); + } else if (err == BR_ALERT_NO_APPLICATION_PROTOCOL) { + fprintf(stderr, "ERROR BR_ALERT_NO_APPLICATION_PROTOCOL\n"); + } else if (err == BR_OPT_FAIL_ON_ALPN_MISMATCH) { + fprintf(stderr, "ERROR BR_OPT_FAIL_ON_ALPN_MISMATCH\n"); + } else if (err == BR_ERR_BAD_SNI) { + fprintf(stderr, "ERROR BR_ERR_BAD_SNI\n"); + } else { + fprintf(stderr, "SSL ERROR %d\n", err); + } + close(cfd); + } +} \ No newline at end of file diff --git a/evaluation-libraries/bearssl/server/server.h b/evaluation-libraries/bearssl/server/server.h new file mode 100644 index 0000000..f49659a --- /dev/null +++ b/evaluation-libraries/bearssl/server/server.h @@ -0,0 +1,356 @@ +/* + * Copyright (c) 2016 Thomas Pornin + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + +#include "bearssl.h" + +/* from bearssl-0.6/src/ssl/ssl_hashes.c */ +int br_ssl_choose_hash(unsigned bf) { + static const unsigned char pref[] = { + br_sha256_ID, br_sha384_ID, br_sha512_ID, + br_sha224_ID, br_sha1_ID}; + size_t u; + + for (u = 0; u < sizeof pref; u++) { + int x; + + x = pref[u]; + if ((bf >> x) & 1) { + return x; + } + } + return 0; +} + +/* from bearssl-0.6/src/ssl/br_ssl_server_set_single_rsa.c */ +static int +choose(const br_ssl_server_policy_class **pctx, + const br_ssl_server_context *cc, + br_ssl_server_choices *choices) { + br_ssl_server_policy_rsa_context *pc; + const br_suite_translated *st; + size_t u, st_num; + unsigned hash_id; + int fh; + + /* verify SNI extension */ + const char *servername_received = br_ssl_engine_get_server_name(&cc->eng); + if (strcmp("tls-server.com", servername_received) != 0) { + fprintf(stderr, "\n Invalid SNI received: %s\n", servername_received); + return 0; + } else { + printf("\n SNI received: %s\n", servername_received); + } + + //printf("workign fine %s \n", name); + + pc = (br_ssl_server_policy_rsa_context *)pctx; + st = br_ssl_server_get_client_suites(cc, &st_num); + if (cc->eng.session.version < BR_TLS12) { + hash_id = 0; + fh = 1; + } else { + hash_id = br_ssl_choose_hash( + br_ssl_server_get_client_hashes(cc)); + fh = (hash_id != 0); + } + choices->chain = pc->chain; + choices->chain_len = pc->chain_len; + for (u = 0; u < st_num; u++) { + unsigned tt; + + tt = st[u][1]; + switch (tt >> 12) { + case BR_SSLKEYX_RSA: + if ((pc->allowed_usages & BR_KEYTYPE_KEYX) != 0) { + choices->cipher_suite = st[u][0]; + return 1; + } + break; + case BR_SSLKEYX_ECDHE_RSA: + if ((pc->allowed_usages & BR_KEYTYPE_SIGN) != 0 && fh) { + choices->cipher_suite = st[u][0]; + choices->algo_id = hash_id + 0xFF00; + return 1; + } + break; + } + } + return 0; +} + +static uint32_t +do_keyx(const br_ssl_server_policy_class **pctx, + unsigned char *data, size_t *len) { + br_ssl_server_policy_rsa_context *pc; + + pc = (br_ssl_server_policy_rsa_context *)pctx; + return br_rsa_ssl_decrypt(pc->irsacore, pc->sk, data, *len); +} + +/* + * OID for hash functions in RSA signatures. + */ +static const unsigned char HASH_OID_SHA1[] = { + 0x05, 0x2B, 0x0E, 0x03, 0x02, 0x1A}; + +static const unsigned char HASH_OID_SHA224[] = { + 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04}; + +static const unsigned char HASH_OID_SHA256[] = { + 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01}; + +static const unsigned char HASH_OID_SHA384[] = { + 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02}; + +static const unsigned char HASH_OID_SHA512[] = { + 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03}; + +static const unsigned char *HASH_OID[] = { + HASH_OID_SHA1, + HASH_OID_SHA224, + HASH_OID_SHA256, + HASH_OID_SHA384, + HASH_OID_SHA512}; + +static size_t +do_sign(const br_ssl_server_policy_class **pctx, + unsigned algo_id, unsigned char *data, size_t hv_len, size_t len) { + br_ssl_server_policy_rsa_context *pc; + unsigned char hv[64]; + size_t sig_len; + const unsigned char *hash_oid; + + pc = (br_ssl_server_policy_rsa_context *)pctx; + memcpy(hv, data, hv_len); + algo_id &= 0xFF; + if (algo_id == 0) { + hash_oid = NULL; + } else if (algo_id >= 2 && algo_id <= 6) { + hash_oid = HASH_OID[algo_id - 2]; + } else { + return 0; + } + sig_len = (pc->sk->n_bitlen + 7) >> 3; + if (len < sig_len) { + return 0; + } + return pc->irsasign(hash_oid, hv, hv_len, pc->sk, data) ? sig_len : 0; +} + +/* + * This sample code can use three possible certificate chains: + * -- A full-RSA chain (server key is RSA, certificates are signed with RSA) + * -- A full-EC chain (server key is EC, certificates are signed with ECDSA) + * -- A mixed chain (server key is EC, certificates are signed with RSA) + * + * The macros below define which chain is selected. This impacts the list + * of supported cipher suites. + */ + +#if !(SERVER_RSA || SERVER_EC || SERVER_MIXED) +#define SERVER_RSA 1 +#define SERVER_EC 0 +#define SERVER_MIXED 0 +#endif + +/* + * Create a server socket bound to the specified host and port. If 'host' + * is NULL, this will bind "generically" (all addresses). + * + * Returned value is the server socket descriptor, or -1 on error. + */ +static int +host_bind(const char *host, const char *port) { + struct addrinfo hints, *si, *p; + int fd; + int err; + + memset(&hints, 0, sizeof hints); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + err = getaddrinfo(host, port, &hints, &si); + if (err != 0) { + fprintf(stderr, "ERROR: getaddrinfo(): %s\n", + gai_strerror(err)); + return -1; + } + fd = -1; + for (p = si; p != NULL; p = p->ai_next) { + struct sockaddr *sa; + struct sockaddr_in sa4; + struct sockaddr_in6 sa6; + size_t sa_len; + void *addr; + char tmp[INET6_ADDRSTRLEN + 50]; + int opt; + + sa = (struct sockaddr *)p->ai_addr; + if (sa->sa_family == AF_INET) { + sa4 = *(struct sockaddr_in *)sa; + sa = (struct sockaddr *)&sa4; + sa_len = sizeof sa4; + addr = &sa4.sin_addr; + if (host == NULL) { + sa4.sin_addr.s_addr = INADDR_ANY; + } + } else if (sa->sa_family == AF_INET6) { + sa6 = *(struct sockaddr_in6 *)sa; + sa = (struct sockaddr *)&sa6; + sa_len = sizeof sa6; + addr = &sa6.sin6_addr; + if (host == NULL) { + sa6.sin6_addr = in6addr_any; + } + } else { + addr = NULL; + sa_len = p->ai_addrlen; + } + if (addr != NULL) { + inet_ntop(p->ai_family, addr, tmp, sizeof tmp); + } else { + sprintf(tmp, "", + (int)sa->sa_family); + } + //fprintf(stderr, "binding to: %s\n", tmp); + fd = socket(p->ai_family, p->ai_socktype, p->ai_protocol); + if (fd < 0) { + perror("socket()"); + continue; + } + opt = 1; + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt); + opt = 0; + setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof opt); + if (bind(fd, sa, sa_len) < 0) { + perror("bind()"); + close(fd); + continue; + } + break; + } + if (p == NULL) { + freeaddrinfo(si); + fprintf(stderr, "ERROR: failed to bind\n"); + return -1; + } + freeaddrinfo(si); + if (listen(fd, 5) < 0) { + perror("listen()"); + close(fd); + return -1; + } + //printf(stderr, "bound.\n"); + return fd; +} + +/* + * Accept a single client on the provided server socket. This is blocking. + * On error, this returns -1. + */ +static int +accept_client(int server_fd) { + int fd; + struct sockaddr sa; + socklen_t sa_len; + char tmp[INET6_ADDRSTRLEN + 50]; + const char *name; + + sa_len = sizeof sa; + fd = accept(server_fd, &sa, &sa_len); + if (fd < 0) { + perror("accept()"); + return -1; + } + name = NULL; + switch (sa.sa_family) { + case AF_INET: + name = inet_ntop(AF_INET, + &((struct sockaddr_in *)&sa)->sin_addr, + tmp, sizeof tmp); + break; + case AF_INET6: + name = inet_ntop(AF_INET, + &((struct sockaddr_in *)&sa)->sin_addr, + tmp, sizeof tmp); + break; + } + if (name == NULL) { + sprintf(tmp, "", (unsigned long)sa.sa_family); + name = tmp; + } + //fprintf(stderr, "accepting connection from: %s\n", name); + return fd; +} + +/* + * Low-level data read callback for the simplified SSL I/O API. + */ +static int +sock_read(void *ctx, unsigned char *buf, size_t len) { + for (;;) { + ssize_t rlen; + + rlen = read(*(int *)ctx, buf, len); + if (rlen <= 0) { + if (rlen < 0 && errno == EINTR) { + continue; + } + return -1; + } + return (int)rlen; + } +} + +/* + * Low-level data write callback for the simplified SSL I/O API. + */ +static int +sock_write(void *ctx, const unsigned char *buf, size_t len) { + for (;;) { + ssize_t wlen; + + wlen = write(*(int *)ctx, buf, len); + if (wlen <= 0) { + if (wlen < 0 && errno == EINTR) { + continue; + } + return -1; + } + return (int)wlen; + } +} diff --git a/evaluation-libraries/botan/CMakeLists.txt b/evaluation-libraries/botan/CMakeLists.txt new file mode 100644 index 0000000..06203de --- /dev/null +++ b/evaluation-libraries/botan/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.0.0) +project(alpaca-botan VERSION 0.1.0) + + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) +include(CMakeToolsHelpers OPTIONAL) +include(FeatureSummary) + +include(CheckCCompilerFlag) +include(CheckCXXCompilerFlag) +include(CheckCXXSourceCompiles) + + + +add_subdirectory(client) +add_subdirectory(server) \ No newline at end of file diff --git a/evaluation-libraries/botan/Dockerfile b/evaluation-libraries/botan/Dockerfile new file mode 100644 index 0000000..abab3ba --- /dev/null +++ b/evaluation-libraries/botan/Dockerfile @@ -0,0 +1,24 @@ +# syntax=docker/dockerfile:1 +FROM tls-baseimage as tls-botan +ARG VERSION=2.18.1 +RUN wget https://botan.randombit.net/releases/Botan-${VERSION}.tar.xz +RUN tar -xf Botan-${VERSION}.tar.xz +WORKDIR /src/Botan-${VERSION} +RUN apk add python2 +RUN ./configure.py --prefix=/build/ +RUN make +RUN make install +RUN mv libbotan-2.a /lib/libbotan-2.a +RUN mv /build/include/* /usr/include/ + +ADD cmake /build/cmake +ADD server /build/server +ADD client /build/client +ADD CMakeLists.txt /build/CMakeLists.txt +WORKDIR /build +RUN cmake . .. && make +RUN mv /build/server/server / +RUN mv /build/client/client / +COPY --from=tls-openssl /openssl-client /openssl-client +WORKDIR / +CMD ["/server"] diff --git a/evaluation-libraries/botan/LICENSE b/evaluation-libraries/botan/LICENSE new file mode 100644 index 0000000..b73156f --- /dev/null +++ b/evaluation-libraries/botan/LICENSE @@ -0,0 +1,24 @@ +Copyright (C) 1999-2021 The Botan Authors +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions, and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/evaluation-libraries/botan/README.md b/evaluation-libraries/botan/README.md new file mode 100644 index 0000000..2a2beab --- /dev/null +++ b/evaluation-libraries/botan/README.md @@ -0,0 +1,13 @@ +# botan example with strict sni and strict alpn + +Tested with botan 2.17.3 + +needs tls-baseimage already in docker + +Based on https://github.com/randombit/botan tls_client.cpp and tls_server.cpp + +```bash +./run.sh +``` + + diff --git a/evaluation-libraries/botan/build.sh b/evaluation-libraries/botan/build.sh new file mode 100755 index 0000000..2ffda90 --- /dev/null +++ b/evaluation-libraries/botan/build.sh @@ -0,0 +1 @@ +docker build . -t tls-botan \ No newline at end of file diff --git a/evaluation-libraries/botan/client/CMakeLists.txt b/evaluation-libraries/botan/client/CMakeLists.txt new file mode 100644 index 0000000..b091162 --- /dev/null +++ b/evaluation-libraries/botan/client/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.0.0) +project(alpaca-botan-client VERSION 0.1.0) + +find_package(Botan2 REQUIRED) +include_directories(SYSTEM ${BOTAN2_INCLUDE_DIR}) + +add_executable(client client.cpp) +target_link_libraries(client pthread ${BOTAN2_LIBRARIES}) +target_compile_options(client PRIVATE -Wall -Wextra) \ No newline at end of file diff --git a/evaluation-libraries/botan/client/client.cpp b/evaluation-libraries/botan/client/client.cpp new file mode 100644 index 0000000..ecdf6ec --- /dev/null +++ b/evaluation-libraries/botan/client/client.cpp @@ -0,0 +1,334 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +typedef int socket_type; +typedef size_t sendrecv_len_type; + +socket_type m_sockfd; +std::string host = "127.0.0.1"; +std::string cert = "/etc/ssl/certs"; +std::string servername = "tls-server.com"; +std::vector alpn = {"http/1.1"}; +uint16_t port = 4433; + +bool message_received = false; + +socket_type connect_to_host(const std::string &host, uint16_t port, bool tcp) { + addrinfo hints; + Botan::clear_mem(&hints, 1); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = tcp ? SOCK_STREAM : SOCK_DGRAM; + addrinfo *res, *rp = nullptr; + + if (::getaddrinfo(host.c_str(), std::to_string(port).c_str(), &hints, &res) != 0) { + std::cerr << "getaddrinfo failed for" << host << std::endl; + } + + socket_type fd = 0; + + for (rp = res; rp != nullptr; rp = rp->ai_next) { + fd = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + + if (fd == -1) { + continue; + } + + if (::connect(fd, rp->ai_addr, static_cast(rp->ai_addrlen)) != 0) { + ::close(fd); + continue; + } + + break; + } + + ::freeaddrinfo(res); + + if (rp == nullptr) // no address succeeded + { + std::cerr << "connect failed" << std::endl; + exit(EXIT_FAILURE); + } + + return fd; +} + +class Basic_TLS_Policy final : public Botan::TLS::Policy { + public: + bool require_cert_revocation_info() const override { + return false; + } + bool acceptable_protocol_version(Botan::TLS::Protocol_Version version) const override { + if (version == Botan::TLS::Protocol_Version::TLS_V12 && allow_tls12()) + return true; + return false; + } +}; + +class Basic_Credentials_Manager : public Botan::Credentials_Manager { + public: + Basic_Credentials_Manager(const std::string &ca_path) { + if (ca_path.empty() == false) { + m_certstores.push_back(std::make_shared(ca_path)); + } + } + + Basic_Credentials_Manager() { + m_certstores.push_back(std::make_shared()); + } + + Basic_Credentials_Manager(const std::string &server_crt, const std::string &server_key) { + Certificate_Info cert; + + Botan::DataSource_Stream key_in(server_key); + cert.key = Botan::PKCS8::load_key(key_in); + + Botan::DataSource_Stream in(server_crt); + while (!in.end_of_data()) { + try { + cert.certs.push_back(Botan::X509_Certificate(in)); + } catch (std::exception &) { + } + } + m_creds.push_back(cert); + } + + std::vector + trusted_certificate_authorities(const std::string &type, + const std::string & /*hostname*/) override { + std::vector v; + + // don't ask for client certs + if (type == "tls-server.com") { + return v; + } + + for (auto const &cs : m_certstores) { + v.push_back(cs.get()); + } + + return v; + } + + std::vector cert_chain( + const std::vector &algos, + const std::string &type, + const std::string &hostname) override { + BOTAN_UNUSED(type); + + for (auto const &i : m_creds) { + if (std::find(algos.begin(), algos.end(), i.key->algo_name()) == algos.end()) { + continue; + } + + if (hostname != "" && !i.certs[0].matches_dns_name(hostname)) { + continue; + } + + return i.certs; + } + + return std::vector(); + } + + Botan::Private_Key *private_key_for(const Botan::X509_Certificate &cert, + const std::string & /*type*/, + const std::string & /*context*/) override { + for (auto const &i : m_creds) { + if (cert == i.certs[0]) { + return i.key.get(); + } + } + + return nullptr; + } + + public: + struct Certificate_Info { + std::vector certs; + std::shared_ptr key; + }; + + std::vector m_creds; + std::vector> m_certstores; +}; + +/** + * @brief Callbacks invoked by TLS::Channel. + * + * Botan::TLS::Callbacks is an abstract class. + * For improved readability, only the functions that are mandatory + * to implement are listed here. See src/lib/tls/tls_callbacks.h. + */ +class Callbacks : public Botan::TLS::Callbacks { + public: + void tls_emit_data(const uint8_t buf[], size_t length) override { + size_t offset = 0; + + while (length) { + ssize_t sent = ::send(m_sockfd, buf + offset, length, MSG_NOSIGNAL); + + if (sent == -1) { + if (errno == EINTR) { + sent = 0; + } else { + std::cerr << "Socket write failed errno=" << std::to_string(errno) << std::endl; + exit(EXIT_FAILURE); + } + } + + offset += sent; + length -= sent; + } + } + void tls_record_received(uint64_t /*seq_no*/, const uint8_t input[], size_t input_len) override { + for (size_t i = 0; i != input_len; ++i) { + std::cout << input[i]; + } + std::cout << std::endl; + message_received = true; + } + + void tls_alert(Botan::TLS::Alert alert) override { + std::cerr << "Alert: " << alert.type_string() << "\n"; + if (alert.is_fatal()) { + exit(alert.type()); + } + } + + bool tls_session_established(const Botan::TLS::Session &session) override { + return true; + } +}; + +int main(int argc, char **argv) { + int opt; + while ((opt = getopt(argc, argv, "a:s:c:h:d")) != -1) { + switch (opt) { + case 'a': + alpn[0] = optarg; + break; + case 's': + servername = optarg; + break; + case 'h': + host = optarg; + break; + case 'c': + cert = optarg; + break; + default: + fprintf(stderr, "Usage: %s [-a alpn] [-s servername] [-c CAfolder] [-h ip] \n", + argv[0]); + exit(EXIT_FAILURE); + } + } + std::cout << "Parameters alpn=" << alpn[0] << " servername=" << servername << " cert=" << cert << std::endl; + + struct sockaddr_storage addrbuf; + m_sockfd = -1; + std::string hostname; + if (!host.empty() && + inet_pton(AF_INET, host.c_str(), &addrbuf) != 1 && + inet_pton(AF_INET6, host.c_str(), &addrbuf) != 1) { + hostname = host; + } + + m_sockfd = connect_to_host(host, port, true); + + Basic_Credentials_Manager creds(cert); + Basic_TLS_Policy policy; + Callbacks callbacks; + Botan::AutoSeeded_RNG rng; + Botan::TLS::Session_Manager_In_Memory session_mgr(rng); + + // open the tls connection + Botan::TLS::Client client(callbacks, + session_mgr, + creds, + policy, + rng, + Botan::TLS::Server_Information(servername, port), + Botan::TLS::Protocol_Version::TLS_V12, alpn); + + bool first_active = true; + + while (!client.is_closed()) { + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(m_sockfd, &readfds); + + // Send Application Data + if (client.is_active() && first_active) { + if (!alpn.empty()) { + std::string app = client.application_protocol(); + if (app.compare(alpn[0]) != 0) { + std::cout << "INVALID ALPN: " << client.application_protocol() << "\n"; + exit(120); + } + std::cout << "ALPN: " << client.application_protocol() << "\n"; + } + + /* Send Message to Server */ + std::string message = "Hello from Client!\n"; + std::vector myVector(message.begin(), message.end()); + client.send(myVector.data(), message.size()); + + first_active = false; + //client.close(); + //break; + } + if (client.is_active() && message_received) { + client.close(); + } + if (FD_ISSET(m_sockfd, &readfds)) { + uint8_t buf[4 * 1024] = {0}; + + ssize_t got = ::read(m_sockfd, buf, sizeof(buf)); + + if (got == 0) { + std::cout << "EOF on socket\n"; + break; + } else if (got == -1) { + std::cout << "Socket error: " << errno << " " << std::strerror(errno) << "\n"; + exit(EXIT_FAILURE); + continue; + } + + try { + client.received_data(buf, got); + } catch (Botan::TLS::TLS_Exception &e) { + std::cout << e.what() << std::endl; + client.close(); + exit(e.error_code()); + } + } + if (client.timeout_check()) { + std::cout << "Timeout detected\n"; + } + } + ::close(m_sockfd); +} diff --git a/evaluation-libraries/botan/cmake/FindBotan2.cmake b/evaluation-libraries/botan/cmake/FindBotan2.cmake new file mode 100644 index 0000000..2503f17 --- /dev/null +++ b/evaluation-libraries/botan/cmake/FindBotan2.cmake @@ -0,0 +1,130 @@ +# Copyright (c) 2018-2020 Ribose Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS +# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +#.rst: +# FindBotan2 +# ----------- +# +# Find the botan-2 library. +# +# IMPORTED Targets +# ^^^^^^^^^^^^^^^^ +# +# This module defines :prop_tgt:`IMPORTED` targets: +# +# ``Botan2::Botan2`` +# The botan-2 library, if found. +# +# Result variables +# ^^^^^^^^^^^^^^^^ +# +# This module defines the following variables: +# +# :: +# +# BOTAN2_FOUND - true if the headers and library were found +# BOTAN2_INCLUDE_DIRS - where to find headers +# BOTAN2_LIBRARIES - list of libraries to link +# BOTAN2_VERSION - library version that was found, if any + +# use pkg-config to get the directories and then use these values +# in the find_path() and find_library() calls +find_package(PkgConfig QUIET) +pkg_check_modules(PC_BOTAN2 QUIET botan-2) + +# find the headers +find_path(BOTAN2_INCLUDE_DIR + NAMES botan/version.h + HINTS + ${PC_BOTAN2_INCLUDEDIR} + ${PC_BOTAN2_INCLUDE_DIRS} + PATH_SUFFIXES botan-2 +) + +# find the library +if(MSVC) + find_library(BOTAN2_LIBRARY + NAMES botan + HINTS + ${PC_BOTAN2_LIBDIR} + ${PC_BOTAN2_LIBRARY_DIRS} + ) +else() + find_library(BOTAN2_LIBRARY + NAMES botan-2 libbotan-2 + HINTS + ${PC_BOTAN2_LIBDIR} + ${PC_BOTAN2_LIBRARY_DIRS} + ) +endif() + +# determine the version +if(PC_BOTAN2_VERSION) + set(BOTAN2_VERSION ${PC_BOTAN2_VERSION}) +elseif(BOTAN2_INCLUDE_DIR AND EXISTS "${BOTAN2_INCLUDE_DIR}/botan/build.h") + file(STRINGS "${BOTAN2_INCLUDE_DIR}/botan/build.h" botan2_version_str + REGEX "^#define[\t ]+(BOTAN_VERSION_[A-Z]+)[\t ]+[0-9]+") + + string(REGEX REPLACE ".*#define[\t ]+BOTAN_VERSION_MAJOR[\t ]+([0-9]+).*" + "\\1" _botan2_version_major "${botan2_version_str}") + string(REGEX REPLACE ".*#define[\t ]+BOTAN_VERSION_MINOR[\t ]+([0-9]+).*" + "\\1" _botan2_version_minor "${botan2_version_str}") + string(REGEX REPLACE ".*#define[\t ]+BOTAN_VERSION_PATCH[\t ]+([0-9]+).*" + "\\1" _botan2_version_patch "${botan2_version_str}") + set(BOTAN2_VERSION "${_botan2_version_major}.${_botan2_version_minor}.${_botan2_version_patch}" + CACHE INTERNAL "The version of Botan which was detected") +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Botan2 + REQUIRED_VARS BOTAN2_LIBRARY BOTAN2_INCLUDE_DIR + VERSION_VAR BOTAN2_VERSION +) + +if (BOTAN2_FOUND) + set(BOTAN2_INCLUDE_DIRS ${BOTAN2_INCLUDE_DIR} ${PC_BOTAN2_INCLUDE_DIRS}) + set(BOTAN2_LIBRARIES ${BOTAN2_LIBRARY}) +endif() + +if (BOTAN2_FOUND AND NOT TARGET Botan2::Botan2) + # create the new library target + add_library(Botan2::Botan2 UNKNOWN IMPORTED) + # set the required include dirs for the target + if (BOTAN2_INCLUDE_DIRS) + set_target_properties(Botan2::Botan2 + PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${BOTAN2_INCLUDE_DIRS}" + ) + endif() + # set the required libraries for the target + if (EXISTS "${BOTAN2_LIBRARY}") + set_target_properties(Botan2::Botan2 + PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION "${BOTAN2_LIBRARY}" + ) + endif() +endif() + +mark_as_advanced(BOTAN2_INCLUDE_DIR BOTAN2_LIBRARY) \ No newline at end of file diff --git a/evaluation-libraries/botan/docker-compose.yml b/evaluation-libraries/botan/docker-compose.yml new file mode 100644 index 0000000..c360815 --- /dev/null +++ b/evaluation-libraries/botan/docker-compose.yml @@ -0,0 +1,21 @@ +version: "3.9" +networks: + default: + name: tls-network + internal: true +services: + botan-server: + image: tls-botan + openssl-server-wrong-cn: + image: tls-openssl + command: [ "/openssl-server", "-k", "/etc/ssl/cert-data/wrong-cn.com.key", "-c" , "/etc/ssl/cert-data/wrong-cn.com-chain.crt"] + openssl-malicious-alpn: + image: tls-openssl + command: [ "/openssl-server", "-m"] + botan-client: + image: tls-botan + command: [ "/client.sh", "/client", "botan-server", "openssl-server-wrong-cn", "openssl-malicious-alpn" ,"1"] + depends_on: + - botan-server + - openssl-server-wrong-cn + - openssl-malicious-alpn \ No newline at end of file diff --git a/evaluation-libraries/botan/run.sh b/evaluation-libraries/botan/run.sh new file mode 100755 index 0000000..8e38d8e --- /dev/null +++ b/evaluation-libraries/botan/run.sh @@ -0,0 +1,2 @@ +./build.sh +docker-compose up --abort-on-container-exit --exit-code-from botan-client --remove-orphans \ No newline at end of file diff --git a/evaluation-libraries/botan/server/CMakeLists.txt b/evaluation-libraries/botan/server/CMakeLists.txt new file mode 100644 index 0000000..bb1145d --- /dev/null +++ b/evaluation-libraries/botan/server/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.0.0) +project(alpaca-botan-server VERSION 0.1.0) + +find_package(Botan2 REQUIRED) +include_directories(SYSTEM ${BOTAN2_INCLUDE_DIR}) + +add_executable(server server.cpp) +target_link_libraries(server pthread ${BOTAN2_LIBRARIES}) +target_compile_options(server PRIVATE -Wall -Wextra) \ No newline at end of file diff --git a/evaluation-libraries/botan/server/server.cpp b/evaluation-libraries/botan/server/server.cpp new file mode 100644 index 0000000..59e4d23 --- /dev/null +++ b/evaluation-libraries/botan/server/server.cpp @@ -0,0 +1,328 @@ +#include "server.h" + +std::string host = "127.0.0.1"; +std::string servername = "tls-server.com"; +std::vector alpn = {"http/1.1"}; +uint16_t port = 4433; + +std::string cert = "/etc/ssl/cert-data/tls-server.com.crt"; +std::string key = "/etc/ssl/cert-data/tls-server.com.pkcs8.key"; +size_t max_clients = 5; +std::string transport = "tcp"; + +bool message_sent = false; +bool message_received = false; + +std::list m_pending_output; +std::string m_line_buf; +socket_type m_socket = -1; +size_t clients_served = 0; + +class Basic_Credentials_Manager : public Botan::Credentials_Manager { + public: + Basic_Credentials_Manager(const std::string &ca_path) { + if (ca_path.empty() == false) { + m_certstores.push_back(std::make_shared(ca_path)); + } + } + + Basic_Credentials_Manager(const std::string &server_crt, const std::string &server_key) { + Certificate_Info cert; + + Botan::DataSource_Stream key_in(server_key); + cert.key = Botan::PKCS8::load_key(key_in); + + Botan::DataSource_Stream in(server_crt); + while (!in.end_of_data()) { + try { + cert.certs.push_back(Botan::X509_Certificate(in)); + } catch (std::exception &) { + } + } + + // TODO: attempt to validate chain ourselves + + m_creds.push_back(cert); + } + + std::vector find_cert_chain( + const std::vector &key_types, + const std::vector &, + const std::string &type, + const std::string &context) override { + return cert_chain(key_types, type, context); + } + + std::vector + trusted_certificate_authorities(const std::string &type, + const std::string & /*hostname*/) override { + std::vector v; + + // don't ask for client certs + if (type == "tls-server.com") { + return v; + } + + for (auto const &cs : m_certstores) { + v.push_back(cs.get()); + } + + return v; + } + + std::vector cert_chain( + const std::vector &algos, + const std::string &type, + const std::string &hostname) override { + BOTAN_UNUSED(type); + + if (servername.compare(hostname) != 0) { + throw Botan::TLS::TLS_Exception(Botan::TLS::Alert::UNRECOGNIZED_NAME, "INVALID SNI"); + } else { + std::cout << "SNI: " << servername << std::endl; + } + + for (auto const &i : m_creds) { + if (std::find(algos.begin(), algos.end(), i.key->algo_name()) == algos.end()) { + continue; + } + + if (hostname != "" && !i.certs[0].matches_dns_name(hostname)) { + continue; + } + + return i.certs; + } + + return std::vector(); + } + + std::vector cert_chain_single_type( + const std::string &cert_key_type, + const std::string &type, + const std::string &context) { + std::vector cert_types; + cert_types.push_back(cert_key_type); + std::cout << context << std::endl; + return find_cert_chain(cert_types, std::vector(), type, context); + } + + Botan::Private_Key *private_key_for(const Botan::X509_Certificate &cert, + const std::string & /*type*/, + const std::string & /*context*/) override { + for (auto const &i : m_creds) { + if (cert == i.certs[0]) { + return i.key.get(); + } + } + + return nullptr; + } + + public: + struct Certificate_Info { + std::vector certs; + std::shared_ptr key; + }; + + std::vector m_creds; + std::vector> m_certstores; +}; + +class Basic_TLS_Policy final : public Botan::TLS::Policy { + public: + bool require_cert_revocation_info() const override { + return false; + } + bool acceptable_protocol_version(Botan::TLS::Protocol_Version version) const override { + if (version == Botan::TLS::Protocol_Version::TLS_V12 && allow_tls12()) + return true; + return false; + } +}; + +class Callbacks : public Botan::TLS::Callbacks { + public: + bool tls_session_established(const Botan::TLS::Session &session) override { + //std::cout << "Handshake complete, " << session.version().to_string() + // << " using " << session.ciphersuite().to_string() << std::endl; + + if (!session.session_id().empty()) { + //std::cout << "Session ID " << Botan::hex_encode(session.session_id()) << std::endl; + } + + if (!session.session_ticket().empty()) { + std::cout << "Session ticket " << Botan::hex_encode(session.session_ticket()) << std::endl; + } + + message_received = false; + message_sent = false; + + return true; + } + + void tls_record_received(uint64_t, const uint8_t input[], size_t input_len) override { + for (size_t i = 0; i != input_len; ++i) { + const char c = static_cast(input[i]); + m_line_buf += c; + if (c == '\n') { + m_pending_output.push_back(m_line_buf); + std::cout << m_line_buf << std::endl; + m_line_buf.clear(); + } + } + } + + void tls_emit_data(const uint8_t buf[], size_t length) override { + ssize_t sent = ::send(m_socket, buf, static_cast(length), MSG_NOSIGNAL); + + if (sent == -1) { + std::cout << "Error writing to socket - " << std::strerror(errno) << std::endl; + } else if (sent != static_cast(length)) { + std::cout << "Packet of length " << length << " truncated to " << sent << std::endl; + } + } + + void tls_alert(Botan::TLS::Alert alert) override { + std::cout << "Alert: " << alert.type_string() << std::endl; + } + + std::string tls_server_choose_app_protocol(const std::vector &client_protos) override { + for (unsigned int i = 0; i < client_protos.size(); i++) { + if (client_protos[i].compare(alpn[0]) == 0) { + return client_protos[i]; + } + } + throw Botan::TLS::TLS_Exception(Botan::TLS::Alert::NO_APPLICATION_PROTOCOL, "INVALID ALPN"); + } + + /*void tls_verify_cert_chain( + const std::vector &cert_chain, + const std::vector> &ocsp_responses, + const std::vector &trusted_roots, + Botan::Usage_Type usage, + const std::string &hostname, + const Botan::TLS::Policy &policy) override + { + std::cout << hostname << "HOSTNAMEASDASDS" << std::endl; + if (cert_chain.empty()) + throw Botan::Invalid_Argument("Certificate chain was empty"); + + Botan::Path_Validation_Restrictions restrictions(policy.require_cert_revocation_info(), + policy.minimum_signature_strength()); + + Botan::Path_Validation_Result result = + x509_path_validate(cert_chain, + restrictions, + trusted_roots, + (usage == Botan::Usage_Type::TLS_SERVER_AUTH ? hostname : ""), + usage, + std::chrono::system_clock::now(), + tls_verify_cert_chain_ocsp_timeout(), + ocsp_responses); + + if (!result.successful_validation()) + { + throw Botan::TLS::TLS_Exception(Botan::TLS::Alert::BAD_CERTIFICATE, + "Certificate validation failure: " + result.result_string()); + } + } */ +}; + +int main(int argc, char **argv) { + /* Get commandline arguments */ + int opt; + while ((opt = getopt(argc, argv, "a:s:c:k:")) != -1) { + switch (opt) { + case 'a': + alpn[0] = optarg; + break; + case 's': + servername = optarg; + break; + case 'c': + cert = optarg; + break; + case 'k': + key = optarg; + break; + default: + fprintf(stderr, "Usage: %s [-a alpn] [-s servername] [-c certfile] [-k keyfile] \n", + argv[0]); + exit(EXIT_FAILURE); + } + } + std::cout << "Parameters alpn=" << alpn[0] << " servername=" << servername << " cert=" << cert << " key=" << key << std::endl; + + Basic_TLS_Policy policy; + + Botan::AutoSeeded_RNG rng; + Callbacks callbacks; + Botan::TLS::Session_Manager_In_Memory session_manager(rng); + + Basic_Credentials_Manager creds(cert, key); + + //std::cout << "Listening for new connections on " << transport << " port " << port << std::endl; + + socket_type server_fd = make_server_socket(port); + + while (true) { + m_socket = ::accept(server_fd, nullptr, nullptr); + + Botan::TLS::Server server( + callbacks, + session_manager, + creds, + policy, + rng, + false); + + try { + while (!server.is_closed()) { + try { + uint8_t buf[4 * 1024] = {0}; + ssize_t got = ::recv(m_socket, Botan::cast_uint8_ptr_to_char(buf), sizeof(buf), 0); + + if (got == -1) { + std::cerr << "Error in socket read - " << std::strerror(errno) << std::endl; + break; + } + + if (got == 0) { + std::cerr << "EOF on socket" << std::endl; + break; + } + + server.received_data(buf, got); + + while (server.is_active() && !m_pending_output.empty()) { + std::string output = m_pending_output.front(); + m_pending_output.pop_front(); + //server.send(output); + + if (!message_sent) { + std::string message = "Hello from Server!"; + server.send(message); + message_sent = true; + } + + if (output == "quit\n") { + server.close(); + } + } + + } catch (std::exception &e) { + std::cerr << "Connection problem: " << e.what() << std::endl; + ::close(m_socket); + m_socket = -1; + } + } + } catch (Botan::Exception &e) { + std::cerr << "Connection failed: " << e.what() << "\n"; + } + + ::close(m_socket); + m_socket = -1; + } + ::close(server_fd); +} \ No newline at end of file diff --git a/evaluation-libraries/botan/server/server.h b/evaluation-libraries/botan/server/server.h new file mode 100644 index 0000000..8397d37 --- /dev/null +++ b/evaluation-libraries/botan/server/server.h @@ -0,0 +1,84 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +typedef int socket_type; +typedef size_t sendrecv_len_type; + +socket_type make_server_socket(uint16_t port) { + socket_type fd = ::socket(PF_INET, SOCK_STREAM, 0); + if (fd == -1) { + std::cerr << "Unable to acquire socket" << std::endl; + } + + sockaddr_in socket_info; + Botan::clear_mem(&socket_info, 1); + socket_info.sin_family = AF_INET; + socket_info.sin_port = htons(port); + + // FIXME: support limiting listeners + socket_info.sin_addr.s_addr = INADDR_ANY; + + if (::bind(fd, reinterpret_cast(&socket_info), sizeof(struct sockaddr)) != 0) { + ::close(fd); + std::cerr << "server bind failed" << std::endl; + } + + if (::listen(fd, 100) != 0) { + ::close(fd); + std::cerr << "listen failed" << std::endl; + } + return fd; +} + +/* class Basic_TLS_Policy final : public Botan::TLS::Policy +{ +public: + bool require_cert_revocation_info() const override + { + return false; + } + std::vector allowed_ciphers() const override + { + return {"ChaCha20Poly1305", "AES-256/GCM", "AES-128/GCM"}; + } + std::vector allowed_signature_hashes() const override + { + return {"SHA-512", "SHA-384"}; + } + std::vector allowed_macs() const override + { + return {"AEAD"}; + } + std::vector allowed_key_exchange_methods() const override + { + return {"CECPQ1", "ECDH"}; + } + bool allow_tls10() const override { return false; } + bool allow_tls11() const override { return false; } + bool allow_tls12() const override { return true; } +}; */ diff --git a/evaluation-libraries/build-everything.sh b/evaluation-libraries/build-everything.sh new file mode 100755 index 0000000..dd0dfc8 --- /dev/null +++ b/evaluation-libraries/build-everything.sh @@ -0,0 +1,5 @@ +#!/bin/bash +for library in baseimage openssl bearssl botan java gnutls golang mbedtls wolfssl rustls; do + (cd "$library" + ./build.sh); +done diff --git a/evaluation-libraries/gnutls/CMakeLists.txt b/evaluation-libraries/gnutls/CMakeLists.txt new file mode 100644 index 0000000..3ce00f0 --- /dev/null +++ b/evaluation-libraries/gnutls/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.0.0) +project(alpaca-gnutls VERSION 0.1.0) + + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) +include(CMakeToolsHelpers OPTIONAL) +include(FeatureSummary) + +include(CheckCCompilerFlag) +include(CheckCXXCompilerFlag) +include(CheckCXXSourceCompiles) + + + +add_subdirectory(client) +add_subdirectory(server) diff --git a/evaluation-libraries/gnutls/Dockerfile b/evaluation-libraries/gnutls/Dockerfile new file mode 100644 index 0000000..5a4aa36 --- /dev/null +++ b/evaluation-libraries/gnutls/Dockerfile @@ -0,0 +1,38 @@ +# syntax=docker/dockerfile:1 +FROM tls-baseimage as tls-gnutls +ARG VERSION=3.7.2 +# RUN git clone --depth=1 -b ${VERSION} https://gitlab.com/gnutls/gnutls.git +# RUN apk add guile-dev +# ENV PKG_CONFIG_PATH=/build/lib/pkgconfig/ +# # RUN apk add flex gmp-dev libunistring-dev libffi-dev gc-dev +# # RUN git clone https://github.com/cky/guile.git +# # WORKDIR /src/guile +# # RUN ./autogen.sh +# # RUN ./configure +# # RUN make + +# RUN wget https://ftp.gnu.org/gnu/autogen/rel5.18.12/autogen-5.18.12.tar.gz +# RUN tar -xzf autogen-5.18.12.tar.gz +# WORKDIR /src/autogen-5.18.12/ +# RUN ./configure +# RUN make && make install + +# WORKDIR /src/gnutls +# RUN git submodule update --init --no-fetch +# RUN ./bootstrap +# RUN ./configure --with-included-libtasn1 --with-included-unistring --disable-maintainer-mode --disable-doc --disable-full-test-suite --disable-cxx --disable-padlock --without-p11-kit --without-tpm +# RUN make + +RUN apk add --no-cache gnutls-dev + +ADD cmake /build/cmake +ADD server /build/server +ADD client /build/client +ADD CMakeLists.txt /build/CMakeLists.txt +WORKDIR /build +RUN cmake . .. && make +RUN mv /build/server/server / +RUN mv /build/client/client / +COPY --from=tls-openssl /openssl-client /openssl-client +WORKDIR / +CMD ["/server"] diff --git a/evaluation-libraries/gnutls/LICENSE b/evaluation-libraries/gnutls/LICENSE new file mode 100644 index 0000000..efe80f5 --- /dev/null +++ b/evaluation-libraries/gnutls/LICENSE @@ -0,0 +1,23 @@ +LICENSING +========= + +Since GnuTLS version 3.1.10, the core library is released under +the GNU Lesser General Public License (LGPL) version 2.1 or later +(see doc/COPYING.LESSER for the license terms). + +The GNU LGPL applies to the main GnuTLS library, while the +included applications as well as gnutls-openssl +library are under the GNU GPL version 3. The gnutls library is +located in the lib/ and libdane/ directories, while the applications +in src/ and, the gnutls-openssl library is at extra/. + +The documentation in doc/ is under the GNU FDL license 1.3. + + +Note, however, that the nettle and the gmp libraries which are +GnuTLS dependencies, they are distributed under a LGPLv3+ or GPLv2+ dual +license. As such binaries linking to them need to adhere to either LGPLv3+ +or the GPLv2+ license. + +For any copyright year range specified as YYYY-ZZZZ in this package +note that the range specifies every single year in that closed interval. \ No newline at end of file diff --git a/evaluation-libraries/gnutls/build.sh b/evaluation-libraries/gnutls/build.sh new file mode 100755 index 0000000..3b4ad38 --- /dev/null +++ b/evaluation-libraries/gnutls/build.sh @@ -0,0 +1 @@ +docker build . -t tls-gnutls diff --git a/evaluation-libraries/gnutls/client/CMakeLists.txt b/evaluation-libraries/gnutls/client/CMakeLists.txt new file mode 100644 index 0000000..6adf40d --- /dev/null +++ b/evaluation-libraries/gnutls/client/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.0.0) +project(alpaca-gnutls-client VERSION 0.1.0) + +find_package(GnuTLS REQUIRED) +include_directories(SYSTEM ${GNUTLS_INCLUDE_DIR}) + +add_executable(client client.c) +target_link_libraries(client ${GNUTLS_LIBRARIES}) +target_compile_options(client PRIVATE -Wall -Wextra) \ No newline at end of file diff --git a/evaluation-libraries/gnutls/client/client.c b/evaluation-libraries/gnutls/client/client.c new file mode 100644 index 0000000..89516e6 --- /dev/null +++ b/evaluation-libraries/gnutls/client/client.c @@ -0,0 +1,182 @@ +/* This example code is placed in the public domain. */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include "examples.h" +#include "tcp.c" + +/* A very basic TLS client, with X.509 authentication and server certificate + * verification. Note that error recovery is minimal for simplicity. + */ + +#define CHECK(x) assert((x) >= 0) +#define LOOP_CHECK(rval, cmd) \ + do { \ + rval = cmd; \ + } while (rval == GNUTLS_E_AGAIN || rval == GNUTLS_E_INTERRUPTED); \ + assert(rval >= 0) + +#define MAX_BUF 1024 +#define MSG "Hello from Client!" + +const char *host = "localhost"; +const char *port = "4433"; +const char *servername = "tls-server.com"; +const char *cert = "/etc/ssl/certs/ca.crt"; +gnutls_datum_t alpn = {.data = (unsigned char *)"http/1.1", .size = 8U}; + +int main(int argc, char *argv[]) { + // Disable buffering on stdout so docker output is shown + setbuf(stdout, NULL); + + /* Get commandline arguments */ + int opt; + while ((opt = getopt(argc, argv, "a:s:c:h:p")) != -1) { + switch (opt) { + case 'a': + alpn.data = (unsigned char *)optarg; + alpn.size = strlen(optarg); + break; + case 's': + servername = optarg; + break; + case 'h': + host = optarg; + break; + case 'p': + port = optarg; + break; + case 'c': + cert = optarg; + break; + default: + fprintf(stderr, "Usage: %s [-a alpn] [-s servername] [-h ip] [-p port] [-c certificate] \n", + argv[0]); + exit(EXIT_FAILURE); + } + } + //std::cout << "Parameters alpn=" << alpn.data << " servername=" << servername << " cert=" << cert << " host=" << host << std::endl; + printf("Parameters alpn=%s servername=%s cert=%s host=%s port=%s \n", alpn.data, servername, cert, host, port); + + int ret, socket, ii; + gnutls_session_t session; + char buffer[MAX_BUF + 1], *desc; + gnutls_datum_t out; + gnutls_certificate_type_t type; + unsigned status; + gnutls_certificate_credentials_t xcred; + + if (gnutls_check_version("3.4.6") == NULL) { + fprintf(stderr, "GnuTLS 3.4.6 or later is required for this example\n"); + exit(1); + } + + /* for backwards compatibility with gnutls < 3.3.0 */ + CHECK(gnutls_global_init()); + + /* X509 stuff */ + CHECK(gnutls_certificate_allocate_credentials(&xcred)); + CHECK(gnutls_certificate_set_x509_trust_file(xcred, cert, GNUTLS_X509_FMT_PEM)); + + /* sets the system trusted CAs for Internet PKI */ + //CHECK(gnutls_certificate_set_x509_system_trust(xcred)); + + /* If client holds a certificate it can be set using the following: + * + gnutls_certificate_set_x509_key_file (xcred, "cert.pem", "key.pem", + GNUTLS_X509_FMT_PEM); + */ + + /* Initialize TLS session */ + CHECK(gnutls_init(&session, GNUTLS_CLIENT)); + + // Set SNI + CHECK(gnutls_server_name_set(session, GNUTLS_NAME_DNS, servername, strlen(servername))); + + // Set strict ALPN + CHECK(gnutls_alpn_set_protocols(session, &alpn, 1, GNUTLS_ALPN_MANDATORY)); + + /* It is recommended to use the default priorities */ + CHECK(gnutls_set_default_priority(session)); + + /* put the x509 credentials to the current session */ + CHECK(gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, xcred)); + + // hostname verification + gnutls_session_set_verify_cert(session, servername, 0); + + // connect to the peer + socket = tcp_connect(host, port); + + gnutls_transport_set_int(session, socket); + gnutls_handshake_set_timeout(session, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT); + + // do the handshake + do { + ret = gnutls_handshake(session); + } while (ret < 0 && gnutls_error_is_fatal(ret) == 0); + if (ret < 0) { + if (ret == GNUTLS_E_CERTIFICATE_VERIFICATION_ERROR) { + /* check certificate verification status */ + type = gnutls_certificate_type_get(session); + status = gnutls_session_get_verify_cert_status(session); + CHECK(gnutls_certificate_verification_status_print(status, type, &out, 0)); + printf("cert verify output: %s\n", out.data); + gnutls_free(out.data); + } + fprintf(stderr, "*** Handshake failed: %s\n", gnutls_strerror(ret)); + goto end; + } else { + desc = gnutls_session_get_desc(session); + //printf("- Session info: %s\n", desc); + gnutls_free(desc); + } + + // Send message to server + LOOP_CHECK(ret, gnutls_record_send(session, MSG, strlen(MSG))); + + // Receive message from server + LOOP_CHECK(ret, gnutls_record_recv(session, buffer, MAX_BUF)); + if (ret == 0) { + printf("- Peer has closed the TLS connection\n"); + goto end; + } else if (ret < 0 && gnutls_error_is_fatal(ret) == 0) { + fprintf(stderr, "*** Warning: %s\n", gnutls_strerror(ret)); + } else if (ret < 0) { + fprintf(stderr, "*** Error: %s\n", gnutls_strerror(ret)); + goto end; + } + + if (ret > 0) { + //printf("- Received %d bytes: ", ret); + //std::cout << ret << std::endl; + for (ii = 0; ii < ret; ii++) { + fputc(buffer[ii], stdout); + } + fputs("\n", stdout); + ret = 0; + } + + CHECK(gnutls_bye(session, GNUTLS_SHUT_RDWR)); + +end: + + tcp_close(socket); + + gnutls_deinit(session); + + gnutls_certificate_free_credentials(xcred); + + gnutls_global_deinit(); + + return ret; +} diff --git a/evaluation-libraries/gnutls/client/examples.h b/evaluation-libraries/gnutls/client/examples.h new file mode 100644 index 0000000..0c451b3 --- /dev/null +++ b/evaluation-libraries/gnutls/client/examples.h @@ -0,0 +1,26 @@ +/* This example code is placed in the public domain. */ +// gnutls/gnutls/doc/examples + +#ifndef EXAMPLES_H +#define EXAMPLES_H + +void check_alert(gnutls_session_t session, int ret); + +int write_pkcs12(const gnutls_datum_t *cert, + const gnutls_datum_t *pkcs8_key, const char *password); + +void verify_certificate(gnutls_session_t session, const char *hostname); + +int print_info(gnutls_session_t session); + +void print_x509_certificate_info(gnutls_session_t session); + +int _ssh_verify_certificate_callback(gnutls_session_t session); + +void verify_certificate_chain(const char *hostname, + const gnutls_datum_t *cert_chain, + int cert_chain_length); + +int verify_certificate_callback(gnutls_session_t session); + +#endif /* EXAMPLES_H */ diff --git a/evaluation-libraries/gnutls/client/tcp.c b/evaluation-libraries/gnutls/client/tcp.c new file mode 100644 index 0000000..07857c6 --- /dev/null +++ b/evaluation-libraries/gnutls/client/tcp.c @@ -0,0 +1,82 @@ +/* This example code is placed in the public domain. */ +// gnutls/gnutls/doc/examples + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* tcp.c */ +void tcp_close(int sd); + +/* Connects to the peer and returns a socket + * descriptor. + */ +int tcp_connect(const char *hostname, const char *port) { + int sockfd, portno; + struct sockaddr_in serv_addr; + struct hostent *server; + portno = atoi(port); + sockfd = socket(AF_INET, SOCK_STREAM, 0); + if (sockfd < 0) + printf("ERROR opening socket"); + server = gethostbyname(hostname); + if (server == NULL) { + fprintf(stderr, "ERROR, no such host\n"); + exit(0); + } + bzero((char *)&serv_addr, sizeof(serv_addr)); + serv_addr.sin_family = AF_INET; + bcopy((char *)server->h_addr, + (char *)&serv_addr.sin_addr.s_addr, + server->h_length); + serv_addr.sin_port = htons(portno); + int err = connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); + if (err < 0) { + fprintf(stderr, "Connect error\n"); + exit(1); + } + return sockfd; + /*int err, sd; + struct sockaddr_in sa; + + struct hostent *host = gethostbyname(hostname); + if (!host) + { + printf("unable to resolve : %s\n", hostname); + return false; + } + + //printf("%s", host->h_addr_list[0]); + + sd = socket(AF_INET, SOCK_STREAM, 0); + + memset(&sa, '\0', sizeof(sa)); + sa.sin_family = AF_INET; + sa.sin_port = htons(atoi(port)); + inet_pton(AF_INET, host->h_addr_list[0], &sa.sin_addr); + + err = connect(sd, (struct sockaddr *) &sa, sizeof(sa)); + if (err < 0) { + fprintf(stderr, "Connect error\n"); + exit(1); + } + + return sd;*/ +} + +/* closes the given socket descriptor. + */ +extern void tcp_close(int sd) { + shutdown(sd, SHUT_RDWR); /* no more receptions */ + close(sd); +} diff --git a/evaluation-libraries/gnutls/cmake/FindGnuTLS.cmake b/evaluation-libraries/gnutls/cmake/FindGnuTLS.cmake new file mode 100644 index 0000000..5a059b7 --- /dev/null +++ b/evaluation-libraries/gnutls/cmake/FindGnuTLS.cmake @@ -0,0 +1,84 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#[=======================================================================[.rst: +FindGnuTLS +---------- + +Find the GNU Transport Layer Security library (gnutls) + +IMPORTED Targets +^^^^^^^^^^^^^^^^ + +.. versionadded:: 3.16 + +This module defines :prop_tgt:`IMPORTED` target ``GnuTLS::GnuTLS``, if +gnutls has been found. + +Result Variables +^^^^^^^^^^^^^^^^ + +``GNUTLS_FOUND`` + System has gnutls +``GNUTLS_INCLUDE_DIR`` + The gnutls include directory +``GNUTLS_LIBRARIES`` + The libraries needed to use gnutls +``GNUTLS_DEFINITIONS`` + Compiler switches required for using gnutls +``GNUTLS_VERSION`` + version of gnutls. +#]=======================================================================] + +# Note that this doesn't try to find the gnutls-extra package. + + +if (GNUTLS_INCLUDE_DIR AND GNUTLS_LIBRARY) + # in cache already + set(gnutls_FIND_QUIETLY TRUE) +endif () + +if (NOT WIN32) + # try using pkg-config to get the directories and then use these values + # in the find_path() and find_library() calls + # also fills in GNUTLS_DEFINITIONS, although that isn't normally useful + find_package(PkgConfig QUIET) + PKG_CHECK_MODULES(PC_GNUTLS QUIET gnutls) + set(GNUTLS_DEFINITIONS ${PC_GNUTLS_CFLAGS_OTHER}) + set(GNUTLS_VERSION ${PC_GNUTLS_VERSION}) + # keep for backward compatibility + set(GNUTLS_VERSION_STRING ${PC_GNUTLS_VERSION}) +endif () + +find_path(GNUTLS_INCLUDE_DIR gnutls/gnutls.h + HINTS + ${PC_GNUTLS_INCLUDEDIR} + ${PC_GNUTLS_INCLUDE_DIRS} + ) + +find_library(GNUTLS_LIBRARY NAMES gnutls libgnutls + HINTS + ${PC_GNUTLS_LIBDIR} + ${PC_GNUTLS_LIBRARY_DIRS} + ) + +mark_as_advanced(GNUTLS_INCLUDE_DIR GNUTLS_LIBRARY) + +include(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs.cmake) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(GnuTLS + REQUIRED_VARS GNUTLS_LIBRARY GNUTLS_INCLUDE_DIR + VERSION_VAR GNUTLS_VERSION_STRING) + +if(GNUTLS_FOUND) + set(GNUTLS_LIBRARIES ${GNUTLS_LIBRARY}) + set(GNUTLS_INCLUDE_DIRS ${GNUTLS_INCLUDE_DIR}) + + if(NOT TARGET GnuTLS::GnuTLS) + add_library(GnuTLS::GnuTLS UNKNOWN IMPORTED) + set_target_properties(GnuTLS::GnuTLS PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${GNUTLS_INCLUDE_DIRS}" + INTERFACE_COMPILE_DEFINITIONS "${GNUTLS_DEFINITIONS}" + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION "${GNUTLS_LIBRARIES}") + endif() +endif() \ No newline at end of file diff --git a/evaluation-libraries/gnutls/cmake/FindPackageHandleStandardArgs.cmake b/evaluation-libraries/gnutls/cmake/FindPackageHandleStandardArgs.cmake new file mode 100644 index 0000000..ecfad0c --- /dev/null +++ b/evaluation-libraries/gnutls/cmake/FindPackageHandleStandardArgs.cmake @@ -0,0 +1,605 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#[=======================================================================[.rst: +FindPackageHandleStandardArgs +----------------------------- + +This module provides functions intended to be used in :ref:`Find Modules` +implementing :command:`find_package()` calls. + +.. command:: find_package_handle_standard_args + + This command handles the ``REQUIRED``, ``QUIET`` and version-related + arguments of :command:`find_package`. It also sets the + ``_FOUND`` variable. The package is considered found if all + variables listed contain valid results, e.g. valid filepaths. + + There are two signatures: + + .. code-block:: cmake + + find_package_handle_standard_args( + (DEFAULT_MSG|) + ... + ) + + find_package_handle_standard_args( + [FOUND_VAR ] + [REQUIRED_VARS ...] + [VERSION_VAR ] + [HANDLE_VERSION_RANGE] + [HANDLE_COMPONENTS] + [CONFIG_MODE] + [NAME_MISMATCHED] + [REASON_FAILURE_MESSAGE ] + [FAIL_MESSAGE ] + ) + + The ``_FOUND`` variable will be set to ``TRUE`` if all + the variables ``...`` are valid and any optional + constraints are satisfied, and ``FALSE`` otherwise. A success or + failure message may be displayed based on the results and on + whether the ``REQUIRED`` and/or ``QUIET`` option was given to + the :command:`find_package` call. + + The options are: + + ``(DEFAULT_MSG|)`` + In the simple signature this specifies the failure message. + Use ``DEFAULT_MSG`` to ask for a default message to be computed + (recommended). Not valid in the full signature. + + ``FOUND_VAR `` + .. deprecated:: 3.3 + + Specifies either ``_FOUND`` or + ``_FOUND`` as the result variable. This exists only + for compatibility with older versions of CMake and is now ignored. + Result variables of both names are always set for compatibility. + + ``REQUIRED_VARS ...`` + Specify the variables which are required for this package. + These may be named in the generated failure message asking the + user to set the missing variable values. Therefore these should + typically be cache entries such as ``FOO_LIBRARY`` and not output + variables like ``FOO_LIBRARIES``. + + .. versionchanged:: 3.18 + If ``HANDLE_COMPONENTS`` is specified, this option can be omitted. + + ``VERSION_VAR `` + Specify the name of a variable that holds the version of the package + that has been found. This version will be checked against the + (potentially) specified required version given to the + :command:`find_package` call, including its ``EXACT`` option. + The default messages include information about the required + version and the version which has been actually found, both + if the version is ok or not. + + ``HANDLE_VERSION_RANGE`` + .. versionadded:: 3.19 + + Enable handling of a version range, if one is specified. Without this + option, a developer warning will be displayed if a version range is + specified. + + ``HANDLE_COMPONENTS`` + Enable handling of package components. In this case, the command + will report which components have been found and which are missing, + and the ``_FOUND`` variable will be set to ``FALSE`` + if any of the required components (i.e. not the ones listed after + the ``OPTIONAL_COMPONENTS`` option of :command:`find_package`) are + missing. + + ``CONFIG_MODE`` + Specify that the calling find module is a wrapper around a + call to ``find_package( NO_MODULE)``. This implies + a ``VERSION_VAR`` value of ``_VERSION``. The command + will automatically check whether the package configuration file + was found. + + ``REASON_FAILURE_MESSAGE `` + .. versionadded:: 3.16 + + Specify a custom message of the reason for the failure which will be + appended to the default generated message. + + ``FAIL_MESSAGE `` + Specify a custom failure message instead of using the default + generated message. Not recommended. + + ``NAME_MISMATCHED`` + .. versionadded:: 3.17 + + Indicate that the ```` does not match + ``${CMAKE_FIND_PACKAGE_NAME}``. This is usually a mistake and raises a + warning, but it may be intentional for usage of the command for components + of a larger package. + +Example for the simple signature: + +.. code-block:: cmake + + find_package_handle_standard_args(LibXml2 DEFAULT_MSG + LIBXML2_LIBRARY LIBXML2_INCLUDE_DIR) + +The ``LibXml2`` package is considered to be found if both +``LIBXML2_LIBRARY`` and ``LIBXML2_INCLUDE_DIR`` are valid. +Then also ``LibXml2_FOUND`` is set to ``TRUE``. If it is not found +and ``REQUIRED`` was used, it fails with a +:command:`message(FATAL_ERROR)`, independent whether ``QUIET`` was +used or not. If it is found, success will be reported, including +the content of the first ````. On repeated CMake runs, +the same message will not be printed again. + +.. note:: + + If ```` does not match ``CMAKE_FIND_PACKAGE_NAME`` for the + calling module, a warning that there is a mismatch is given. The + ``FPHSA_NAME_MISMATCHED`` variable may be set to bypass the warning if using + the old signature and the ``NAME_MISMATCHED`` argument using the new + signature. To avoid forcing the caller to require newer versions of CMake for + usage, the variable's value will be used if defined when the + ``NAME_MISMATCHED`` argument is not passed for the new signature (but using + both is an error).. + +Example for the full signature: + +.. code-block:: cmake + + find_package_handle_standard_args(LibArchive + REQUIRED_VARS LibArchive_LIBRARY LibArchive_INCLUDE_DIR + VERSION_VAR LibArchive_VERSION) + +In this case, the ``LibArchive`` package is considered to be found if +both ``LibArchive_LIBRARY`` and ``LibArchive_INCLUDE_DIR`` are valid. +Also the version of ``LibArchive`` will be checked by using the version +contained in ``LibArchive_VERSION``. Since no ``FAIL_MESSAGE`` is given, +the default messages will be printed. + +Another example for the full signature: + +.. code-block:: cmake + + find_package(Automoc4 QUIET NO_MODULE HINTS /opt/automoc4) + find_package_handle_standard_args(Automoc4 CONFIG_MODE) + +In this case, a ``FindAutmoc4.cmake`` module wraps a call to +``find_package(Automoc4 NO_MODULE)`` and adds an additional search +directory for ``automoc4``. Then the call to +``find_package_handle_standard_args`` produces a proper success/failure +message. + +.. command:: find_package_check_version + + .. versionadded:: 3.19 + + Helper function which can be used to check if a ```` is valid + against version-related arguments of :command:`find_package`. + + .. code-block:: cmake + + find_package_check_version( + [HANDLE_VERSION_RANGE] + [RESULT_MESSAGE_VARIABLE ] + ) + + The ```` will hold a boolean value giving the result of the check. + + The options are: + + ``HANDLE_VERSION_RANGE`` + Enable handling of a version range, if one is specified. Without this + option, a developer warning will be displayed if a version range is + specified. + + ``RESULT_MESSAGE_VARIABLE `` + Specify a variable to get back a message describing the result of the check. + +Example for the usage: + +.. code-block:: cmake + + find_package_check_version(1.2.3 result HANDLE_VERSION_RANGE + RESULT_MESSAGE_VARIABLE reason) + if (result) + message (STATUS "${reason}") + else() + message (FATAL_ERROR "${reason}") + endif() +#]=======================================================================] + +include(${CMAKE_SOURCE_DIR}/cmake/FindPackageMessage.cmake) + + +cmake_policy(PUSH) +# numbers and boolean constants +cmake_policy (SET CMP0012 NEW) +# IN_LIST operator +cmake_policy (SET CMP0057 NEW) + + +# internal helper macro +macro(_FPHSA_FAILURE_MESSAGE _msg) + set (__msg "${_msg}") + if (FPHSA_REASON_FAILURE_MESSAGE) + string(APPEND __msg "\n Reason given by package: ${FPHSA_REASON_FAILURE_MESSAGE}\n") + endif() + if (${_NAME}_FIND_REQUIRED) + message(FATAL_ERROR "${__msg}") + else () + if (NOT ${_NAME}_FIND_QUIETLY) + message(STATUS "${__msg}") + endif () + endif () +endmacro() + + +# internal helper macro to generate the failure message when used in CONFIG_MODE: +macro(_FPHSA_HANDLE_FAILURE_CONFIG_MODE) + # _CONFIG is set, but FOUND is false, this means that some other of the REQUIRED_VARS was not found: + if(${_NAME}_CONFIG) + _FPHSA_FAILURE_MESSAGE("${FPHSA_FAIL_MESSAGE}: missing:${MISSING_VARS} (found ${${_NAME}_CONFIG} ${VERSION_MSG})") + else() + # If _CONSIDERED_CONFIGS is set, the config-file has been found, but no suitable version. + # List them all in the error message: + if(${_NAME}_CONSIDERED_CONFIGS) + set(configsText "") + list(LENGTH ${_NAME}_CONSIDERED_CONFIGS configsCount) + math(EXPR configsCount "${configsCount} - 1") + foreach(currentConfigIndex RANGE ${configsCount}) + list(GET ${_NAME}_CONSIDERED_CONFIGS ${currentConfigIndex} filename) + list(GET ${_NAME}_CONSIDERED_VERSIONS ${currentConfigIndex} version) + string(APPEND configsText "\n ${filename} (version ${version})") + endforeach() + if (${_NAME}_NOT_FOUND_MESSAGE) + if (FPHSA_REASON_FAILURE_MESSAGE) + string(PREPEND FPHSA_REASON_FAILURE_MESSAGE "${${_NAME}_NOT_FOUND_MESSAGE}\n ") + else() + set(FPHSA_REASON_FAILURE_MESSAGE "${${_NAME}_NOT_FOUND_MESSAGE}") + endif() + else() + string(APPEND configsText "\n") + endif() + _FPHSA_FAILURE_MESSAGE("${FPHSA_FAIL_MESSAGE} ${VERSION_MSG}, checked the following files:${configsText}") + + else() + # Simple case: No Config-file was found at all: + _FPHSA_FAILURE_MESSAGE("${FPHSA_FAIL_MESSAGE}: found neither ${_NAME}Config.cmake nor ${_NAME_LOWER}-config.cmake ${VERSION_MSG}") + endif() + endif() +endmacro() + + +function(FIND_PACKAGE_CHECK_VERSION version result) + cmake_parse_arguments (PARSE_ARGV 2 FPCV "HANDLE_VERSION_RANGE;NO_AUTHOR_WARNING_VERSION_RANGE" "RESULT_MESSAGE_VARIABLE" "") + + if (FPCV_UNPARSED_ARGUMENTS) + message (FATAL_ERROR "find_package_check_version(): ${FPCV_UNPARSED_ARGUMENTS}: unexpected arguments") + endif() + if ("RESULT_MESSAGE_VARIABLE" IN_LIST FPCV_KEYWORDS_MISSING_VALUES) + message (FATAL_ERROR "find_package_check_version(): RESULT_MESSAGE_VARIABLE expects an argument") + endif() + + set (${result} FALSE PARENT_SCOPE) + if (FPCV_RESULT_MESSAGE_VARIABLE) + unset (${FPCV_RESULT_MESSAGE_VARIABLE} PARENT_SCOPE) + endif() + + if (_CMAKE_FPHSA_PACKAGE_NAME) + set (package "${_CMAKE_FPHSA_PACKAGE_NAME}") + elseif (CMAKE_FIND_PACKAGE_NAME) + set (package "${CMAKE_FIND_PACKAGE_NAME}") + else() + message (FATAL_ERROR "find_package_check_version(): Cannot be used outside a 'Find Module'") + endif() + + if (NOT FPCV_NO_AUTHOR_WARNING_VERSION_RANGE + AND ${package}_FIND_VERSION_RANGE AND NOT FPCV_HANDLE_VERSION_RANGE) + message(AUTHOR_WARNING + "`find_package()` specify a version range but the option " + "HANDLE_VERSION_RANGE` is not passed to `find_package_check_version()`. " + "Only the lower endpoint of the range will be used.") + endif() + + + set (version_ok FALSE) + unset (version_msg) + + if (FPCV_HANDLE_VERSION_RANGE AND ${package}_FIND_VERSION_RANGE) + if ((${package}_FIND_VERSION_RANGE_MIN STREQUAL "INCLUDE" + AND version VERSION_GREATER_EQUAL ${package}_FIND_VERSION_MIN) + AND ((${package}_FIND_VERSION_RANGE_MAX STREQUAL "INCLUDE" + AND version VERSION_LESS_EQUAL ${package}_FIND_VERSION_MAX) + OR (${package}_FIND_VERSION_RANGE_MAX STREQUAL "EXCLUDE" + AND version VERSION_LESS ${package}_FIND_VERSION_MAX))) + set (version_ok TRUE) + set(version_msg "(found suitable version \"${version}\", required range is \"${${package}_FIND_VERSION_RANGE}\")") + else() + set(version_msg "Found unsuitable version \"${version}\", required range is \"${${package}_FIND_VERSION_RANGE}\"") + endif() + elseif (DEFINED ${package}_FIND_VERSION) + if(${package}_FIND_VERSION_EXACT) # exact version required + # count the dots in the version string + string(REGEX REPLACE "[^.]" "" version_dots "${version}") + # add one dot because there is one dot more than there are components + string(LENGTH "${version_dots}." version_dots) + if (version_dots GREATER ${package}_FIND_VERSION_COUNT) + # Because of the C++ implementation of find_package() ${package}_FIND_VERSION_COUNT + # is at most 4 here. Therefore a simple lookup table is used. + if (${package}_FIND_VERSION_COUNT EQUAL 1) + set(version_regex "[^.]*") + elseif (${package}_FIND_VERSION_COUNT EQUAL 2) + set(version_regex "[^.]*\\.[^.]*") + elseif (${package}_FIND_VERSION_COUNT EQUAL 3) + set(version_regex "[^.]*\\.[^.]*\\.[^.]*") + else() + set(version_regex "[^.]*\\.[^.]*\\.[^.]*\\.[^.]*") + endif() + string(REGEX REPLACE "^(${version_regex})\\..*" "\\1" version_head "${version}") + if (NOT ${package}_FIND_VERSION VERSION_EQUAL version_head) + set(version_msg "Found unsuitable version \"${version}\", but required is exact version \"${${package}_FIND_VERSION}\"") + else () + set(version_ok TRUE) + set(version_msg "(found suitable exact version \"${_FOUND_VERSION}\")") + endif () + else () + if (NOT ${package}_FIND_VERSION VERSION_EQUAL version) + set(version_msg "Found unsuitable version \"${version}\", but required is exact version \"${${package}_FIND_VERSION}\"") + else () + set(version_ok TRUE) + set(version_msg "(found suitable exact version \"${version}\")") + endif () + endif () + else() # minimum version + if (${package}_FIND_VERSION VERSION_GREATER version) + set(version_msg "Found unsuitable version \"${version}\", but required is at least \"${${package}_FIND_VERSION}\"") + else() + set(version_ok TRUE) + set(version_msg "(found suitable version \"${version}\", minimum required is \"${${package}_FIND_VERSION}\")") + endif() + endif() + else () + set(version_ok TRUE) + set(version_msg "(found version \"${version}\")") + endif() + + set (${result} ${version_ok} PARENT_SCOPE) + if (FPCV_RESULT_MESSAGE_VARIABLE) + set (${FPCV_RESULT_MESSAGE_VARIABLE} "${version_msg}" PARENT_SCOPE) + endif() +endfunction() + + +function(FIND_PACKAGE_HANDLE_STANDARD_ARGS _NAME _FIRST_ARG) + + # Set up the arguments for `cmake_parse_arguments`. + set(options CONFIG_MODE HANDLE_COMPONENTS NAME_MISMATCHED HANDLE_VERSION_RANGE) + set(oneValueArgs FAIL_MESSAGE REASON_FAILURE_MESSAGE VERSION_VAR FOUND_VAR) + set(multiValueArgs REQUIRED_VARS) + + # Check whether we are in 'simple' or 'extended' mode: + set(_KEYWORDS_FOR_EXTENDED_MODE ${options} ${oneValueArgs} ${multiValueArgs} ) + list(FIND _KEYWORDS_FOR_EXTENDED_MODE "${_FIRST_ARG}" INDEX) + + unset(FPHSA_NAME_MISMATCHED_override) + if (DEFINED FPHSA_NAME_MISMATCHED) + # If the variable NAME_MISMATCHED variable is set, error if it is passed as + # an argument. The former is for old signatures, the latter is for new + # signatures. + list(FIND ARGN "NAME_MISMATCHED" name_mismatched_idx) + if (NOT name_mismatched_idx EQUAL "-1") + message(FATAL_ERROR + "The `NAME_MISMATCHED` argument may only be specified by the argument or " + "the variable, not both.") + endif () + + # But use the variable if it is not an argument to avoid forcing minimum + # CMake version bumps for calling modules. + set(FPHSA_NAME_MISMATCHED_override "${FPHSA_NAME_MISMATCHED}") + endif () + + if(${INDEX} EQUAL -1) + set(FPHSA_FAIL_MESSAGE ${_FIRST_ARG}) + set(FPHSA_REQUIRED_VARS ${ARGN}) + set(FPHSA_VERSION_VAR) + else() + cmake_parse_arguments(FPHSA "${options}" "${oneValueArgs}" "${multiValueArgs}" ${_FIRST_ARG} ${ARGN}) + + if(FPHSA_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "Unknown keywords given to FIND_PACKAGE_HANDLE_STANDARD_ARGS(): \"${FPHSA_UNPARSED_ARGUMENTS}\"") + endif() + + if(NOT FPHSA_FAIL_MESSAGE) + set(FPHSA_FAIL_MESSAGE "DEFAULT_MSG") + endif() + + # In config-mode, we rely on the variable _CONFIG, which is set by find_package() + # when it successfully found the config-file, including version checking: + if(FPHSA_CONFIG_MODE) + list(INSERT FPHSA_REQUIRED_VARS 0 ${_NAME}_CONFIG) + list(REMOVE_DUPLICATES FPHSA_REQUIRED_VARS) + set(FPHSA_VERSION_VAR ${_NAME}_VERSION) + endif() + + if(NOT FPHSA_REQUIRED_VARS AND NOT FPHSA_HANDLE_COMPONENTS) + message(FATAL_ERROR "No REQUIRED_VARS specified for FIND_PACKAGE_HANDLE_STANDARD_ARGS()") + endif() + endif() + + if (DEFINED FPHSA_NAME_MISMATCHED_override) + set(FPHSA_NAME_MISMATCHED "${FPHSA_NAME_MISMATCHED_override}") + endif () + + if (DEFINED CMAKE_FIND_PACKAGE_NAME + AND NOT FPHSA_NAME_MISMATCHED + AND NOT _NAME STREQUAL CMAKE_FIND_PACKAGE_NAME) + message(AUTHOR_WARNING + "The package name passed to `find_package_handle_standard_args` " + "(${_NAME}) does not match the name of the calling package " + "(${CMAKE_FIND_PACKAGE_NAME}). This can lead to problems in calling " + "code that expects `find_package` result variables (e.g., `_FOUND`) " + "to follow a certain pattern.") + endif () + + if (${_NAME}_FIND_VERSION_RANGE AND NOT FPHSA_HANDLE_VERSION_RANGE) + message(AUTHOR_WARNING + "`find_package()` specify a version range but the module ${_NAME} does " + "not support this capability. Only the lower endpoint of the range " + "will be used.") + endif() + + # to propagate package name to FIND_PACKAGE_CHECK_VERSION + set(_CMAKE_FPHSA_PACKAGE_NAME "${_NAME}") + + # now that we collected all arguments, process them + + if("x${FPHSA_FAIL_MESSAGE}" STREQUAL "xDEFAULT_MSG") + set(FPHSA_FAIL_MESSAGE "Could NOT find ${_NAME}") + endif() + + if (FPHSA_REQUIRED_VARS) + list(GET FPHSA_REQUIRED_VARS 0 _FIRST_REQUIRED_VAR) + endif() + + string(TOUPPER ${_NAME} _NAME_UPPER) + string(TOLOWER ${_NAME} _NAME_LOWER) + + if(FPHSA_FOUND_VAR) + set(_FOUND_VAR_UPPER ${_NAME_UPPER}_FOUND) + set(_FOUND_VAR_MIXED ${_NAME}_FOUND) + if(FPHSA_FOUND_VAR STREQUAL _FOUND_VAR_MIXED OR FPHSA_FOUND_VAR STREQUAL _FOUND_VAR_UPPER) + set(_FOUND_VAR ${FPHSA_FOUND_VAR}) + else() + message(FATAL_ERROR "The argument for FOUND_VAR is \"${FPHSA_FOUND_VAR}\", but only \"${_FOUND_VAR_MIXED}\" and \"${_FOUND_VAR_UPPER}\" are valid names.") + endif() + else() + set(_FOUND_VAR ${_NAME_UPPER}_FOUND) + endif() + + # collect all variables which were not found, so they can be printed, so the + # user knows better what went wrong (#6375) + set(MISSING_VARS "") + set(DETAILS "") + # check if all passed variables are valid + set(FPHSA_FOUND_${_NAME} TRUE) + foreach(_CURRENT_VAR ${FPHSA_REQUIRED_VARS}) + if(NOT ${_CURRENT_VAR}) + set(FPHSA_FOUND_${_NAME} FALSE) + string(APPEND MISSING_VARS " ${_CURRENT_VAR}") + else() + string(APPEND DETAILS "[${${_CURRENT_VAR}}]") + endif() + endforeach() + if(FPHSA_FOUND_${_NAME}) + set(${_NAME}_FOUND TRUE) + set(${_NAME_UPPER}_FOUND TRUE) + else() + set(${_NAME}_FOUND FALSE) + set(${_NAME_UPPER}_FOUND FALSE) + endif() + + # component handling + unset(FOUND_COMPONENTS_MSG) + unset(MISSING_COMPONENTS_MSG) + + if(FPHSA_HANDLE_COMPONENTS) + foreach(comp ${${_NAME}_FIND_COMPONENTS}) + if(${_NAME}_${comp}_FOUND) + + if(NOT DEFINED FOUND_COMPONENTS_MSG) + set(FOUND_COMPONENTS_MSG "found components:") + endif() + string(APPEND FOUND_COMPONENTS_MSG " ${comp}") + + else() + + if(NOT DEFINED MISSING_COMPONENTS_MSG) + set(MISSING_COMPONENTS_MSG "missing components:") + endif() + string(APPEND MISSING_COMPONENTS_MSG " ${comp}") + + if(${_NAME}_FIND_REQUIRED_${comp}) + set(${_NAME}_FOUND FALSE) + string(APPEND MISSING_VARS " ${comp}") + endif() + + endif() + endforeach() + set(COMPONENT_MSG "${FOUND_COMPONENTS_MSG} ${MISSING_COMPONENTS_MSG}") + string(APPEND DETAILS "[c${COMPONENT_MSG}]") + endif() + + # version handling: + set(VERSION_MSG "") + set(VERSION_OK TRUE) + + # check with DEFINED here as the requested or found version may be "0" + if (DEFINED ${_NAME}_FIND_VERSION) + if(DEFINED ${FPHSA_VERSION_VAR}) + set(_FOUND_VERSION ${${FPHSA_VERSION_VAR}}) + if (FPHSA_HANDLE_VERSION_RANGE) + set (FPCV_HANDLE_VERSION_RANGE HANDLE_VERSION_RANGE) + else() + set(FPCV_HANDLE_VERSION_RANGE NO_AUTHOR_WARNING_VERSION_RANGE) + endif() + find_package_check_version ("${_FOUND_VERSION}" VERSION_OK RESULT_MESSAGE_VARIABLE VERSION_MSG + ${FPCV_HANDLE_VERSION_RANGE}) + else() + # if the package was not found, but a version was given, add that to the output: + if(${_NAME}_FIND_VERSION_EXACT) + set(VERSION_MSG "(Required is exact version \"${${_NAME}_FIND_VERSION}\")") + elseif (FPHSA_HANDLE_VERSION_RANGE AND ${_NAME}_FIND_VERSION_RANGE) + set(VERSION_MSG "(Required is version range \"${${_NAME}_FIND_VERSION_RANGE}\")") + else() + set(VERSION_MSG "(Required is at least version \"${${_NAME}_FIND_VERSION}\")") + endif() + endif() + else () + # Check with DEFINED as the found version may be 0. + if(DEFINED ${FPHSA_VERSION_VAR}) + set(VERSION_MSG "(found version \"${${FPHSA_VERSION_VAR}}\")") + endif() + endif () + + if(VERSION_OK) + string(APPEND DETAILS "[v${${FPHSA_VERSION_VAR}}(${${_NAME}_FIND_VERSION})]") + else() + set(${_NAME}_FOUND FALSE) + endif() + + + # print the result: + if (${_NAME}_FOUND) + FIND_PACKAGE_MESSAGE(${_NAME} "Found ${_NAME}: ${${_FIRST_REQUIRED_VAR}} ${VERSION_MSG} ${COMPONENT_MSG}" "${DETAILS}") + else () + + if(FPHSA_CONFIG_MODE) + _FPHSA_HANDLE_FAILURE_CONFIG_MODE() + else() + if(NOT VERSION_OK) + set(RESULT_MSG) + if (_FIRST_REQUIRED_VAR) + string (APPEND RESULT_MSG "found ${${_FIRST_REQUIRED_VAR}}") + endif() + if (COMPONENT_MSG) + if (RESULT_MSG) + string (APPEND RESULT_MSG ", ") + endif() + string (APPEND RESULT_MSG "${FOUND_COMPONENTS_MSG}") + endif() + _FPHSA_FAILURE_MESSAGE("${FPHSA_FAIL_MESSAGE}: ${VERSION_MSG} (${RESULT_MSG})") + else() + _FPHSA_FAILURE_MESSAGE("${FPHSA_FAIL_MESSAGE} (missing:${MISSING_VARS}) ${VERSION_MSG}") + endif() + endif() + + endif () + + set(${_NAME}_FOUND ${${_NAME}_FOUND} PARENT_SCOPE) + set(${_NAME_UPPER}_FOUND ${${_NAME}_FOUND} PARENT_SCOPE) +endfunction() + + +cmake_policy(POP) \ No newline at end of file diff --git a/evaluation-libraries/gnutls/cmake/FindPackageMessage.cmake b/evaluation-libraries/gnutls/cmake/FindPackageMessage.cmake new file mode 100644 index 0000000..e53285b --- /dev/null +++ b/evaluation-libraries/gnutls/cmake/FindPackageMessage.cmake @@ -0,0 +1,48 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#[=======================================================================[.rst: +FindPackageMessage +------------------ + +.. code-block:: cmake + + find_package_message( "message for user" "find result details") + +This function is intended to be used in FindXXX.cmake modules files. +It will print a message once for each unique find result. This is +useful for telling the user where a package was found. The first +argument specifies the name (XXX) of the package. The second argument +specifies the message to display. The third argument lists details +about the find result so that if they change the message will be +displayed again. The macro also obeys the QUIET argument to the +find_package command. + +Example: + +.. code-block:: cmake + + if(X11_FOUND) + find_package_message(X11 "Found X11: ${X11_X11_LIB}" + "[${X11_X11_LIB}][${X11_INCLUDE_DIR}]") + else() + ... + endif() +#]=======================================================================] + +function(find_package_message pkg msg details) + # Avoid printing a message repeatedly for the same find result. + if(NOT ${pkg}_FIND_QUIETLY) + string(REPLACE "\n" "" details "${details}") + set(DETAILS_VAR FIND_PACKAGE_MESSAGE_DETAILS_${pkg}) + if(NOT "${details}" STREQUAL "${${DETAILS_VAR}}") + # The message has not yet been printed. + message(STATUS "${msg}") + + # Save the find details in the cache to avoid printing the same + # message again. + set("${DETAILS_VAR}" "${details}" + CACHE INTERNAL "Details about finding ${pkg}") + endif() + endif() +endfunction() \ No newline at end of file diff --git a/evaluation-libraries/gnutls/docker-compose.yml b/evaluation-libraries/gnutls/docker-compose.yml new file mode 100644 index 0000000..e9b0ecb --- /dev/null +++ b/evaluation-libraries/gnutls/docker-compose.yml @@ -0,0 +1,21 @@ +version: "3.9" +networks: + default: + name: tls-network + internal: true +services: + gnutls-server: + image: tls-gnutls + openssl-server-wrong-cn: + image: tls-openssl + command: [ "/openssl-server", "-k", "/etc/ssl/cert-data/wrong-cn.com.key", "-c" , "/etc/ssl/cert-data/wrong-cn.com-chain.crt"] + openssl-malicious-alpn: + image: tls-openssl + command: [ "/openssl-server", "-m"] + gnutls-client: + image: tls-gnutls + command: [ "./client.sh", "/client", "gnutls-server", "openssl-server-wrong-cn", "openssl-malicious-alpn" ,"1"] + depends_on: + - gnutls-server + - openssl-server-wrong-cn + - openssl-malicious-alpn \ No newline at end of file diff --git a/evaluation-libraries/gnutls/readme.md b/evaluation-libraries/gnutls/readme.md new file mode 100644 index 0000000..25ecc69 --- /dev/null +++ b/evaluation-libraries/gnutls/readme.md @@ -0,0 +1,11 @@ +# gnuttls example with strict sni and strict alpn + +Tested with GnuTLS 3.7.1 + +needs tls-baseimage already in docker + +based on https://gitlab.com/gnutls/gnutls/-/tree/3.7.1/doc/examples + +```bash +./run.sh +``` \ No newline at end of file diff --git a/evaluation-libraries/gnutls/run.sh b/evaluation-libraries/gnutls/run.sh new file mode 100755 index 0000000..4c46cce --- /dev/null +++ b/evaluation-libraries/gnutls/run.sh @@ -0,0 +1,3 @@ +./build.sh +docker-compose up --abort-on-container-exit --exit-code-from gnutls-client --remove-orphans + \ No newline at end of file diff --git a/evaluation-libraries/gnutls/server/CMakeLists.txt b/evaluation-libraries/gnutls/server/CMakeLists.txt new file mode 100644 index 0000000..5907cc7 --- /dev/null +++ b/evaluation-libraries/gnutls/server/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.0.0) +project(alpaca-gnutls-server VERSION 0.1.0) + +find_package(GnuTLS REQUIRED) +include_directories(SYSTEM ${GNUTLS_INCLUDE_DIR}) + +add_executable(server server.c) +target_link_libraries(server ${GNUTLS_LIBRARIES}) +target_compile_options(server PRIVATE -Wall -Wextra) \ No newline at end of file diff --git a/evaluation-libraries/gnutls/server/server.c b/evaluation-libraries/gnutls/server/server.c new file mode 100644 index 0000000..7bee4d0 --- /dev/null +++ b/evaluation-libraries/gnutls/server/server.c @@ -0,0 +1,168 @@ +/* +This example code is placed in the public domain. +gnutls/gnutls/doc/examples/ex-serv-x509.c +*/ + +#include "server.h" + +u_int16_t port = 4433; +const char *servername = "tls-server.com"; +const char *cert = "/etc/ssl/cert-data/tls-server.com-chain.crt"; +const char *key = "/etc/ssl/cert-data/tls-server.com.key"; +gnutls_datum_t alpn = {.data = (unsigned char *)"http/1.1", .size = 8U}; + +#define MSG "Hello from Server!" + +static int ext_hook_func(void *ctx, unsigned tls_id, + const unsigned char *data, unsigned size) { + if (tls_id == 0) { /* 0 = server_name extension */ + const unsigned char *servername_received = data + 5; + + if (strcmp(servername, servername_received) != 0) { + fprintf(stderr, "INVALID SNI: %s\n", servername_received); + return GNUTLS_E_UNRECOGNIZED_NAME; + } + else { + printf("SNI: %s\n", servername_received); + } + } + return 0; +} + +static int handshake_hook_func(gnutls_session_t session, unsigned int htype, + unsigned when, unsigned int incoming, const gnutls_datum_t *msg) { + // call hook for parsing the raw TLS extension + return gnutls_ext_raw_parse(NULL, ext_hook_func, msg, GNUTLS_EXT_RAW_FLAG_TLS_CLIENT_HELLO); +} + +int main(int argc, char **argv) { + // Disable buffering on stdout so docker output is shown + setbuf(stdout, NULL); + + /* Get commandline arguments */ + int opt; + while ((opt = getopt(argc, argv, "a:s:c:k:")) != -1) { + switch (opt) { + case 'a': + alpn.data = (unsigned char *)optarg; + alpn.size = strlen(optarg); + break; + case 's': + servername = optarg; + break; + case 'c': + cert = optarg; + break; + case 'k': + key = optarg; + break; + /*case 'p': + port = optarg; + break;*/ + default: + fprintf(stderr, "Usage: %s [-a alpn] [-s servername] [-c certfile] [-k keyfile] \n", + argv[0]); + exit(EXIT_FAILURE); + } + } + printf("Parameters alpn=%s servername=%s cert=%s key=%s port=%d \n", alpn.data, servername, cert, key, port); + + int sd, ret; + gnutls_certificate_credentials_t x509_cred; + gnutls_priority_t priority_cache; + struct sockaddr_in sa_cli; + socklen_t client_len; + gnutls_session_t session; + char buffer[MAX_BUF + 1]; + + // Initialize gnutls + CHECK(gnutls_global_init()); + CHECK(gnutls_certificate_allocate_credentials(&x509_cred)); + + // Load certificate and key file + CHECK(gnutls_certificate_set_x509_key_file(x509_cred, cert, key, GNUTLS_X509_FMT_PEM)); + + CHECK(gnutls_priority_init(&priority_cache, NULL, NULL)); + gnutls_certificate_set_known_dh_params(x509_cred, GNUTLS_SEC_PARAM_MEDIUM); + + // Create socket and listen on port + int listen_sd = create_socket(port); + listen(listen_sd, 1024); + client_len = sizeof(sa_cli); + for (;;) { + CHECK(gnutls_init(&session, GNUTLS_SERVER)); + CHECK(gnutls_priority_set(session, priority_cache)); + CHECK(gnutls_priority_set_direct(session, "NORMAL:-VERS-SSL3.0:-VERS-TLS1.0:-VERS-TLS1.1:+VERS-TLS1.2", 0)); + CHECK(gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, x509_cred)); + + // Set ALPN + CHECK(gnutls_alpn_set_protocols(session, &alpn, 1, GNUTLS_ALPN_MANDATORY)); + + gnutls_handshake_set_hook_function(session, GNUTLS_HANDSHAKE_CLIENT_HELLO, GNUTLS_HOOK_PRE, handshake_hook_func); + + /* We don't request any certificate from the client. + * If we did we would need to verify it. One way of + * doing that is shown in the "Verifying a certificate" + * example. + */ + gnutls_certificate_server_set_request(session, GNUTLS_CERT_IGNORE); + gnutls_handshake_set_timeout(session, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT); + + sd = accept(listen_sd, (struct sockaddr *)&sa_cli, &client_len); + + gnutls_transport_set_int(session, sd); + + // Handshake + LOOP_CHECK(ret, gnutls_handshake(session)); + if (ret < 0) { + if (ret == GNUTLS_E_NO_APPLICATION_PROTOCOL) { + gnutls_alert_send(session, GNUTLS_AL_FATAL, GNUTLS_A_NO_APPLICATION_PROTOCOL); + } else if (ret == GNUTLS_E_UNRECOGNIZED_NAME) { + gnutls_alert_send(session, GNUTLS_AL_FATAL, GNUTLS_A_UNRECOGNIZED_NAME); + } + + close(sd); + gnutls_deinit(session); + fprintf(stderr, "*** Handshake has failed (%s)\n\n", gnutls_strerror(ret)); + continue; + } + + //for (;;) { + // Receive message from client + LOOP_CHECK(ret, gnutls_record_recv(session, buffer, MAX_BUF)); + + if (ret == 0) { + printf("\n- Peer has closed the GnuTLS connection\n"); + //break; + } else if (ret < 0 && gnutls_error_is_fatal(ret) == 0) { + fprintf(stderr, "*** Warning: %s\n", gnutls_strerror(ret)); + } else if (ret < 0) { + fprintf(stderr, + "\n*** Received corrupted " + "data(%d). Closing the connection.\n\n", + ret); + //break; + } else if (ret > 0) { + printf("%.*s", ret, buffer); + + // Send message to client + CHECK(gnutls_record_send(session, MSG, strlen(MSG))); + LOOP_CHECK(ret, gnutls_bye(session, GNUTLS_SHUT_WR)); + } + //} + printf("\n"); + /* do not wait for the peer to close the connection. */ + //gnutls_bye(session, GNUTLS_SHUT_WR); + + close(sd); + gnutls_deinit(session); + } + close(listen_sd); + + gnutls_certificate_free_credentials(x509_cred); + gnutls_priority_deinit(priority_cache); + + gnutls_global_deinit(); + + return 0; +} diff --git a/evaluation-libraries/gnutls/server/server.h b/evaluation-libraries/gnutls/server/server.h new file mode 100644 index 0000000..85f58b7 --- /dev/null +++ b/evaluation-libraries/gnutls/server/server.h @@ -0,0 +1,41 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CHECK(x) assert((x) >= 0) +#define LOOP_CHECK(rval, cmd) \ + do { \ + rval = cmd; \ + } while (rval == GNUTLS_E_AGAIN || rval == GNUTLS_E_INTERRUPTED) + +#define MAX_BUF 1024 + +int create_socket(u_int16_t port) { + int listen_sd; + struct sockaddr_in sa_serv; + int optval = 1; + /* Socket operations + */ + listen_sd = socket(AF_INET, SOCK_STREAM, 0); + + memset(&sa_serv, '\0', sizeof(sa_serv)); + sa_serv.sin_family = AF_INET; + sa_serv.sin_addr.s_addr = INADDR_ANY; + sa_serv.sin_port = htons(port); + + setsockopt(listen_sd, SOL_SOCKET, SO_REUSEADDR, (void *)&optval, sizeof(int)); + + bind(listen_sd, (struct sockaddr *)&sa_serv, sizeof(sa_serv)); + + return listen_sd; + + printf("Server ready. Listening to port '%d'.\n\n", port); +} \ No newline at end of file diff --git a/evaluation-libraries/golang/Dockerfile b/evaluation-libraries/golang/Dockerfile new file mode 100644 index 0000000..39df3e1 --- /dev/null +++ b/evaluation-libraries/golang/Dockerfile @@ -0,0 +1,14 @@ +# syntax=docker/dockerfile:1 +FROM tls-baseimage +WORKDIR . +ADD server /build/server +ADD client /build/client +WORKDIR /build/server +RUN go build server.go +RUN mv server / +WORKDIR /build/client +RUN go build client.go +RUN mv client / +WORKDIR / +COPY --from=tls-openssl /openssl-client /openssl-client +CMD ["/server"] diff --git a/evaluation-libraries/golang/LICENSE b/evaluation-libraries/golang/LICENSE new file mode 100644 index 0000000..670154e --- /dev/null +++ b/evaluation-libraries/golang/LICENSE @@ -0,0 +1,116 @@ +CC0 1.0 Universal + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator and +subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for the +purpose of contributing to a commons of creative, cultural and scientific +works ("Commons") that the public can reliably and without fear of later +claims of infringement build upon, modify, incorporate in other works, reuse +and redistribute as freely as possible in any form whatsoever and for any +purposes, including without limitation commercial purposes. These owners may +contribute to the Commons to promote the ideal of a free culture and the +further production of creative, cultural and scientific works, or to gain +reputation or greater distribution for their Work in part through the use and +efforts of others. + +For these and/or other purposes and motivations, and without any expectation +of additional consideration or compensation, the person associating CC0 with a +Work (the "Affirmer"), to the extent that he or she is an owner of Copyright +and Related Rights in the Work, voluntarily elects to apply CC0 to the Work +and publicly distribute the Work under its terms, with knowledge of his or her +Copyright and Related Rights in the Work and the meaning and intended legal +effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not limited +to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, communicate, + and translate a Work; + + ii. moral rights retained by the original author(s) and/or performer(s); + + iii. publicity and privacy rights pertaining to a person's image or likeness + depicted in a Work; + + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + + v. rights protecting the extraction, dissemination, use and reuse of data in + a Work; + + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation thereof, + including any amended or successor version of such directive); and + + vii. other similar, equivalent or corresponding rights throughout the world + based on applicable law or treaty, and any national implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention of, +applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and +unconditionally waives, abandons, and surrenders all of Affirmer's Copyright +and Related Rights and associated claims and causes of action, whether now +known or unknown (including existing as well as future claims and causes of +action), in the Work (i) in all territories worldwide, (ii) for the maximum +duration provided by applicable law or treaty (including future time +extensions), (iii) in any current or future medium and for any number of +copies, and (iv) for any purpose whatsoever, including without limitation +commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes +the Waiver for the benefit of each member of the public at large and to the +detriment of Affirmer's heirs and successors, fully intending that such Waiver +shall not be subject to revocation, rescission, cancellation, termination, or +any other legal or equitable action to disrupt the quiet enjoyment of the Work +by the public as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason be +judged legally invalid or ineffective under applicable law, then the Waiver +shall be preserved to the maximum extent permitted taking into account +Affirmer's express Statement of Purpose. In addition, to the extent the Waiver +is so judged Affirmer hereby grants to each affected person a royalty-free, +non transferable, non sublicensable, non exclusive, irrevocable and +unconditional license to exercise Affirmer's Copyright and Related Rights in +the Work (i) in all territories worldwide, (ii) for the maximum duration +provided by applicable law or treaty (including future time extensions), (iii) +in any current or future medium and for any number of copies, and (iv) for any +purpose whatsoever, including without limitation commercial, advertising or +promotional purposes (the "License"). The License shall be deemed effective as +of the date CC0 was applied by Affirmer to the Work. Should any part of the +License for any reason be judged legally invalid or ineffective under +applicable law, such partial invalidity or ineffectiveness shall not +invalidate the remainder of the License, and in such case Affirmer hereby +affirms that he or she will not (i) exercise any of his or her remaining +Copyright and Related Rights in the Work or (ii) assert any associated claims +and causes of action with respect to the Work, in either case contrary to +Affirmer's express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + + b. Affirmer offers the Work as-is and makes no representations or warranties + of any kind concerning the Work, express, implied, statutory or otherwise, + including without limitation warranties of title, merchantability, fitness + for a particular purpose, non infringement, or the absence of latent or + other defects, accuracy, or the present or absence of errors, whether or not + discoverable, all to the greatest extent permissible under applicable law. + + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without limitation + any person's Copyright and Related Rights in the Work. Further, Affirmer + disclaims responsibility for obtaining any necessary consents, permissions + or other rights required for any use of the Work. + + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to this + CC0 or use of the Work. + +For more information, please see + diff --git a/evaluation-libraries/golang/README.md b/evaluation-libraries/golang/README.md new file mode 100644 index 0000000..95d1d87 --- /dev/null +++ b/evaluation-libraries/golang/README.md @@ -0,0 +1,12 @@ +# golang tls example with strict sni and strict alpn + +Tested with golang 1.16.7 + +Based on https://github.com/denji/golang-tls + +needs tls-baseimage already in docker + + +```bash +./run.sh +``` \ No newline at end of file diff --git a/evaluation-libraries/golang/build.sh b/evaluation-libraries/golang/build.sh new file mode 100755 index 0000000..6892085 --- /dev/null +++ b/evaluation-libraries/golang/build.sh @@ -0,0 +1 @@ +docker build -t tls-golang . \ No newline at end of file diff --git a/evaluation-libraries/golang/client/client.go b/evaluation-libraries/golang/client/client.go new file mode 100755 index 0000000..27cf7d5 --- /dev/null +++ b/evaluation-libraries/golang/client/client.go @@ -0,0 +1,93 @@ +package main + +import ( + "crypto/tls" + "crypto/x509" + "errors" + "flag" + "io/ioutil" + "log" + "os" + "runtime" + "strings" +) + +var servername string +var certificate string +var host string = "127.0.0.1" +var port string = "4433" +var alpn = []string{""} + +func main() { + log.SetFlags(log.LstdFlags) + + // Get commandline arguments + flag.StringVar(&servername, "s", "tls-server.com", "servername for SNI") + flag.StringVar(&alpn[0], "a", "http/1.1", "ALPN") + flag.StringVar(&certificate, "c", "/etc/ssl/certs/ca.crt", "certicate") + flag.StringVar(&host, "h", "127.0.0.1", "host") + flag.StringVar(&port, "p", "4433", "port") + flag.Parse() + println("Parameters servername=" + servername + " alpn=" + alpn[0] + " cert=" + certificate + " host=" + host + " port=" + port) + + certs := x509.NewCertPool() + + // Read Certificate + pemData, err := ioutil.ReadFile(certificate) + if err != nil { + log.Println(err) + os.Exit(-1) + } + certs.AppendCertsFromPEM(pemData) + + // Setup TLS config + conf := &tls.Config{ + RootCAs: certs, + NextProtos: alpn, + ServerName: servername, + } + if runtime.Version() < "go1.17" { + println("Strict ALPN not implemented in go version. Overriding VerifyConnection") + conf.VerifyConnection = func(cs tls.ConnectionState) error { + if cs.NegotiatedProtocol == "" { + return errors.New("INVALID ALPN") + } else { + log.Println("ALPN:", cs.NegotiatedProtocol) + return nil + } + } + } + + // Connect to host + conn, err := tls.Dial("tcp", host+":"+port, conf) + if err != nil { + log.Println(err) + if strings.Contains(err.Error(), "server selected unadvertised ALPN protocol") { + os.Exit(120) + } else if strings.Contains(err.Error(), "x509: certificate is valid for") { + os.Exit(42) + } + os.Exit(1) + } + + // Send message to server + n, err := conn.Write([]byte("Hello from Client!\n")) + if err != nil { + log.Println(n, err) + os.Exit(2) + return + } + + // Receive message from server + buf := make([]byte, 100) + n, err = conn.Read(buf) + if err != nil { + log.Println(n, err) + os.Exit(3) + return + } + print(string(buf[:n])) + + defer conn.Close() + os.Exit(0) +} diff --git a/evaluation-libraries/golang/client/go.mod b/evaluation-libraries/golang/client/go.mod new file mode 100644 index 0000000..0ef1b48 --- /dev/null +++ b/evaluation-libraries/golang/client/go.mod @@ -0,0 +1,3 @@ +module client + +go 1.17 diff --git a/evaluation-libraries/golang/docker-compose.yml b/evaluation-libraries/golang/docker-compose.yml new file mode 100644 index 0000000..02b031a --- /dev/null +++ b/evaluation-libraries/golang/docker-compose.yml @@ -0,0 +1,21 @@ +version: "3" +networks: + default: + name: tls-network + internal: true +services: + golang-server: + image: tls-golang + openssl-server-wrong-cn: + image: tls-openssl + command: [ "/openssl-server", "-k", "/etc/ssl/cert-data/wrong-cn.com.key", "-c" , "/etc/ssl/cert-data/wrong-cn.com-chain.crt"] + openssl-malicious-alpn: + image: tls-openssl + command: [ "/openssl-server", "-m"] + golang-client: + image: tls-golang + depends_on: + - golang-server + - openssl-server-wrong-cn + - openssl-malicious-alpn + command: [ "/client.sh", "/client", "golang-server", "openssl-server-wrong-cn", "openssl-malicious-alpn" ,"1"] \ No newline at end of file diff --git a/evaluation-libraries/golang/run.sh b/evaluation-libraries/golang/run.sh new file mode 100755 index 0000000..a5e85f0 --- /dev/null +++ b/evaluation-libraries/golang/run.sh @@ -0,0 +1,2 @@ +./build.sh +docker-compose up --abort-on-container-exit --exit-code-from golang-client --remove-orphans \ No newline at end of file diff --git a/evaluation-libraries/golang/server/go.mod b/evaluation-libraries/golang/server/go.mod new file mode 100644 index 0000000..9a7f23b --- /dev/null +++ b/evaluation-libraries/golang/server/go.mod @@ -0,0 +1,3 @@ +module server + +go 1.17 diff --git a/evaluation-libraries/golang/server/server.go b/evaluation-libraries/golang/server/server.go new file mode 100644 index 0000000..79f12db --- /dev/null +++ b/evaluation-libraries/golang/server/server.go @@ -0,0 +1,112 @@ +package main + +import ( + "bufio" + "crypto/tls" + "errors" + "flag" + "log" + "net" + "runtime" +) + +var servername = "tls-server.com" +var certificate = "certs/tls-server.com-chain.crt" +var privatekey = "certs/tls-server.com.key" +var port = ":4433" +var alpn = []string{"http/1.1"} + +func main() { + log.SetFlags(log.LstdFlags) + + println("Using GO:" + runtime.Version()) + + // Get commandline arguments + flag.StringVar(&servername, "s", "tls-server.com", "servername for SNI") + flag.StringVar(&alpn[0], "a", "http/1.1", "ALPN") + flag.StringVar(&certificate, "c", "/etc/ssl/cert-data/tls-server.com-chain.crt", "certifcate") + flag.StringVar(&privatekey, "k", "/etc/ssl/cert-data/tls-server.com.key", "private key") + flag.Parse() + println("Parameters servername=" + servername + " alpn=" + alpn[0] + " cert=" + certificate + " key=" + privatekey) + + // Load certificate and private key + cer, err := tls.LoadX509KeyPair(certificate, privatekey) + if err != nil { + log.Println(err) + return + } + + conf := &tls.Config{ + Certificates: []tls.Certificate{cer}, + MinVersion: tls.VersionTLS12, + ServerName: servername, + NextProtos: alpn, + } + + if runtime.Version() < "go1.17" { + println("Strict ALPN not implemented in go version. Overriding VerifyConnection") + // Assign a custom function for VerifyConnection + // if no ALPN is negotiated abort the handshake + // it is not possible to access the protocol sent by the client if no ALPN could be negotiated + // so it's not possible to accept the connection if no ALPN is sent + // if the wrong hostname is sent abort the connection + // if no hostname is sent continue + conf.VerifyConnection = func(cs tls.ConnectionState) error { + if cs.NegotiatedProtocol == "" { + return errors.New("INVALID ALPN") + } else if cs.ServerName != servername && len(cs.ServerName) > 0 { + return errors.New("INVALID SNI: " + cs.ServerName) + } else { + log.Println("ALPN:", cs.NegotiatedProtocol) + log.Println("SNI:", cs.ServerName) + return nil + } + } + } else { + conf.VerifyConnection = func(cs tls.ConnectionState) error { + if cs.ServerName != servername && len(cs.ServerName) > 0 { + return errors.New("INVALID SNI: " + cs.ServerName) + } else { + return nil + } + } + } + + // Listen for connections + ln, err := tls.Listen("tcp", port, conf) + if err != nil { + log.Println(err) + return + } + defer ln.Close() + + for { + conn, err := ln.Accept() + if err != nil { + log.Println(err) + continue + } + go handleConnection(conn) + } +} + +func handleConnection(conn net.Conn) { + defer conn.Close() + r := bufio.NewReader(conn) + for { + // Receive message from Client + msg, err := r.ReadString('\n') + if err != nil { + log.Println(err) + return + } + print(msg) + + // Send message to Client + n, err := conn.Write([]byte("Hello from Server!\n")) + if err != nil { + log.Println(n, err) + return + } + } +} diff --git a/evaluation-libraries/java/Dockerfile b/evaluation-libraries/java/Dockerfile new file mode 100644 index 0000000..3343077 --- /dev/null +++ b/evaluation-libraries/java/Dockerfile @@ -0,0 +1,15 @@ +# syntax=docker/dockerfile:1 +FROM tls-baseimage as tls-java +ARG VERSION=4.8.1-stable +RUN apk add openjdk11-jdk +ADD client /client +ADD server /server +WORKDIR /client/bin +RUN javac -cp ../lib/java-getopt-1.0.14.jar -d . ../src/Client.java +RUN jar cmf ../manifest Client.jar Client.class +WORKDIR /server/bin +RUN javac -cp ../lib/java-getopt-1.0.14.jar -d . ../src/Server.java +RUN jar cmf ../manifest Server.jar Server.class +WORKDIR / +COPY --from=tls-openssl /openssl-client /openssl-client +CMD ["java", "-jar", "/server/bin/Server.jar"] diff --git a/evaluation-libraries/java/LICENSE-java-getopt b/evaluation-libraries/java/LICENSE-java-getopt new file mode 100644 index 0000000..23f0d47 --- /dev/null +++ b/evaluation-libraries/java/LICENSE-java-getopt @@ -0,0 +1,481 @@ +GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Library General Public License, applies to some +specially designated Free Software Foundation software, and to any +other libraries whose authors decide to use it. You can use it for +your libraries, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if +you distribute copies of the library, or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link a program with the library, you must provide +complete object files to the recipients so that they can relink them +with the library, after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + Our method of protecting your rights has two steps: (1) copyright +the library, and (2) offer you this license which gives you legal +permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain +that everyone understands that there is no warranty for this free +library. If the library is modified by someone else and passed on, we +want its recipients to know that what they have is not the original +version, so that any problems introduced by others will not reflect on +the original authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that companies distributing free +software will individually obtain patent licenses, thus in effect +transforming the program into proprietary software. To prevent this, +we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary +GNU General Public License, which was designed for utility programs. This +license, the GNU Library General Public License, applies to certain +designated libraries. This license is quite different from the ordinary +one; be sure to read it in full, and don't assume that anything in it is +the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that +they blur the distinction we usually make between modifying or adding to a +program and simply using it. Linking a program with a library, without +changing the library, is in some sense simply using the library, and is +analogous to running a utility program or application program. However, in +a textual and legal sense, the linked executable is a combined work, a +derivative of the original library, and the ordinary General Public License +treats it as such. + + Because of this blurred distinction, using the ordinary General +Public License for libraries did not effectively promote software +sharing, because most developers did not use the libraries. We +concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the +users of those programs of all benefit from the free status of the +libraries themselves. This Library General Public License is intended to +permit developers of non-free programs to use free libraries, while +preserving your freedom as a user of such programs to change the free +libraries that are incorporated in them. (We have not seen how to achieve +this as regards changes in header files, but we have achieved it as regards +changes in the actual functions of the Library.) The hope is that this +will lead to faster development of free libraries. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, while the latter only +works together with the library. + + Note that it is possible for a library to be covered by the ordinary +General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which +contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Library +General Public License (also called "this License"). Each licensee is +addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also compile or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + c) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + d) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the source code distributed need not include anything that is normally +distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Library General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! \ No newline at end of file diff --git a/evaluation-libraries/java/README.md b/evaluation-libraries/java/README.md new file mode 100644 index 0000000..0b924ad --- /dev/null +++ b/evaluation-libraries/java/README.md @@ -0,0 +1,9 @@ +# Java tls example with strict sni and strict alpn + +Tested with openjdk11 and openjdk17 + +uses java-getopt for command-line interface https://directory.fsf.org/wiki/Java-getopt + +```bash +./run.sh +``` \ No newline at end of file diff --git a/evaluation-libraries/java/build.sh b/evaluation-libraries/java/build.sh new file mode 100755 index 0000000..53d183e --- /dev/null +++ b/evaluation-libraries/java/build.sh @@ -0,0 +1 @@ +docker build . -t tls-java -f Dockerfile \ No newline at end of file diff --git a/evaluation-libraries/java/client/README.md b/evaluation-libraries/java/client/README.md new file mode 100644 index 0000000..3edfddc --- /dev/null +++ b/evaluation-libraries/java/client/README.md @@ -0,0 +1 @@ +https://docs.oracle.com/en/java/javase/16/security/java-secure-socket-extension-jsse-reference-guide.html#GUID-59618539-24AD-431E-84E3-585C4C4BF4E5 \ No newline at end of file diff --git a/evaluation-libraries/java/client/lib/java-getopt-1.0.14.jar b/evaluation-libraries/java/client/lib/java-getopt-1.0.14.jar new file mode 100644 index 0000000000000000000000000000000000000000..20c18c98946980604d24ecf8e099d57ef976a0ea GIT binary patch literal 194939 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 z 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 G$o~W4_kdFX literal 0 HcmV?d00001 diff --git a/evaluation-libraries/java/client/manifest b/evaluation-libraries/java/client/manifest new file mode 100644 index 0000000..1091cec --- /dev/null +++ b/evaluation-libraries/java/client/manifest @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: Client +Class-path: ../lib/java-getopt-1.0.14.jar diff --git a/evaluation-libraries/java/client/src/Client.java b/evaluation-libraries/java/client/src/Client.java new file mode 100644 index 0000000..cbfc20a --- /dev/null +++ b/evaluation-libraries/java/client/src/Client.java @@ -0,0 +1,148 @@ +import java.io.*; +import java.security.KeyStore; +import java.security.cert.*; +import java.util.*; +import javax.net.ssl.*; + +import javax.naming.ldap.LdapName; +import javax.naming.ldap.Rdn; +import javax.naming.InvalidNameException; + +import gnu.getopt.Getopt; + +public class Client { + public static String cert = "/etc/ssl/certs/ca.crt"; + public static String[] tls_versions = new String[] { "TLSv1.2", "TLSv1.3" }; + public static String[] alpn = { "http/1.1" }; + public static String servername = "tls-server.com"; + public static String host = "127.0.0.1"; + public static int port = 4433; + + public static void main(String[] argv) throws Exception { + + // Get commandline arguments with GetOpt + Getopt g = new Getopt("Client", argv, "a:s:c:h:p:"); + int opt; + while ((opt = g.getopt()) != -1) { + switch (opt) { + case 'a': + alpn[0] = g.getOptarg(); + break; + case 's': + servername = g.getOptarg(); + break; + case 'h': + host = g.getOptarg(); + break; + case 'c': + cert = g.getOptarg(); + break; + case 'p': + port = Integer.parseInt(g.getOptarg()); + break; + default: + System.out.print("Usage: %s [-a alpn] [-s servername] [-t target] [-c certfile] [-p port]"); + } + } + System.out.println("Parameters servername=" + servername + " alpn=" + alpn[0] + " cert=" + cert + " host=" + + host + " port=" + port); + + // Create custom Keystore + File crtFile = new File(cert); + Certificate certificate = CertificateFactory.getInstance("X.509") + .generateCertificate(new FileInputStream(crtFile)); + + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + keyStore.load(null, null); + keyStore.setCertificateEntry("asd", certificate); + + TrustManagerFactory trustManagerFactory = TrustManagerFactory + .getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(keyStore); + + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, trustManagerFactory.getTrustManagers(), null); + + // Use Custom Keystore + SSLSocketFactory sslsf = (SSLSocketFactory) sslContext.getSocketFactory(); + // SSLSocketFactory sslsf = (SSLSocketFactory) SSLSocketFactory.getDefault(); + SSLSocket sslSocket = (SSLSocket) sslsf.createSocket(host, port); + SSLParameters sslp = sslSocket.getSSLParameters(); + + // set ALPN + sslp.setApplicationProtocols(alpn); + + // set SNI + SNIHostName serverName = new SNIHostName(servername); + List serverNames = new ArrayList<>(1); + serverNames.add(serverName); + sslp.setServerNames(serverNames); + + sslSocket.setSSLParameters(sslp); + sslSocket.setEnabledProtocols(tls_versions); + + // do Handshake + try { + sslSocket.startHandshake(); + } catch (javax.net.ssl.SSLHandshakeException e) { + System.out.println(e); + sslSocket.close(); + System.exit(1); + } catch (javax.net.ssl.SSLProtocolException e) { + System.out.println(e); + sslSocket.close(); + if (e.getMessage().startsWith("Invalid application_layer_protocol_negotiation")) { + System.exit(120); + } + System.exit(1); + } + + // Hostname verification + String peerCNname = getCommonName((X509Certificate) sslSocket.getSession().getPeerCertificates()[0]); + if (!peerCNname.equals(servername)) { + System.out.println("Hostname Verification failed: " + peerCNname); + System.exit(42); + } + System.out.println("Hostname Verification succeded: " + peerCNname); + + // ALPN verification + String ap = sslSocket.getApplicationProtocol(); + if (!ap.equals(alpn[0])) { + System.out.println("INVALID ALPN: \"" + ap + "\""); + System.exit(120); + } + System.out.println("ALPN: \"" + ap + "\""); + + // Send message to server + PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(sslSocket.getOutputStream()))); + out.println("Hello from Client!"); + out.flush(); + + // Receive message from server + BufferedReader in = new BufferedReader(new InputStreamReader(sslSocket.getInputStream())); + String inputLine; + inputLine = in.readLine(); + // while ((inputLine = in.readLine()) != null) + System.out.println(inputLine); + + sslSocket.close(); + } + + public static String getCommonName(X509Certificate cert) { + try { + LdapName ldapName = new LdapName(cert.getSubjectX500Principal().getName()); + /* + * Looking for the "most specific CN" (i.e. the last). + */ + String cn = null; + for (Rdn rdn : ldapName.getRdns()) { + if ("CN".equalsIgnoreCase(rdn.getType())) { + cn = rdn.getValue().toString(); + } + } + return cn; + } catch (InvalidNameException e) { + return null; + } + } +} \ No newline at end of file diff --git a/evaluation-libraries/java/docker-compose.yml b/evaluation-libraries/java/docker-compose.yml new file mode 100644 index 0000000..336a365 --- /dev/null +++ b/evaluation-libraries/java/docker-compose.yml @@ -0,0 +1,21 @@ +version: "3.9" +networks: + default: + name: tls-network + internal: true +services: + java-server: + image: tls-java + openssl-server-wrong-cn: + image: tls-openssl + command: [ "/openssl-server", "-k", "/etc/ssl/cert-data/wrong-cn.com.key", "-c" , "/etc/ssl/cert-data/wrong-cn.com-chain.crt"] + openssl-malicious-alpn: + image: tls-openssl + command: [ "/openssl-server", "-m"] + java-client: + image: tls-java + command: [ "/client.sh", "java -Djavax.net.ssl.trustStore=certs/ca.crt -jar /client/bin/Client.jar", "java-server", "openssl-server-wrong-cn", "openssl-malicious-alpn" ,"1"] + depends_on: + - java-server + - openssl-server-wrong-cn + - openssl-malicious-alpn diff --git a/evaluation-libraries/java/run.sh b/evaluation-libraries/java/run.sh new file mode 100755 index 0000000..bbd5448 --- /dev/null +++ b/evaluation-libraries/java/run.sh @@ -0,0 +1,2 @@ +./build.sh +docker-compose up --abort-on-container-exit --exit-code-from java-client --remove-orphans diff --git a/evaluation-libraries/java/server/README.md b/evaluation-libraries/java/server/README.md new file mode 100644 index 0000000..3edfddc --- /dev/null +++ b/evaluation-libraries/java/server/README.md @@ -0,0 +1 @@ +https://docs.oracle.com/en/java/javase/16/security/java-secure-socket-extension-jsse-reference-guide.html#GUID-59618539-24AD-431E-84E3-585C4C4BF4E5 \ No newline at end of file diff --git a/evaluation-libraries/java/server/lib/java-getopt-1.0.14.jar b/evaluation-libraries/java/server/lib/java-getopt-1.0.14.jar new file mode 100644 index 0000000000000000000000000000000000000000..20c18c98946980604d24ecf8e099d57ef976a0ea GIT binary patch literal 194939 zcmbTcV~}UTw=Gz+Tl0{s5Sqkd>iln1h$0rGKm>8s5(@u;Zh`IXvwrmU- z#XPtufPk7?Z6CknDPayT-y4^~K&}dvr8w_am!Px1Hr9*Um)>&p&oeJm5FLD`I-Yf= zMD0S)i=2Kkoinx3EhmJ%tO9GkM(&O+Bb2T(Ooy@RjEA77k|$8mqKO+8m<(@VTS_3& z5ns1P-b)Vex6C$DnKhsWu?}Ddr->5=k^Qu2B)D{n`{mipSlAqT5PLft7^Wh-_BO2O zNdJUieip*T**ZyfmDIMfe)c6_K8e$WfD?1l1X=}l3$c0PEIyg&-zZCw=~*w6X-CJaY->QoMi)c>2eU{DN(a^V-gIKqsz_= zTr;AXGh-ywlNJz|3aFw;`dFoPZ90aRCdXhaHoJkGZ2uak&`6N04Z_bz)@l?94lg`? znu)U%L^HHBV?S!F8n;5$P!*HF)rvnjyKlG2%CCxR3F7Yb$E5HYZHwmqWC0g*U;Mot zv6hXPkiw!)KweJ|mb7!V z5>A~5dwH()`U!<(Ov;w2yIT=aQ+fn=VN~aSgeG&|v#7Gyo-Z|Vc?qGJBTavh4~<8$;Q>~!l^Cb4x>lyCFp zp3xTu_xzaQ@Ms;#vSuPA%)MZwy-X8k1F+%z0RJZ^@XhSm zT>nc7N)Z2xjsJfrWMcY1CkwNNwpgj-8zcZ{a3^o_@j56h+K^Gff?~(uh?}c6boqRZpn9q`EcatpC|mHRluHc9=~y3QwCudtP0mne>#1 zxBh!GUKbjk+*#29c~r~zk7pngFB32NRfN6evNmnh`~ew{m>m-q(Uy^>(h{4Hx|x0)m+?)t<2Nc2}% z%ZS~{c>$(J5N%~*W6S>{m*vra$kmDKovy>s5)iiY$EV?zyswcEvay=bR@`ZGd=aL@ zv?#M$Azh^+5xZwvvQeiGr5{&aiC}cq2EGfy$V}eI{&#E8?ffZh{NW7-ZbNL|+z!VE zD-N!^TreU6-6VIfoaS??`d(f_&hQ;GZ%54uM!Ae0?G6*UmruMjF}X@?YKm0=E$EbP zb|?M}s%R1Tb>SHs4tW$T!*fXmuZ0K?O`8_&*rd$_%vDQuH^ThV<2}7dp`kY}CG4+& z3r=j^DYI4rd1ONrK+>zDFD`&NL!Rq)%w2M90OioYo=owVwwh+Hc zg-iMmv7WoQ;{Xmgi`+pTBWpagsk7pS+3bS>elH=md2 z$Bp4V-%V8;7HEfDrucuPO^E?V+q}6x-;f8~?iRlYz4EdEa;YO_kcnKXdKi$=^p)tA zH=a8I>sR>E)hMdx4)WPUrxovz&OalmabG+A^k3>yG9yo=#Xa>3vDe5ZC0t~sv-hY+ zJdZkM&WwML-~4X&u*!d&7WF;|hW$8bg4cn!lK?R%k5?CO*`~b(YIebikWxeHLm0kF z9d18Ym$B>o+ecjr=v`dSe_9V331vf%)T1JHSCu*KGE3|U-BtfXCD8v+X`3*c`}rT0 z{Qp0xMEpOi(Z3qU@V_b;gT#LoG6oCh-!@l#mQEYusd<<^g##7z38sk+mzk5%Bs_{X zaYZ#6&*P4r%9?4QC;${=q}_d{sr8(n?wfO1Ao90aStWFJk_gUr{hp8W<1zKPJc0Jl zjVQ|VunBX4O3b?MpS#Nh99aZJ{T)AN_t)2Z=ABl7E;skrL+@KA?l&tIu9XeUJ0*FF zTL;xT*3XiIV7tmP?iEv=3(+ap^P9U5PU_>z(WMCr^|C3Yt)8mbZ%6N+i-Qk8wKxL( z{;-};%{dJhE%SH7;>9AmTAHTGtTUyRq0(Ib68*Wc0$xW)D%9yn6UZK;lo9JMgIf#& zFCKSyN34M%K*iZ|&s zIq3d-5Hwl8L#HGK`>XV(lj`Z?uf=-rrO&sHU%B>7f95nWyjg^~N1?zjDP>r6q-umf zZ0#`9w)rCQRjFn+W7&+k-^v4yp7Kwaz_skI{!Yg>n^fpRHrbO%lckv^x4Kj2y~%St zp#WF*8l?d>w>%B`LpI+2MzH8BquLL!e>p&Lxl*IN?4%BDh9^{;_q32|iGrbuO~2CP zkKTw!VFTMrLga*L<_0Na`gP`ujZBa&QePnxPo$*z^7Xl3*A&5VRl!dZ}dd$Z-&hFpiL}zqMP!(i{Xg|jeUGFW7wa$Q&KuD3_lZB{^T**NHFOLq zm)~bepiIDg2gZze^!Q~3x;sUXh;4qJh`lYWian1>L9hI^O<6y?g2@?Ns-W5^G_Qip zCnhtX4%BY2RgDUL@pmwXCVujBRztNt*7FGY`b7{vT-*P7j=&o^_AXn`9tozTd*w+j z>YS*8?G*oJL^>1o8%Iq%!L#g1d?_Duo7h>~NHH)0T~iAi54Q`^6SV}a-nm^+7sN?I zjkS9J1&0Zqi8&xab+Z@_4NC?^elaF6UQA2Pzr1slQrK1=Ki=p5kONJ1{%>qN)2BUJ z)2*}KVrGax^pMN~;ji1O^95zOp*g(h0I0d-Vt0Y*#VXz}12}|!NeHva)j4P$)85S_ z&@$ID!AWy~T3Oa4AoxNP4SSTZ21L()?;EDGSYXiF+ny=H`~eu6s2ppTv==4iSYyh-7Ju6t)`dv`sqx%Wqm_SG$X{tH^E2^lVkd}_ zL(hZ{+#dJxLyhBRbMT_H^2LDmJdp9VqSvw$Mc}O89}$+s;gr(Qmf3Pn&BYu%TI@qH zv@&5eYHT;|P?8{I;LalV4F{Fl7-Ki}b6r*3RqjHxMq#Kgebgy`oFkVpoL>rj#fgk! z-}~SGC<%dLo<>0UQc7klq@Y7N!!%f~aHq>cyh>`Rb882$@&dt?v593(O1^z9>VcL*=_rU z*QH5WxOjMMFgoryWfjAK{Ug>dJ|2X1LY6YR${7&|d_fZV9%jC5MFff$=1aC_kBTL7 zs+sT-8x=n2Hu%=Za#&5Q=;+t|5ef>6l_&i2%X>Kf4=yS*p23c2$L=eDJQa69=wNI< z(1ku1{!|tIkEzieQi!iesv1gw?Ey`+6ZIrK-UT|hktK95m_`Y+|C&!4q3$Flv*(Q( zq}MN+WAm6lrVFtm9%?NpRW_Z>Dk-PJ7}%uhto;27PI(*lN2^cG;qIH`xbE^$8y6;E z-BvjFb$7Y|EJLwCjXr4@7U$@{RI^@!8VTeHW z2Woa~6QLFF(iP?2o!!=~rg?-?IqyN%6PFGNOL5bi;1GNZI7%0eF_LaF-W3|b_q z(b|V{p}+<$M{fGx!m#3*saB#bLv~BWN|nM5{yw*6+=}_7}wofVJy=OG6@ zyjf$8-BcnCuw7bL+rBukAB@h%#B8agEvEdy0TCwuBvM&8As5FU->!fBf>K%-LI;csAUN{|Z@LvDXPkd1H__gFaVZ9fak+t8mrG=o`SgVj&1t74M zsbL8FoG6Wm{RSMP0S0`GNbF5vcp5R zJ#vmk@LWd}9?nnuZxf9_k(XDoL^#4ighh&`ZZ2G5 z`oLl)Sv&TR#Xy|n7j0eS%$fij=ytK1;;xh70_Xpw$cFF-IVmx~JkXAa%b);g1WOHf zg34j)noEHr>SX=6TQq~)h#5+Z!K$)+(77NFvHKT{8QeT3cT2e`=H zH{S`5T)KTMMCA<%PMpi46N+8^n#}O4jqJNDz#9{ z+fl@6hIL?EM6yUz3K|?AoOzsH?`-AR`ecxWi1T{iLMX^mIUBUPE*-aWpHU8%Z091B zQD%}d3o9&AsR_to&A7mc?6@6m^gj0ieru;t#dJ0kWrHXZn~E#24_Y#v@23c3!tP2u zA?D-(%fU7Vn#9S!)W5hH=Lagsyd3U@$&P1#f`8Rky1#JA7pFhk6?6g*!snk0}Zh=yupd{ zOuIJ=Vxzn}td9iY_54^B267-QdEM!4F?#b?&Vo^xNe7mMDyLcsMcIk$$Z0ZEOH<1; z9pkep8bMgoJ6#PCwlN0|9duf(UbRB-kfGnq)@dUh0e^>hj4muo)dE=WX|uu9#H}g? ziG8-Pk3F!WRmz8$lY!`?D0l;`&$8QDfe>OPH`en+Wq8GMJ5XA}N-5hjOUd;g$wLlf zmgPbsg7A^?V@4P=P!s#XUSVVhQ&A$oCj)7P+1miLTdZ>BAe^qNJ1qB^!F02y;N0q~ zNsoFb)Hv)Q9+x0Y9( zF>!4lmoqG2?rP?RPx$2fhK<%^pqd5!wvOE3vqTA=S4xd;814KzV%4Fg3ctP>RvyXRJwMQ_=Fi5&Hc#6oF@%*ufJL7TuOwQKNGUJ$IF_VR6xd%{=qdl{T! zIYUBQDiE&@!|R}rP*a4rOhlUkPTX79na#yha;X&RojY1zB zY4&}|)OX_m0Eu9>86UuS4L2m7y*;#)Van_Cc{%!WHDfa7ex6h{Mp4FX)j&o3wiWPm z@Ob|Ge3bC=Vr!Op?IzC9HMyJb<$pBNNPJBoQ`T(`D8OxI8yLMvGuc43k7h${`ySI- zC)>J5s01p(gEIAmwpyC0HSU5|4Kazz!$Ag`R~6=&#Ld>r&dU+tg9wP~501VH>|1MF zLoRGHS}>_)SvAUGPHNdIlQNX zCexzg7&bxclzMA=)cj7qc4&_tg`y#A)OFlrQCT!xd)_?VH9%i;x)-ioxJ_7zQ+JwX zK8)`W;-`_k=$P)2G#GlCwGrycJyUs7L${zbC~Y%|`f^dH-!_{;@t@&dQyN8oDvSnoaqQJIxh?ZkD`7;RIIeG?y!+KUI8f^FeL z?$Cl3tT-qw{@?a+r>h{4TAoTPn&PVzkrCk0E6Od)SK0gB448&$v0;g>dUZjbiN z3JI3-^A%z31xsNg&cJ=yQK(CZ6y8vtI^_DHVc$%5@a_Jfz_a2c8peIe62mADS`uO! zRy2phRM0n-2Ij}A_U}vPSnry)xOf$5w_V=W!0)gY?BHMtZ*}NcK1@H~8=-~T8ODIa zGv{)d>R*)PEws&9c?ZHGG(o{nGqUa!j?^9fYZNs7^rgGGT4H0Kc5zYYTF~JuGsCW{ zeC3-*M~ILcxc6ilCCg~t8+4JCUp`_L9?oMd#$Ro=&~CsV=n`GZG$qfuZtwi0;I`Q7 zoVi|jbn;{XE_3r&&tpq5c%TJH_%O)bjsUcYCM8a4 zV#|PPBkHOgqXB5o_159E^(Di`rF8oU?az=87YI+bF~sw}CXvqN(j56& zXL_1yTOx&;d*7fu`wOhX{YqpA{ujjBGrZ5l$rY=J%Yo39t=6&nc106@BiW7viRp_L z-Dmb-Uyvg{L4f6V(K_=_i58IT^3;={PFK=5pKCwAZRyI&^y7dh{51GJD!ya15=Gtb z`6aQF;;uS%RK59QVq6tT9f@d=c_TC4*s4ptvFL7cO+^=t8>tH=^nJGe4JgMdZhTfc)UXFXOUt|9WAx#pb$bWQ-kDW``J5LV)i8>~1Ew?gjqM3tpEk3x;zugs^U z$<6w;ucPLGy9s4y#={}N$B1IUreg$0n%@!)C?`|-lb?#E`>ZoUeMDmqcW(XD8&Kxy z{CYZ#PY|s%q78os{waPz>IG(M=K`Ks51|<$V^>D-`}UWhGN~$xoefN!*4mVA46GRi zh;a+Xd%=iw95IaYX2g!wC!>&ZJ`TU~1t&4`*>z}fzXyN|G=(h_2$&{CTLT;&U@G8K z_(zXnE2PzIjIS4u+RpssSGEHgF3(h|1Pzu9j(2GnGBu*ma|HId^ENV6Y1inC(w(iu zLb4IYN8pNv<(X{`dveHdcMx+59Fb!+r+$=}pB(c5+%sMfyTzY&PKf34hnq}CjFTaV zqF!oVe)m-`>^QD;an3Hk``)q3J$c!BW3NrYdj8=Ie+v5W6K8!kxE*cOH0R~d zrEsVdOL%7Ei7_)`0Ts_sX^q;-lj1m3j1uit|+#k6Yak_sJ^Zdjly#$3SL$WLsIE$b0yi&jm`^7VaC9L4S! z)Dp<`WL_MOW%-alz6)&$VI=A3;&EoowxXQ)!tK9UeO1VdG~nnJ0AHw4;i2*L;d9pX}GOhzv_n76<_!_FC3GjcNr9v*W>Ip_dI{6 z$>djY2HIB*T}nS`qikN^Cdyb|qE&EJ-@-NB06tnI883Pi3BKVTv}NJw8)2h4=Gqd1QE?15JgL|GdYVq-r4qkN4Z}rM7x=q>zGl%+F3$Pg zS#HesSpb^|69vPYFN8#ed+hYUlzH3dXQ>s-ntT@>P?c{&FvLK6F=rp$*8og z#K4#g;{^!yRsv4`ekn;xzAfj+6_!nF9FI$Atli>JxDk-q?KBls8Y`vKpA7oWcWlKmbiHcC>p4D9`p={AnO?AyODTZPzIT8; z%|SX^fnL3<^or{A4rspDHAbkN`17a0K_Bpfw_Nq&cxg!rTa%iVAi-Jhlk+hZcd}Q$ zB{&GCTn`f@q)LtddbKVyCZ5CejjeIP>8p@@z0-sYq0cSI?E?{*tBFPjmh7=$kw;>Ns_4Dq2|LJ|+6xA3~y ze8*(|DWVQ3UJC)y4@3#+6!-j{Sa8t6nhy!d!N=nOb$d*Hvq&1x1X-D$2dYEkdYuqj zQ1!qKixkTm_B>jPjp=b7LsO0k?2+p^3qu8s#P2l?b$wFJ)}`mIRdxpgjZs9MsnNfb zi8k{PZ0jANLyWwGiqnd>kl$e z*=|#Elcqk$`Po6M_Wu1c5_|D88NKxUUKdmTEM~`{R_DWd6|<=I{^ApGxfkS4s{XZt zv0ZSF+_QtGD`X6fICl+$h?0fd7Y${%_4n$wxP&C-5~##KmxOaG0z=GfL5BR!m3qB1 z+dFgq4m|5`>~=H7F(_{B;SrO^>yZ06!bV<#jRUHg*(a%u7w3r+YTEHGaw7?>Q@3$(w1BgySU#sz>qcPQrPPJ% z$&k$nt4e#lIlXtKmasvNT!S3`PMZ?GZVv2E;$-l`W68=36UslO2DK~uk#S~FhMB0a z55E5D0b?qbuxEpcO>X$2IST;Hd5K7KRtwGWyhGS)7{SL=jHnS{5EtziOEFtrkXn?e8GG-eQAa0qgT&=^(wMl z6R`WOO0j^5UfU$=|o^)J?~P5W-q?}2%d&8+hR8bpNU zwQwb&G>blsc5uQf)`wKB9pXHh$`=;H?a{Onn-E@fCmdPJ z!#S7^9Bz32tk2yQL0S7GuUUBC07N_}s`; zt*CAZu5WIPYgcQJCKju$w=dtBr9GyJ>|oC5R@Wg(KiT6~p?@Lsg>XcRg!q<)^+Wg{ z$vH5Ld6kR+2x#8ne|K*vJN{2Q7{>pUoc~*AwlZ`zyz;Sj+E7c~b^D66R{I@=m-P2= z(N?~g>d!uBL@C}H?Zg$pRM|EjjQp>ZKd8UbBKFS<-;CD`xL~B~M#lDiA?`eK7qvqX zw^aF&R|>!*3Z+%--BC~bSPlAIgDQm#q~El|v$UCjtJ}~zAZat@jIQdOC9C>k(X>hCeyym`dBP}l>c$@L6#*kN zwYFPMHz#!hrojJ4P?C3s!c=J+c<}zOEXMVbXTxae?GxKn)gpO4*k@_GoH>F-5MZ_= z%fR7s9?q9>3!tTTYxkmIzCav>GIB=AZd>5&59w|LbkXe;Rg7R4%h5}0<96JT9oo&H zR^Mceh4RZQiyp%^vE3zQdL^nLh|(dQ!#qu(+ui3(ZpP2mPL7d5Zidm1KQ!#(!brE{ zk>aSJ;m9hXZ?wLKW$c1SgRlE*p)smVpr5vECzns4;}iSWUbjF8uix`|%E$Lm2#*V+ zJR`$QZnv+m=bm17xBJ`vQsW<|Iwc)7X*3Uk>XYDK{>OARNj8eqK)w*N>h*AWVYRJzE|$Yyv(@LqzB$d&VB!O-ios5F7RoF67%-)DN81Dosn zLW1@U7EqijSd!r1qJp1H&Ocu=ZW$oF$}f%yvdSEeb<3RnnEH5dmeF=mmEzBDXNzq? zBVl1-MFWfo-DRm>0A0u8~{#J)7XebB&e(Uf|lx2ft z+=hToF?#$t+ZV>KRquQ`zp4>|5>@a6KmFv1K{(xH^@B4PZlAPNCm=^p?c|%sD2Hx6 zI!AZA&zlPqX5Uox3znMcs876Zf$wnS+b$EM)ED3zz2cwlY1bSA`|L@~A^M(ol+>=T zw_D6E`%F+~;G!8IEuc#IC=0CLGhE0xxB3lI&xi?L7*eXp8#O3MT)mh?zG*~)TOkJgr>R~hwSm<9I zM3JhT4h6lf6w%q>!q<3M!Q;M`&aFfB1MZa{PiFxWXiN~Sx0to*pY%D)bmAyr^D!bG z9_IPMj))Rk6!itBFh;JSGT_Xxvdu&a9)fo>oeyG))mRN=qZ5Di(r$ein5sQG7Dynm zp=bLYY!ipLRhdT(oEI*NWlPKDpxRlNpW3OS%+oKY*k$~XjHpKM1CQ#Bz+6G!gGaDI zE-j(-G>*i>m=EQO`o0_mmE{#0X7*%4+{PLU2A+X5rK;}&8B zTaHQ31yxlhc}LzPV~;}w^39)DhGGg8>YFcE>*}!Nlm>UP-v#!=)ISCiyoAq}oU>RG zmxLpA7-*ouGfPYfW@_YT6Q6~SsNLpXBVyjnH;9^!Dyd3nrbv!5eQ-st4ec`^#gmXa zeC%8{s`}rKo;P7xSb*(f$?|sU6D54YEK#kZS+av?fqXMdDK>~<9d(&;0r6*@ufkEH$uFPUSR#4&;)(k>10b%*^f3Lse= zSTs%D*4TvIrVrZBgi)_i53iHn&A9({N3UT92AWzE46{y~PGf>IFCtRUX(b?@6tb5P zdjyJfLe!(wD2-E4Sklj9Sb+@D1Gv~wAw&Gr*zetnEvG4mq}#|@WVl^&Hoiopw!J59 z7m;Y$MF-y~4P18)R?O6Z)_dg~TL@LkajE-Q3m%G(lK#Cr2tPug0RJI_ zIc~kx_zaip=dzE&&Y1ep8!ulx!5vR2ia9d{8-GnKmT(=ES3Rl8&f*@}1;!QCE?`GB z%1|GuYMOe6mVTP73krAhLG~^8DLw-}sH*2dAU=$OETZuK6skC6V@O!%-q3LTWJtD1 z2gz4J`X27&bxW0sBr8XCtF>d042kqCJgsq7A(u+u3l|ACGP8afS&mRT=AqUvSwn*{ zB3D`@AZ(_F9Zx&9^F2~c-I6Ge=s;34U8Cj%+$kV_lNZm8+AQ%TpS}oQ4(`XS!Zl6| zCh*^p7G&b-b1hYYj|@_UJm}?8O{%UC=MhA&MH;lwIB&aBRSE%5%uK69{E2Df$@-|Q zZ{s;Yv^(PhA6?uvR(~hGL^4rUIWzD8aj|Ayeau}irwJTRWuH^D3X|da_vac*zhrlA zBdPtym(>dYnbjn0>f;ItVpmSl-{b}_?@(~K4kc_-qk+U*mN=f6nkkdJU0Ld!=SLM zYdd-O&S%v%^LN=kyCL5AH13Qvf%MNp61Lh|oJrJ%{anKHL$7vQ4ut84odm&2V%%#zB|nV~IPZR4T8{&m)0aY9pI+PPC&0f#@1E4O^aA(hp-w zKA?S`Wk80vjPsc?Hb~>$Y!pZ4cst&bVge6Lu4s!9tVto~B^?25oFx0=uNI zY0BCp&_-oQ7Z58kY*C{OymvU6&t9R>QHd;|;0AZP>-9E5ZM7KC z!{Os7?yz($AQ|kv{yRz7M#*@G++FinDR((t%fGX1LFyu1@G3Ng)_64#ZFukkkmDki zdaqgt!6P=m=Y0Rtz(8*9!=g0ZeLI>?B0w9{H%6*>VgisZQHld-z!I2Jg?k5*!8+Tf zE3KJM*H)tOpCaLsUvcFVu$(HH?tqS!W0BjyZ;xB)Kj)hf(ec*ih!51l57mUV=r`uF za-`QOf_oEbz@(OhFai+0K;zJ%Vc#@QV{DucKFF(7Gvixk5E_ETaFJVNP`_EH%d|_5 z9O2EB=zNp;nY)GtqAnHD6v(Rgbas?D#j8`Sq%kQ+Edr>Ix75^_mtW0Z>xBN=)L zz`S^N;Ed)S(nE)Ckg(VXpm|7j?F#iL`Zs=u5@6lB4>OU>?BRs9YZn}gKlqgLoJI&B zfSs>*L)70a4!RjO9s0QY6-9sqW~=kO@L-nh*1cY#Eu3Y7jgsYH;w2*cfo_v*_gfB0 zAc2tj5tVToIkg50vp2b8=L+$elV@v`|KdVYO_JrU$LA4@{27H-(hL?AY$e2x&Dqb* zGnxFD1vj!AEr;EXbLY}db+zQcXj4c+Quy7LfPyWd)eg9(QB$G+LsUR9&SzeM14Ny)n%rN{-31b5Lb=9jQHkZtjm{;W{a3hw zv?m$<1xX2#@J1&|-HvXxxqYu1P>Yi+4kcJQ7r2m+^0@hy72A@ukFhGVI`gzJS~N1d zaEIU!^#PA4B0@l<0>eZwkuv&Za5b<1xbVtt4q32)10Qhnx-?(j5~!};4mD;8*RU2> zE%+@20KsPeXuGr=mPjF9_#mDP4vX9+Qj?PwRjNFq?~vRmx!=GW5Sf)smc>EYD7cDs zPsX*lfToZ%i0z6KjnvuFSN=Fn^v_hT-5?|8WxI$8TWducx9+|zUa%DFmJsc3Vj9(? z1tt&0U7;Khv#7`d>`YA+@jm@juVtXBvSt_6(P9MlLS+Wa@Q}F!0{gJXk7cLs!PAQ- z)~Iz{dkI5!ghxf#h-k%?Sl~u`o_j@73Sk1#u^IamvXsD~ua4*`)CvI{V|37!V>%&few*|yCte7htp96OAE zQMD)!eJ@OZ8j?`GvBnfqbIz!s)YLUd5#EHdceui!gCHSe@u*lgdJ2`WGhgqm1cRKv zyd{U^L)();jDxLm`+m~j>;RMDLxAx7Y&&;%z@tV4+*l=8QoC=Lw_ z%r_^J*$|9h$dEWO>A*bXvMgAYqYSGo3j%CWH2b*|E6e^hMV28x@X^Yg4&KxRoa_a- zj1t;E8>HycLiGsKMby9tGaJgEuUfA|QN6M#^w7vf;oQXhq!`wu-qKs@za^wh0yDiw z<+T;gq6HSfVjrc*oN0FPn!{Y7P&G_bVhaO@e?&aO8 zRSiku;NHSTtx%xuMjaaV&og$?k|srr5KHs()qW1c>jKip2{-ae&fYKV*EAWxA+pYnBV{?O2giU& zuha^O0vWQaCatykWCzf*7i{4zBZc@m&Y0KOJPjdmmBPQ3oaAgG%`uuwq1g4*JKi~Y zL4jI*P#HpxnO}2vv_p`>JbC|N5K@L`gAzG4z*gKY2uEYxzHpggsC_wZJ$ZMdzPWKV!jFOt;K8*@LISDFPe&I>IaqL*w%ADy$)BJB#tFM&}Fc;(apf>!H? z>z*!E2?nh)lxVLI3kMxiIXbH{Fr6cTTGJXe6AKX?pY)$<&C~c`!yr-rLW+Pw?wR&K z6x`TU!fW!6u(zf%vTLNdBA~&*zFgc?CwvU$WSNHp$8pgpc1=3QK9|<|)*U2e-!0Qf z0uKeofZv^2frXRmq?x7e*Wl91H*VbTQbV3 zu#06wMmIed1!ZVgLWDz=Z&Klc>6(`X+bk<*iLoIzMN?+h+S<5CGm1(_W~Bv47#dV# z%>oQC(bts0$cTAHR1MFHgYm#)p%`z;!e;|T+W3!(cBNchMAF}dkKn3CK#wa#zrN#b z67VRWTjJY0SOi!A4I?S9ToAMJy_nq#*?-NNJett9wDJi~Qb(Z2q6CK)5YH6OITUJPK zi1iH@HYh;By=W-8dyD#K77*zCK3jV`n)UOzyY%bk?{I(CeT_&{ey7yy?Bw-w@O|GD zV8(jy^6sD@$YV^T-4YUb?vql1BWuUor|cZ|6)9#Du|0PdBO`Y)Eg;}XYuKn?`l118 zcv-_ZLzlG3iteQ&7{`Jx`N_`)d94O3z=G!L;uPTd*@Mzh7Ez|SuWXWUg{aL2NBpZ% zgGmg!kzZLL;nnz_wpsDs4o08CSuN}*Wr}yyxKm1PJsG;#bngF`Ev31E9hKE+GWdK{ z3I`Fc`LmKGB_3@R1|)$AuEBiog64p}O+%?NZ(RczM=gDqBh*Y5i7jZDgG^S`)H^9qeG0 z-sw?A_;-}37&bvlopuu|%?pE!eNFk873lkDSF2I7DzSsOqXAFKOu!}I6}5x5ysi~0 zma46fd^C_gg`OQ?5!Fj^Up#GK*dJO?itA`7CC@B8l}b@wj{&4cxR<65>;NTDNfm?A zC@tPpWAU2>S|;K9ElS3b4+8%eD`w&Y!kl2HTNZhBMWlDGZq3j!G_0Wl@_U%CqJKVk zdSkbW-_*0=>QpEhV`m562*qR^aQm4ZZ7(jJ;64LRglQg?9%b(;@M;7B%8ID zl0u87Rdb1&Z1`K6z9mC?WuhnB*Itx<)8%9auxO(ys>ix-m6$5`E+b^mu<^5(XwiV} z(u8cQU)K)aaXq^%6q3!t>OXHoo&TK<@9x2}|HNU7O`Bfkp4Pc8=Bqo>fa!^2_Z+e+ zHJ)zJ3D;v(!d&fefn3h`Dx^y66U7#!9I$QaLVSx=eqvP#jt)(|E6w*oqJX*ecVVPJ zB@&MXn%$N@-wNHe*5TJSV*WL~S7N{USbL8$IbO=f6LP6}HTCp7x0e7mFx03o3_@RLCI^8%%TJ4?zPsRpnR4cFUVTWI6 zcI&sKuCR}1saOkwyLR-B$lz!-uAueo_e={75qV!@7n@_3y7|qWnHMa&M*PMMm(97f z3a~6TGAt+wX(a3j73aGNT|bB`LT+@f`SUufwBv{K2PE?$2uwz)E2&++8Bf4*nWJO! z{lF2r8A%w|2hO=)+|HYhCzx7N|B}pK*5;x}-S_Xrcn@tY;cS0}^s@LivLhi8YIv`TmX{D!rbS}0*6A&VyP0`1*zbcF5-66F8tH(i|pI>x?p%BKTcOh z0yvpx++LLc{kgG{&w*H`KJZmv8X{rk)ofGo1Bn|x%|p1Fc*yEnokN$W#P9bbh&<^e zbRFM=0~);XfgBA=fART#_-wIK1-sznkutu zJ#TS2ZUrAM#u)^_62#Fu&u@>7F6m-|Olh6O`8@D6Ft+1wk>RZ2zRf=om25PD2Uh-* z>b?@+%rOT$%-i1XHL2%6#j5Iy&+{S-L`fvge_5X5T=B5`?v%> zd_-@$OEUA35hzZssqxrBNOa64X;5aE&XC5r9q1Loe7taU5uli|xeTx>UwpP?27(@u zK~mwK!_9Hj9o<`!NfKHmxR7KL4icMX0>}hTlv_qAx?f~SO0bAwP_>wE7Z!{lOu{z+ z?nsZ|_5ThcOvvu2tdfnvWJN@X2qNYzzQ!$^d_Iqldm;pJ*wD28eSZRfimsCmTIbOU zKteS^WXMan5M-vXozJu)IAqlLMWsiW9)ZuX2FM`RU8&v0K(h%%?9G7cVp(h>>4EEj zU1v5~GLTuD5Q&xBL;;2v9NJ~=B!UJWusnsyuaO@elSFg)+KWSq<~zmPD>?dpPgHN27{a7{ijVlQh{Jve z`jplh4rdIJ;L|?~k6|pp4X0_3Hbsb6&$+AL)L%PZ>!T$vzV(#p2i+9>r@;t{M|wzz*I(af zJhhj7+OAJo!aqliQF(g$ z{0C+b<^28q3>}p#KinXuqbcra=#$i=<2-n8Mzbtk(&KBwxU}KHkETt2CHX+St%R{j z2_;ZHy1X6UJ`Uf{m)UOYWFCZ_l5%{|WmQD-vK96wXb`Zxku!7s~IWjN;Q zd{7OvU2S91%6*!dZ#hSUh(xv7F;|q7ZmZ)+9%6eIJ7vRProzg&!ZS3}Uy2@_R^ksE zC`Wj}Ec8jY{E(#_XznzH9D5nT#9`(?@AhfuVtjLIYzRyb!h~1g$a)38viddJ4%v)a zb6W=vX1gV&WT&T~zl^vUmQdlWl$SFjMDz@6B+!Rtzfu+zikkavUDN#nA7$P zM&KLd=+glucOkz!ddp%v$pP>BnJ8UmO4}OYUX0kR$jHhO^eaXkx>S6_3lSgttZd)w zq#x4JEn~#6r@9LUZnVHWhP2W}=fy zcSJn{ubp3^tZI};6gQJZ$LeQc%ea{)Rm&!-g#uT&uT=bjnU0K(70_$~h+8`g4M-)b z6Cm(}z4jyZinqPJJ8vC|hw#;5X8HpbSaN3T&AiR($}1UpyOt48B(^blhl26zd=(MQ za4_6J&=WRD*&4IG{}*TP6eQ@{Z0lB+ZQHhO+tp>;w$)`@UAAr8wryLd=D&8tS#w>i z6Z>3$5#Qa2H*;j>Gh`MaV<>&_E-Mc)9h>v;Zp;{2u;-ZU-Nf-J3Sr0RBuEQO{nC+K z?W`(ViyQ1H6k|iAH=rPpzLr;skz@=k`@AkpvOMd1aevv$R_}tlsOmwW$JCU&5f!T8 z>^D;K7C6+~tG-~-eiajY(SU*NJ4#0hHpu!QvhTi-u@`CY7v8~SjYg-E@&e+Fdi&;8 zE)iwQ)JAghWrh0CC7Fzgxo7i&!DqMr?=QgSzoGA93FNW@IC+9=i!%yy4RQOHY69%J- zL|0H>qg53&j$w;r&Q1h)-m(cFuR1OlMyk4AgJWZXMJix)hmM2fv|#iK$thYsVM$u} z68(0Rg;dYDc-t33()_)IwRP)3o?3i}O@R|moF4*9{#U;@l)FG%{SV7&?ICUS!!9+` zD%C+wi(wl6b`>o^QGHc@L_KPzTEtV*RSiv(Y)LVo0VFjSEqC$!BSt!SM$_^Q_{2mp zcQx}=y0RawpjikYKsv_k(KuT z%)>#YPt%SvE`cp-X8vORII42L)z#Z%Y$gccSp)a4NcT9Lpa6GxH|j8M+F2F0KvQTE zYZwci&Jb(AC;lEQZpv^#H6HA%(oZ^i7j1?Xvo#%-R>cS=^3F*QZ*zhri85mi;PXbw zv6dM5TPI53)W;e<-)Xm%wf0*k8ED&0Ru&m14n`$!YWhmw7Th8odB0SDZ8dG6zP!Yw zmf%Xn9EI!VGvZr4M-q7tf3X;!^UAqVANUAbMMhD44mP7Fv}DDAg#t9XZMwA~deXZR z6V^#an<-a`rB|Ru6Z;LY+RI;!=c7ZLinYu{y>&SuV7`*u3l)sPic>ISPb}x9RrN6H zIhD|v)YsySTdK!)FlIJ<9`5s0h<(P@{gqYlOfwNf08u>8u7La;yoxgZZWpin|h1f?}!&*Yy15$THC6v?QS~bLOztXqH~lLPlA9 zyYTxDQvkuLpwaWRh}mkMh*?h1)QakcxHYGbFh;TFgb?j{(u@3J0~{Zgic%=z+lHks zBhZG}+*8!|y9AdlCkU)*#c4JOK4rL^VdW&E?YlfjW;a&I*n_t%<26{}`Q$R}c@j$_ z^IX{EUE`BI$9~0iEYjg#eL&1a6+4$7v+Rcnl|G*7*>29wwR5yPxDv&bsA}9an!4FL zu1b~CcE|Vhqc8UI@I8JQ{8PPqz6O6ZWUMYC0X?C`Dg63hD59k}+AlqQCad;LYG1t= zZ@$0CZ+;2R4tubm%pN_LVdt&5=*|U=t0c*QV33F^P6nZKgtn{FURZhl*jiAVGs1UK zJSsWpP``bM0C^Cq`Mc`N8RCFmS@IGRLfG(l0@-=PHf+}xl^$3N6%LoCND|$wA#uir zddM$GhCj_}z5Ri@ zdt3BNxz({d+Z3NntykF|qgE$DVXKaC2M*LPcK-JJ3zo_A2Vh@}+nf{|nH`J$%F>nn zW_?TgeBXj+xBI6T%8&e8dG6I>oki;YHe69Bz>8Pc@T`4crW5yY!~j*QJUwr@3V8Ht zoGE85+-kCSK}Ne7mbFkqaUc&@gtn{9savQYSe_?Uy_cPWy`T8N2aXkWKlVo_Mr>25 z-x((Jt#1VmgTME5u?)8+OL%$qRJ9(rYNcQHE+BCj;N+f?w^+#4yoLH<vMs_VFfKMP!3OGUVu6DDMW%c#|my@}0pDh1e!;P_}(VZ7DVx`p!}EUoY{ z%xk#2=Zpbp16CvVKuf+SbKS3fCWAK=k^K6WW4nAC!ou*^zqq7s-?GW;Ttgi)(}cJv z12X~9nHlRw!7wfdq&U@qHOrEuulVVL(KB1jTrTCG7Uh11QF@s+Ua@U_-S zZnV9)bSbLIfGiszj}Y;~5jSd;cXPf)amyo6ly7QZ|xzJMvY5VTTl z6PCR5GM^Ur=XtINTSgT@dyrxH)#~nm01}yl4>c|)aBY{b+o7}3-89c?gEYLjMS$_a zI?;ws-N5r~md5kFnRtRLWg|6M6N`< zmkoU0879UO$p=sPIx*xn18^`=qcCF8WR4vWHlySs^k?Qu!{G#&hgJi!Tt>hF{g|XEkJcP*$ zJmkr0%44?xDQk?U6-;H5FW<^c5eNi&Kgy@E8DHu=AS)hX)+Vb(x*8}*_rHSFjK?hX ztCNb$ugF4nhJkz}raWU$#!Fd+-j^V2q8IY*ulv+{=h&Vi(~hU3kRiV*t#;1j`;_>8 z(>RLiCpu}^gUY{2{a^^sZob|*o+WywvF*!`c?@HJxo@`3v~PCz|4@Fp_zFk{?}%a(2N zRaH$-{4N7-gKM}Bg$^o{a%*{QxC1axyTo*~YHTkQERLW!;xPZd|L3aIvj`(t)lP6` z)pBZ&$Pj0x8(PmczYFO;d4d=FvW_9*)vfybbOepq#%u87OQD1%s0&EP**~jf(d_Ab z5qgraVxGn{soJ?5Rb;cH3b65!Mq9H6a2k3$MsiLMH;iwmbq&9bTnB48)!St|>7Mo= zHBJJZ8^O_ogO}|iQFEb@cFnETCRvmv5{uD7zWF8Oa)IiZHKWMO5~RJ_bZnpi&MDW?-Q`%Oy0V5<4( zxe(6StE(oWVu$W6eP{8xTP>pE>scAEptzq-sLhbbaa(ACc^^$-Y37U-e@NV3rz`Ks{$Vi95+N${ z7NHZNXu6;@<(1pu?_DGwGIp>4AMwmg%JekYvCXwB3Q=c>|O@OU|dk)q8LT{ z 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 G$o~W4_kdFX literal 0 HcmV?d00001 diff --git a/evaluation-libraries/java/server/manifest b/evaluation-libraries/java/server/manifest new file mode 100644 index 0000000..b95175b --- /dev/null +++ b/evaluation-libraries/java/server/manifest @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: Server +Class-path: ../lib/java-getopt-1.0.14.jar diff --git a/evaluation-libraries/java/server/src/Server.java b/evaluation-libraries/java/server/src/Server.java new file mode 100644 index 0000000..a47cd19 --- /dev/null +++ b/evaluation-libraries/java/server/src/Server.java @@ -0,0 +1,115 @@ +import java.io.*; +import java.security.KeyStore; +import java.util.*; +import javax.net.ssl.*; + +import gnu.getopt.Getopt; + +public class Server { + public static String keyFile = "/etc/ssl/cert-data/tls-server.com.p12"; + public static String keyPassword = "123456"; + public static String[] protocols = new String[] { "TLSv1.2", "TLSv1.3" }; + public static String[] alpn = { "http/1.1" }; + public static String servername = "tls-server.com"; + public static int port = 4433; + + public static void main(String[] argv) throws Exception { + + // Get commandline arguments with GetOpt + Getopt g = new Getopt("Server", argv, "a:s:k:p:"); + int opt; + while ((opt = g.getopt()) != -1) { + switch (opt) { + case 'a': + alpn[0] = g.getOptarg(); + break; + case 's': + servername = g.getOptarg(); + break; + case 'k': + keyFile = g.getOptarg(); + break; + case 'p': + port = Integer.parseInt(g.getOptarg()); + break; + default: + System.out.print("Usage: %s [-a alpn] [-s servername] [-t target] [-c certfile] [-p port]"); + } + } + System.out.println( + "Parameters servername=" + servername + " alpn=" + alpn[0] + " key=" + keyFile + " port=" + port); + + SSLContext ctx = SSLContext.getInstance("TLS"); + + // Create Keystore + KeyStore keyKS = KeyStore.getInstance("PKCS12"); + keyKS.load(new FileInputStream(keyFile), keyPassword.toCharArray()); + + // Generate KeyManager + KeyManagerFactory kmf = KeyManagerFactory.getInstance("PKIX"); + kmf.init(keyKS, keyPassword.toCharArray()); + KeyManager[] kms = kmf.getKeyManagers(); + + // Initialize SSLContext using the new KeyManager + ctx.init(kms, null, null); + + // Instead of using SSLServerSocketFactory.getDefault(), + // get a SSLServerSocketFactory based on the SSLContext + SSLServerSocketFactory sslssf = ctx.getServerSocketFactory(); + SSLServerSocket sslServerSocket = (SSLServerSocket) sslssf.createServerSocket(port); + + while (true) { + // Listen for connectionss + SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept(); + SSLParameters sslp = sslSocket.getSSLParameters(); + + // Set SNI hostname, the matcher aborts the connection if the servername is not + // found + SNIMatcher matcher = SNIHostName.createSNIMatcher(servername); + Collection matchers = new ArrayList<>(1); + matchers.add(matcher); + sslp.setSNIMatchers(matchers); + + // Add ALPN to the SSL parameters + // Java will abort the connection if there is a mismatch in the Protocols + sslp.setApplicationProtocols(alpn); + + sslSocket.setSSLParameters(sslp); + sslSocket.setEnabledProtocols(protocols); + + // Do the handshake + try { + sslSocket.startHandshake(); + + String ap = sslSocket.getApplicationProtocol(); + System.out.println("ALPN: \"" + ap + "\""); + + // Send message to client + PrintWriter out = new PrintWriter( + new BufferedWriter(new OutputStreamWriter(sslSocket.getOutputStream()))); + out.println("Hello from Server!"); + out.flush(); + + // Get message from client + BufferedReader in = new BufferedReader(new InputStreamReader(sslSocket.getInputStream())); + String inputLine; + while ((inputLine = in.readLine()) != null) + System.out.println(inputLine); + } catch (javax.net.ssl.SSLHandshakeException e) { + System.out.println(e); + sslSocket.close(); + continue; + } catch (java.net.SocketException e) { + System.out.println(e); + sslSocket.close(); + continue; + } catch (javax.net.ssl.SSLException e) { + System.out.println(e); + sslSocket.close(); + continue; + } + + sslSocket.close(); + } + } +} \ No newline at end of file diff --git a/evaluation-libraries/mbedtls/CMakeLists.txt b/evaluation-libraries/mbedtls/CMakeLists.txt new file mode 100644 index 0000000..52e1aea --- /dev/null +++ b/evaluation-libraries/mbedtls/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.0.0) +project(alpaca-mbedtls VERSION 0.1.0) + +add_subdirectory(client) +add_subdirectory(server) diff --git a/evaluation-libraries/mbedtls/Dockerfile b/evaluation-libraries/mbedtls/Dockerfile new file mode 100644 index 0000000..9cccb28 --- /dev/null +++ b/evaluation-libraries/mbedtls/Dockerfile @@ -0,0 +1,18 @@ +# syntax=docker/dockerfile:1 +FROM tls-baseimage as tls-mbedtls +ARG VERSION=2.18 +WORKDIR /build +RUN git clone --depth=1 --branch=archive/mbedtls-${VERSION} https://github.com/ARMmbed/mbedtls +WORKDIR /build/mbedtls +RUN git submodule update --init --recursive +RUN cmake -DCMAKE_BUILD_TYPE=Debug . && make install +WORKDIR /build +ADD server /build/server +ADD client /build/client +ADD CMakeLists.txt /build/CMakeLists.txt +RUN cmake . .. && make +RUN mv /build/server/server / +RUN mv /build/client/client / +COPY --from=tls-openssl /openssl-client /openssl-client +WORKDIR / +CMD ["/server"] \ No newline at end of file diff --git a/evaluation-libraries/mbedtls/LICENSE b/evaluation-libraries/mbedtls/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/evaluation-libraries/mbedtls/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/evaluation-libraries/mbedtls/README.md b/evaluation-libraries/mbedtls/README.md new file mode 100644 index 0000000..e2d301c --- /dev/null +++ b/evaluation-libraries/mbedtls/README.md @@ -0,0 +1,10 @@ +# mbed tls example with strict sni and strict alpn + +Tested with mbedtls 2.18 + +based on ssl_client1.c and ssl_server.c from mbedtls +https://github.com/ARMmbed/mbedtls/tree/development/programs/ssl + +```bash +./run.sh +``` \ No newline at end of file diff --git a/evaluation-libraries/mbedtls/build.sh b/evaluation-libraries/mbedtls/build.sh new file mode 100755 index 0000000..6f9e554 --- /dev/null +++ b/evaluation-libraries/mbedtls/build.sh @@ -0,0 +1 @@ +docker build --build-arg VERSION=2.18 . -t tls-mbedtls -f Dockerfile diff --git a/evaluation-libraries/mbedtls/client/CMakeLists.txt b/evaluation-libraries/mbedtls/client/CMakeLists.txt new file mode 100644 index 0000000..eb035dd --- /dev/null +++ b/evaluation-libraries/mbedtls/client/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.0.0) +project(client VERSION 0.1.0) + + +add_library(mbedtls STATIC IMPORTED) +set_target_properties(mbedtls PROPERTIES + IMPORTED_LOCATION "${CMAKE_SOURCE_DIR}/mbedtls/library/libmbedtls.a" + INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_SOURCE_DIR}/mbedtls/include" + ) +add_library(mbedx509 STATIC IMPORTED) +set_target_properties(mbedx509 PROPERTIES + IMPORTED_LOCATION "${CMAKE_SOURCE_DIR}/mbedtls/library/libmbedx509.a" + INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_SOURCE_DIR}/mbedtls/include" + ) +add_library(mbedcrypto STATIC IMPORTED) +set_target_properties(mbedcrypto PROPERTIES + IMPORTED_LOCATION "${CMAKE_SOURCE_DIR}/mbedtls/crypto/library/libmbedcrypto.a" + INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_SOURCE_DIR}/mbedtls/crypto/include" + ) + +add_executable(client client.c) +target_link_libraries(client mbedx509 mbedtls mbedcrypto) +target_compile_options(client PRIVATE -Wall -Wextra) diff --git a/evaluation-libraries/mbedtls/client/client.c b/evaluation-libraries/mbedtls/client/client.c new file mode 100644 index 0000000..f87631d --- /dev/null +++ b/evaluation-libraries/mbedtls/client/client.c @@ -0,0 +1,208 @@ +/* + * SSL client demonstration program + * + * Copyright (C) 2006-2015, ARM Limited, All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is part of mbed TLS (https://tls.mbed.org) + */ + +#include +#include +#include +#include + +#include "mbedtls/certs.h" +#include "mbedtls/config.h" +#include "mbedtls/ctr_drbg.h" +#include "mbedtls/debug.h" +#include "mbedtls/entropy.h" +#include "mbedtls/error.h" +#include "mbedtls/net_sockets.h" +#include "mbedtls/platform.h" +#include "mbedtls/x509_crt.h" + +int main(int argc, char** argv) { + // Disable buffering on stdout so docker output is shown + setbuf(stdout, NULL); + + char* host = "localhost"; + char* servername = "tls-server.com"; + char* port = "4433"; + const char* alpn[] = {"http/1.1", NULL}; + char* cert = "/etc/ssl/certs/ca.crt"; + + /* Get commandline arguments */ + int opt; + while ((opt = getopt(argc, argv, "a:s:c:h:p")) != -1) { + switch (opt) { + case 'a': + alpn[0] = optarg; + break; + case 's': + servername = optarg; + break; + case 'h': + host = optarg; + break; + case 'p': + port = optarg; + break; + case 'c': + cert = optarg; + break; + default: + fprintf(stderr, "Usage: %s [-a alpn] [-s servername] [-h ip] [-p port] [-c certificate] \n", argv[0]); + exit(EXIT_FAILURE); + } + } + printf("Parameters alpn=%s servername=%s cert=%s host=%s port=%s \n", alpn[0], servername, cert, host, port); + + unsigned char buf[1024]; + int len; + int ret; + uint32_t flags; + + mbedtls_net_context server_fd; + mbedtls_entropy_context entropy; + mbedtls_ctr_drbg_context ctr_drbg; + mbedtls_ssl_context ssl; + mbedtls_ssl_config conf; + mbedtls_x509_crt cacert; + + mbedtls_debug_set_threshold(1); + + const char* pers = "ssl_client1"; + + // 1. Initialize the RNG and the session data + mbedtls_net_init(&server_fd); + mbedtls_ssl_init(&ssl); + // Minimum TLS 1.2 + mbedtls_ssl_conf_min_version(&conf, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3); + mbedtls_ssl_config_init(&conf); + mbedtls_x509_crt_init(&cacert); + + mbedtls_entropy_init(&entropy); + if ((ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, (const unsigned char*)pers, strlen(pers))) != 0) { + mbedtls_printf("mbedtls_ctr_drbg_seed returned %d \n", ret); + goto cleanup; + } + + // 2. Initialize ca certificate + ret = mbedtls_x509_crt_parse_file(&cacert, cert); + if (ret < 0) { + mbedtls_printf("mbedtls_x509_crt_parse returned -0x%x\n", -ret); + goto cleanup; + } + + // 3. Start the connection + if ((ret = mbedtls_net_connect(&server_fd, host, port, MBEDTLS_NET_PROTO_TCP)) != 0) { + mbedtls_printf("mbedtls_net_connect returned %d\n", ret); + goto cleanup; + } + + // 4. Setup ssl config and add ca certificate + if ((ret = mbedtls_ssl_config_defaults(&conf, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT)) != 0) { + mbedtls_printf("mbedtls_ssl_config_defaults returned %d\n", ret); + goto cleanup; + } + mbedtls_ssl_conf_ca_chain(&conf, &cacert, NULL); + mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg); + + // set ALPN + if ((ret = mbedtls_ssl_conf_alpn_protocols(&conf, alpn)) != 0) { + mbedtls_printf("mbedtls_ssl_conf_alpn_protocols returned %d\n", ret); + goto cleanup; + } + + // set SNI + if ((ret = mbedtls_ssl_set_hostname(&ssl, servername)) != 0) { + mbedtls_printf("mbedtls_ssl_set_hostname returned %d\n", ret); + goto cleanup; + } + + // enable Hostname verification + mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_REQUIRED); + + if ((ret = mbedtls_ssl_setup(&ssl, &conf)) != 0) { + mbedtls_printf("mbedtls_ssl_setup returned %d\n", ret); + goto cleanup; + } + + // 5. Handshake + // TLS 1.2 minimum + mbedtls_ssl_conf_min_version(&conf, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3); + mbedtls_ssl_set_bio(&ssl, &server_fd, mbedtls_net_send, mbedtls_net_recv, NULL); + while ((ret = mbedtls_ssl_handshake(&ssl)) != 0) { + if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { + mbedtls_printf("mbedtls_ssl_handshake returned -0x%x\n", ret); + goto cleanup; + } + } + + // get ALPN: + const char* alpn_selected = mbedtls_ssl_get_alpn_protocol(&ssl); + printf("ALPN: %s \n", alpn_selected); + + //6. Verify the server certificate + if ((flags = mbedtls_ssl_get_verify_result(&ssl)) != 0) { + char vrfy_buf[512]; + mbedtls_printf("mbedtls_ssl_get_verify_result failed\n"); + mbedtls_x509_crt_verify_info(vrfy_buf, sizeof(vrfy_buf), " ! ", flags); + mbedtls_printf("%s\n", vrfy_buf); + goto cleanup; + } + + // 7. sent message to server + len = sprintf((char*)buf, "Hello from Client!\n"); + while ((ret = mbedtls_ssl_write(&ssl, buf, len)) <= 0) { + if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { + mbedtls_printf("mbedtls_ssl_write returned %d\n", ret); + goto cleanup; + } + ret = 0; + } + // 8. get message from server + len = sizeof(buf) - 1; + memset(buf, 0, sizeof(buf)); + ret = mbedtls_ssl_read(&ssl, buf, len); + if (ret < 0) { + mbedtls_printf("mbedtls_ssl_read returned %d\n", ret); + } else { + mbedtls_printf((char*)buf); + ret = 0; + } + + mbedtls_ssl_close_notify(&ssl); +cleanup: + mbedtls_net_free(&server_fd); + mbedtls_x509_crt_free(&cacert); + mbedtls_ssl_free(&ssl); + mbedtls_ssl_config_free(&conf); + mbedtls_ctr_drbg_free(&ctr_drbg); + mbedtls_entropy_free(&entropy); + if (ret != 0) { + char error_buf[100]; + mbedtls_strerror(ret, error_buf, 100); + if (strstr(error_buf, "X509 - Certificate verification failed") != NULL) { + ret = 42; + } else if (strstr(error_buf, "Processing of the ServerHello handshake message failed") != NULL) { + ret = 120; + } + mbedtls_printf("Last error was: %d - %s\n", ret, error_buf); + return ret; + } + return 0; +} \ No newline at end of file diff --git a/evaluation-libraries/mbedtls/docker-compose.yml b/evaluation-libraries/mbedtls/docker-compose.yml new file mode 100644 index 0000000..4c0fc46 --- /dev/null +++ b/evaluation-libraries/mbedtls/docker-compose.yml @@ -0,0 +1,21 @@ +version: "3.9" +networks: + default: + name: tls-network + internal: true +services: + mbedtls-server: + image: tls-mbedtls + openssl-server-wrong-cn: + image: tls-openssl + command: [ "/openssl-server", "-k", "/etc/ssl/cert-data/wrong-cn.com.key", "-c" , "/etc/ssl/cert-data/wrong-cn.com-chain.crt"] + openssl-malicious-alpn: + image: tls-openssl + command: [ "/openssl-server", "-m"] + mbedtls-client: + image: tls-mbedtls + command: [ "./client.sh", "/client", "mbedtls-server", "openssl-server-wrong-cn", "openssl-malicious-alpn" ,"1"] + depends_on: + - mbedtls-server + - openssl-server-wrong-cn + - openssl-malicious-alpn \ No newline at end of file diff --git a/evaluation-libraries/mbedtls/run.sh b/evaluation-libraries/mbedtls/run.sh new file mode 100755 index 0000000..b432af3 --- /dev/null +++ b/evaluation-libraries/mbedtls/run.sh @@ -0,0 +1,2 @@ +./build.sh +docker-compose up --abort-on-container-exit --exit-code-from mbedtls-client --remove-orphans \ No newline at end of file diff --git a/evaluation-libraries/mbedtls/server/CMakeLists.txt b/evaluation-libraries/mbedtls/server/CMakeLists.txt new file mode 100644 index 0000000..9826f6f --- /dev/null +++ b/evaluation-libraries/mbedtls/server/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.0.0) +project(server VERSION 0.1.0) + + +add_library(mbedtls STATIC IMPORTED) +set_target_properties(mbedtls PROPERTIES + IMPORTED_LOCATION "${CMAKE_SOURCE_DIR}/mbedtls/library/libmbedtls.a" + INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_SOURCE_DIR}/mbedtls/include" + ) +add_library(mbedx509 STATIC IMPORTED) +set_target_properties(mbedx509 PROPERTIES + IMPORTED_LOCATION "${CMAKE_SOURCE_DIR}/mbedtls/library/libmbedx509.a" + INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_SOURCE_DIR}/mbedtls/include" + ) +add_library(mbedcrypto STATIC IMPORTED) +set_target_properties(mbedcrypto PROPERTIES + IMPORTED_LOCATION "${CMAKE_SOURCE_DIR}/mbedtls/crypto/library/libmbedcrypto.a" + INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_SOURCE_DIR}/mbedtls/crypto/include" + ) + +add_executable(server server.c) +target_link_libraries(server mbedx509 mbedtls mbedcrypto) +target_compile_options(server PRIVATE -Wall -Wextra) diff --git a/evaluation-libraries/mbedtls/server/server.c b/evaluation-libraries/mbedtls/server/server.c new file mode 100644 index 0000000..79d3404 --- /dev/null +++ b/evaluation-libraries/mbedtls/server/server.c @@ -0,0 +1,235 @@ +/* + * SSL server demonstration program + * + * Copyright (C) 2006-2015, ARM Limited, All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is part of mbed TLS (https://tls.mbed.org) + */ + +#include +#include +#include + +#include "mbedtls/certs.h" +#include "mbedtls/config.h" +#include "mbedtls/ctr_drbg.h" +#include "mbedtls/debug.h" +#include "mbedtls/entropy.h" +#include "mbedtls/error.h" +#include "mbedtls/net_sockets.h" +#include "mbedtls/platform.h" +#include "mbedtls/ssl.h" +#include "mbedtls/ssl_cache.h" +#include "mbedtls/x509.h" + +mbedtls_entropy_context entropy; +mbedtls_ctr_drbg_context ctr_drbg; +mbedtls_ssl_context ssl; +mbedtls_ssl_config conf; +mbedtls_x509_crt srvcert; +mbedtls_pk_context pkey; + +// needs to return 0 if a valid SNI was found and set the correct certificate +int sni_callback(void *p_info, mbedtls_ssl_context *ssl, const unsigned char *name, size_t name_len) { + if (strcmp((const char *)name, (const char *)p_info) == 0) { + mbedtls_printf("VALID SNI: %s \n", name); + mbedtls_ssl_set_hs_own_cert(ssl, &srvcert, &pkey); + return 0; + } else { + mbedtls_printf("INVALID SNI: %s \n", name); + return -1; + } +} + +int main(int argc, char **argv) { + // Disable buffering on stdout so docker output is shown + setbuf(stdout, NULL); + + char *servername = "tls-server.com"; + char *port = "4433"; + const char *alpn[] = {"http/1.1", NULL}; + char *cert = "/etc/ssl/cert-data/tls-server.com-chain.crt"; + char *key = "/etc/ssl/cert-data/tls-server.com.key"; + + /* Get commandline arguments */ + int opt; + while ((opt = getopt(argc, argv, "a:s:c:k:p")) != -1) { + switch (opt) { + case 'a': + alpn[0] = optarg; + break; + case 's': + servername = optarg; + break; + case 'k': + key = optarg; + break; + case 'p': + port = optarg; + break; + case 'c': + cert = optarg; + break; + default: + fprintf(stderr, "Usage: %s [-a alpn] [-s servername] [-h ip] [-p port] [-c certificate] \n", argv[0]); + exit(EXIT_FAILURE); + } + } + printf("Parameters alpn=%s servername=%s cert=%s key=%s port=%s \n", alpn[0], servername, cert, key, port); + + int ret, len; + mbedtls_net_context listen_fd, client_fd; + unsigned char buf[1024]; + const char *pers = "ssl_server"; + + mbedtls_net_init(&listen_fd); + mbedtls_net_init(&client_fd); + mbedtls_ssl_init(&ssl); + mbedtls_ssl_config_init(&conf); + mbedtls_x509_crt_init(&srvcert); + mbedtls_pk_init(&pkey); + mbedtls_entropy_init(&entropy); + mbedtls_ctr_drbg_init(&ctr_drbg); + + // 1. Load the certificates and private RSA key + ret = mbedtls_x509_crt_parse_file(&srvcert, cert); + if (ret != 0) { + mbedtls_printf("mbedtls_x509_crt_parse_file returned %d\n", ret); + goto exit; + } + ret = mbedtls_pk_parse_keyfile(&pkey, key, NULL); + if (ret != 0) { + mbedtls_printf("mbedtls_pk_parse_keyfile returned %d\n", ret); + goto exit; + } + + // 2. Setup the listening TCP socket + if ((ret = mbedtls_net_bind(&listen_fd, NULL, port, MBEDTLS_NET_PROTO_TCP)) != 0) { + mbedtls_printf("mbedtls_net_bind returned %d\n", ret); + goto exit; + } + + // 3. Seed the RNG + if ((ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, (const unsigned char *)pers, strlen(pers))) != 0) { + mbedtls_printf("mbedtls_ctr_drbg_seed returned %d\n", ret); + goto exit; + } + + // 4. Setup SSL config + if ((ret = mbedtls_ssl_config_defaults(&conf, MBEDTLS_SSL_IS_SERVER, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT)) != 0) { + mbedtls_printf("mbedtls_ssl_config_defaults returned %d\n", ret); + goto exit; + } + + // Set ALPN + if ((ret = mbedtls_ssl_conf_alpn_protocols(&conf, alpn)) != 0) { + mbedtls_printf("mbedtls_ssl_conf_alpn_protocols returned %d\n", ret); + goto exit; + } + + // set SNI callback function + mbedtls_ssl_conf_sni(&conf, sni_callback, servername); + + mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg); + + mbedtls_ssl_conf_ca_chain(&conf, srvcert.next, NULL); + if ((ret = mbedtls_ssl_conf_own_cert(&conf, &srvcert, &pkey)) != 0) { + mbedtls_printf("mbedtls_ssl_conf_own_cert returned %d\n", ret); + goto exit; + } + + if ((ret = mbedtls_ssl_setup(&ssl, &conf)) != 0) { + mbedtls_printf("mbedtls_ssl_setup returned %d\n", ret); + goto exit; + } + +reset: + if (ret != 0) { + char error_buf[100]; + mbedtls_strerror(ret, error_buf, 100); + mbedtls_printf("Last error was: %d - %s\n", ret, error_buf); + } + mbedtls_net_free(&client_fd); + mbedtls_ssl_session_reset(&ssl); + + // 5. Wait until a client connects + if ((ret = mbedtls_net_accept(&listen_fd, &client_fd, NULL, 0, NULL)) != 0) { + mbedtls_printf(" failed\n ! mbedtls_net_accept returned %d\n", ret); + goto exit; + } + mbedtls_ssl_set_bio(&ssl, &client_fd, mbedtls_net_send, mbedtls_net_recv, NULL); + + // TLS 1.2 minimum + mbedtls_ssl_conf_min_version(&conf, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3); + // 6. Handshake + while ((ret = mbedtls_ssl_handshake(&ssl)) != 0) { + if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { + mbedtls_printf("mbedtls_ssl_handshake returned %d\n", ret); + goto reset; + } + } + + // get ALPN: + const char *alpn_selected = mbedtls_ssl_get_alpn_protocol(&ssl); + printf("ALPN: %s \n", alpn_selected); + + // 6. Get message from client + len = sizeof(buf) - 1; + memset(buf, 0, sizeof(buf)); + ret = mbedtls_ssl_read(&ssl, buf, len); + if (ret < 0) { + mbedtls_printf("failed\n ! mbedtls_ssl_read returned %d\n\n", ret); + } else { + mbedtls_printf((char *)buf); + } + + // 7. Send response to client + len = sprintf((char *)buf, "Hello from Server!\n"); + + while ((ret = mbedtls_ssl_write(&ssl, buf, len)) <= 0) { + if (ret == MBEDTLS_ERR_NET_CONN_RESET) { + mbedtls_printf("peer closed the connection\n"); + goto reset; + } + + if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { + mbedtls_printf("mbedtls_ssl_write returned %d\n", ret); + goto exit; + } + } + while ((ret = mbedtls_ssl_close_notify(&ssl)) < 0) { + if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { + mbedtls_printf("mbedtls_ssl_close_notify returned %d\n", ret); + goto reset; + } + } + + ret = 0; + goto reset; + +exit: + mbedtls_net_free(&client_fd); + mbedtls_net_free(&listen_fd); + + mbedtls_x509_crt_free(&srvcert); + mbedtls_pk_free(&pkey); + mbedtls_ssl_free(&ssl); + mbedtls_ssl_config_free(&conf); + mbedtls_ctr_drbg_free(&ctr_drbg); + mbedtls_entropy_free(&entropy); + + return (ret); +} \ No newline at end of file diff --git a/evaluation-libraries/openssl/.gitignore b/evaluation-libraries/openssl/.gitignore new file mode 100644 index 0000000..a17f9ee --- /dev/null +++ b/evaluation-libraries/openssl/.gitignore @@ -0,0 +1,19 @@ +build +.vscode +CPackConfig.cmake +CPackSourceConfig.cmake +DartConfiguration.tcl +boringssl2 +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps + +client/178.crt diff --git a/evaluation-libraries/openssl/CMakeLists.txt b/evaluation-libraries/openssl/CMakeLists.txt new file mode 100644 index 0000000..ba6227c --- /dev/null +++ b/evaluation-libraries/openssl/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.0.0) +project(alpaca-boringssl VERSION 0.1.0) + +add_subdirectory(client) +add_subdirectory(server) diff --git a/evaluation-libraries/openssl/Dockerfile-boringssl b/evaluation-libraries/openssl/Dockerfile-boringssl new file mode 100644 index 0000000..5d442ec --- /dev/null +++ b/evaluation-libraries/openssl/Dockerfile-boringssl @@ -0,0 +1,21 @@ +# syntax=docker/dockerfile:1 +FROM tls-baseimage as tls-boringssl +ARG VERSION=3945 +RUN git clone --depth=1 https://boringssl.googlesource.com/boringssl +#RUN git clone --depth=1 -b ${VERSION} https://boringssl.googlesource.com/boringssl +WORKDIR /src/boringssl +RUN cmake . && make +RUN mv crypto/libcrypto.a /lib/libcrypto.a +RUN mv ssl/libssl.a /lib/libssl.a +RUN mv include/* /usr/include/ + +WORKDIR /build +ADD server /build/server +ADD client /build/client +ADD CMakeLists.txt /build/CMakeLists.txt +RUN cmake . .. && make +RUN mv /build/server/server / +RUN mv /build/client/client / +COPY --from=tls-openssl /openssl-client /openssl-client +WORKDIR / +CMD ["/server"] \ No newline at end of file diff --git a/evaluation-libraries/openssl/Dockerfile-openssl b/evaluation-libraries/openssl/Dockerfile-openssl new file mode 100644 index 0000000..c87566c --- /dev/null +++ b/evaluation-libraries/openssl/Dockerfile-openssl @@ -0,0 +1,26 @@ +# syntax=docker/dockerfile:1 +FROM tls-baseimage as tls-openssl +#ARG VERSION=1_1_1 +#RUN git clone --depth=1 -b OpenSSL_${VERSION}-stable https://github.com/openssl/openssl +RUN git clone --depth=1 -b openssl-3.0 https://github.com/openssl/openssl +WORKDIR /src/openssl +RUN ./config no-async +RUN make +RUN make install +RUN cp libcrypto.a /lib/libcrypto.a +RUN cp libssl.a /lib/libssl.a +RUN cp -r -L include/* /usr/include/ + +WORKDIR /build +ADD server /build/server +ADD client /build/client +ADD CMakeLists.txt /build/CMakeLists.txt +RUN cmake . .. && make +RUN mv /build/server/server /openssl-server +RUN mv /build/client/client /openssl-client +RUN rm -r /build/* +RUN rm -r /usr/include/openssl +RUN rm /lib/libcrypto.a +RUN rm /lib/libssl.a +WORKDIR / +CMD ["/openssl-server"] \ No newline at end of file diff --git a/evaluation-libraries/openssl/LICENSE b/evaluation-libraries/openssl/LICENSE new file mode 100644 index 0000000..5b5ccdc --- /dev/null +++ b/evaluation-libraries/openssl/LICENSE @@ -0,0 +1,124 @@ + + LICENSE ISSUES + ============== + + The OpenSSL toolkit stays under a double license, i.e. both the conditions of + the OpenSSL License and the original SSLeay license apply to the toolkit. + See below for the actual license texts. + + OpenSSL License + --------------- + +/* ==================================================================== + * Copyright (c) 1998-2019 The OpenSSL Project. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" + * + * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * openssl-core@openssl.org. + * + * 5. Products derived from this software may not be called "OpenSSL" + * nor may "OpenSSL" appear in their names without prior written + * permission of the OpenSSL Project. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit (http://www.openssl.org/)" + * + * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY + * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * ==================================================================== + * + * This product includes cryptographic software written by Eric Young + * (eay@cryptsoft.com). This product includes software written by Tim + * Hudson (tjh@cryptsoft.com). + * + */ + + Original SSLeay License + ----------------------- + +/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) + * All rights reserved. + * + * This package is an SSL implementation written + * by Eric Young (eay@cryptsoft.com). + * The implementation was written so as to conform with Netscapes SSL. + * + * This library is free for commercial and non-commercial use as long as + * the following conditions are aheared to. The following conditions + * apply to all code found in this distribution, be it the RC4, RSA, + * lhash, DES, etc., code; not just the SSL code. The SSL documentation + * included with this distribution is covered by the same copyright terms + * except that the holder is Tim Hudson (tjh@cryptsoft.com). + * + * Copyright remains Eric Young's, and as such any Copyright notices in + * the code are not to be removed. + * If this package is used in a product, Eric Young should be given attribution + * as the author of the parts of the library used. + * This can be in the form of a textual message at program startup or + * in documentation (online or textual) provided with the package. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * "This product includes cryptographic software written by + * Eric Young (eay@cryptsoft.com)" + * The word 'cryptographic' can be left out if the rouines from the library + * being used are not cryptographic related :-). + * 4. If you include any Windows specific code (or a derivative thereof) from + * the apps directory (application code) you must include an acknowledgement: + * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + * + * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * The licence and distribution terms for any publically available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution licence + * [including the GNU Public Licence.] + */ diff --git a/evaluation-libraries/openssl/README.md b/evaluation-libraries/openssl/README.md new file mode 100644 index 0000000..04c02cf --- /dev/null +++ b/evaluation-libraries/openssl/README.md @@ -0,0 +1,13 @@ +# openssl and boringssl example with strict sni and strict alpn + +This library creates containers for openssl and boringssl since they are almost code-compatible. + +needs tls-baseimage already in docker + +Tested openSSL 1.1.0, 1.1.1, 3.0 and BoringSSL/master from November 2021 + +roughly based on https://wiki.openssl.org/index.php/SSL/TLS_Client and https://wiki.openssl.org/index.php/Simple_TLS_Server + +```bash +./run.sh +``` \ No newline at end of file diff --git a/evaluation-libraries/openssl/build.sh b/evaluation-libraries/openssl/build.sh new file mode 100755 index 0000000..4138244 --- /dev/null +++ b/evaluation-libraries/openssl/build.sh @@ -0,0 +1,2 @@ +docker build --build-arg VERSION=1_1_1 . -t tls-openssl -f Dockerfile-openssl +docker build --build-arg VERSION=3945 . -t tls-boringssl -f Dockerfile-boringssl diff --git a/evaluation-libraries/openssl/client/CMakeLists.txt b/evaluation-libraries/openssl/client/CMakeLists.txt new file mode 100644 index 0000000..c5f19c5 --- /dev/null +++ b/evaluation-libraries/openssl/client/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.0.0) +project(client VERSION 0.1.0) + +find_package(OpenSSL REQUIRED) + +add_executable(client client.c) +target_link_libraries(client ${CMAKE_DL_LIBS} ssl crypto pthread dl) +target_compile_options(client PRIVATE -Wall -Wextra) \ No newline at end of file diff --git a/evaluation-libraries/openssl/client/client.c b/evaluation-libraries/openssl/client/client.c new file mode 100644 index 0000000..6f451b1 --- /dev/null +++ b/evaluation-libraries/openssl/client/client.c @@ -0,0 +1,241 @@ +#include "client.h" + +char *alpn = "http/1.1"; +char *host = "127.0.0.1"; +char *port = "4433"; +char *servername = "tls-server.com"; +char *cert = "/etc/ssl/certs/ca.crt"; + +int debug = 0; + +int main(int argc, char *argv[]) { + // Disable buffering on stdout so docker output is shown + setbuf(stdout, NULL); + + /* Get commandline arguments */ + int opt; + while ((opt = getopt(argc, argv, "a:s:c:h:p:d")) != -1) { + switch (opt) { + case 'a': + alpn = optarg; + break; + case 's': + servername = optarg; + break; + case 'h': + host = optarg; + break; + case 'p': + port = optarg; + break; + case 'c': + cert = optarg; + break; + case 'd': + debug = 1; + break; + default: + fprintf(stderr, "Usage: %s [-a alpn] [-s servername] [-h ip] [-p port] [-c certificate] \n", argv[0]); + exit(EXIT_FAILURE); + } + } + printf("Parameters alpn=%s servername=%s cert=%s host=%s port=%s \n", alpn, servername, cert, host, port); + + /* Create SSL Context */ +#if defined(OPENSSL_IS_BORINGSSL) || OPENSSL_VERSION_NUMBER >= 0x10100000L + SSL_CTX *ctx = SSL_CTX_new(TLS_method()); +#else + SSL_CTX *ctx = SSL_CTX_new(TLSv1_2_method()); +#endif + /* Set TLS Version */ +#ifdef TLS1_3_VERSION + if (!SSL_CTX_set_max_proto_version(ctx, TLS1_3_VERSION)) { + exit(2); + } + if (!SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION)) { + exit(2); + } +#endif + + // Format alpn to wire-format ("http/1.1" -> "8http/1.1") + unsigned char *alpn_formatted; + size_t alpn_formatted_len = strlen(alpn) + 1; + alpn_formatted = calloc(alpn_formatted_len, sizeof(unsigned char)); + alpn_formatted[0] = (unsigned char)strlen(alpn); + for (size_t i = 1; i <= strlen(alpn); i++) { + alpn_formatted[i] = alpn[i - 1]; + } + + if (SSL_CTX_set_alpn_protos(ctx, alpn_formatted, alpn_formatted_len)) { + fprintf(stderr, "Failed to set ALPN.\n"); + exit(3); + } + + /* Load certificate file */ + if (!SSL_CTX_load_verify_locations(ctx, cert, NULL)) { + fprintf(stderr, "Failed to load cert.\n"); + exit(4); + } + + /* debug handshake */ + if (debug) { + SSL_CTX_set_info_callback(ctx, InfoCallback); + } + + /* Connect to server */ + char *host_with_port = calloc(sizeof(char), strlen(host) + strlen(port) + 1); + strcat(host_with_port, host); + strcat(host_with_port, ":"); + strcat(host_with_port, port); + BIO *bio = BIO_new_connect(host_with_port); + SSL *ssl = SSL_new(ctx); + +/* 3. Hostname */ +#if defined(OPENSSL_IS_BORINGSSL) || OPENSSL_VERSION_NUMBER < 0x10100000L + X509_VERIFY_PARAM *param = SSL_get0_param(ssl); + X509_VERIFY_PARAM_set_hostflags(param, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); + if (!X509_VERIFY_PARAM_set1_host(param, servername, strlen(servername))) { + fprintf(stderr, "X509_VERIFY_PARAM_set1_host\n"); + exit(5); + } +#else + SSL_set_hostflags(ssl, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); + if (!SSL_set1_host(ssl, servername)) { + fprintf(stderr, "SSL_set1_host\n"); + exit(5); + } +#endif + SSL_set_verify(ssl, SSL_VERIFY_PEER, NULL); + + /* Set SNI */ + SSL_set_tlsext_host_name(ssl, servername); + +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + SSL_set0_wbio(ssl, bio); + SSL_set0_rbio(ssl, bio); +#else + SSL_set_bio(ssl, bio, bio); +#endif + + //bio.release(); + int ret = SSL_connect(ssl); + if (ret != 1) { + //int ssl_err = SSL_get_error(ssl, ret); + //PrintSSLError(stderr, "Error while connecting", ssl_err, ret); + int return_value = SSL_ERROR_SSL; + ERR_print_errors_cb(&error_callback, &return_value); + exit(return_value); + } + printf("Connected! \n"); + + /* 1. Verify Certificate */ + X509 *ServerCert = SSL_get_peer_certificate(ssl); + if (ServerCert) { + X509_free(ServerCert); + } else { + fprintf(stderr, "SSL_get_peer_certificate.\n"); + exit(7); + } + + /* 2. Verify Certificate Chain */ + if (SSL_get_verify_result(ssl) != X509_V_OK) { + fprintf(stderr, "Error in SSL_get_verify_result.\n"); + exit(8); + } + + // strict ALPN verification + const unsigned char *alpn_received; + unsigned int alpn_received_len; + SSL_get0_alpn_selected(ssl, &alpn_received, &alpn_received_len); + + if (alpn_received_len > 0) { + if (strncmp((const char *)alpn_received, alpn, alpn_received_len) == 0) { + printf("ALPN: %.*s \n", alpn_received_len, alpn_received); + } else { + fprintf(stderr, "INVALID ALPN: %.*s \n", alpn_received_len, alpn_received); + return TLS1_AD_NO_APPLICATION_PROTOCOL; + } + } else { + printf("No ALPN negotiated ! %.*s\n", alpn_received_len, alpn_received); + //exit(10); + } + + /* send message to server */ + char *message = "Hello from Client!\n"; + SSL_write(ssl, message, strlen(message)); + + /* get message from server */ + char buff[1536] = {}; + SSL_read(ssl, buff, sizeof(buff)); + printf("%s", buff); + + /* Close connection */ + BIO_free(bio); + SSL_CTX_free(ctx); + + return 0; +} + +/* Debug info callback for handshake */ +static void InfoCallback(const SSL *ssl, int type, __attribute__((unused)) int value) { + switch (type) { + case SSL_CB_HANDSHAKE_START: + printf("Handshake started\n"); + break; + case SSL_CB_HANDSHAKE_DONE: + printf("Handshake done\n"); + break; + case SSL_CB_CONNECT_LOOP: + printf("Handshake progress: %s\n", SSL_state_string_long(ssl)); + break; + } +} + +static int error_callback(const char *str, size_t len, void *err) { + if (strstr(str, "SSL alert number 120") != NULL || strstr(str, "INVALID_ALPN_PROTOCOL") != NULL) { + printf("TLSV1_ALERT_NO_APPLICATION_PROTOCOL \n"); + //err = 1; + (*(int *)err) = TLS1_AD_NO_APPLICATION_PROTOCOL; + } else if (strstr(str, "CERTIFICATE_VERIFY_FAILED") != NULL || strstr(str, "certificate verify failed") != NULL) { + printf("CERTIFICATE_VERIFY_FAILED \n"); + //err = 1; + (*(int *)err) = SSL3_AD_BAD_CERTIFICATE; + } else if (strstr(str, "TLSV1_ALERT_UNRECOGNIZED_NAME") != NULL || strstr(str, "tlsv1 unrecognized name") != NULL) { + printf("TLSV1_ALERT_UNRECOGNIZED_NAME \n"); + //err = 1; + (*(int *)err) = TLS1_AD_UNRECOGNIZED_NAME; + } else { + printf("%s", str); + (*(int *)err) = SSL_ERROR_SSL; + } + return 0; +} + +void PrintSSLError(FILE *file, const char *msg, int ssl_err, int ret) { + switch (ssl_err) { + case SSL_ERROR_SSL: + fprintf(stderr, " %s \n", ERR_reason_error_string(ERR_peek_error())); + break; + case SSL_ERROR_SYSCALL: + if (ret == 0) { + fprintf(stderr, " peer closed connection \n"); + } else { + char *error = strerror(errno); + fprintf(stderr, " %s %s \n", msg, error); + } + break; + case SSL_ERROR_ZERO_RETURN: + fprintf(stderr, "received close_notify \n"); + break; + default: + break; +#ifdef OPENSSL_IS_BORINGSSL + fprintf(file, "%s: unexpected error: %s\n", msg, SSL_error_description(ssl_err)); +#else + fprintf(file, "%s: unexpected error: %s\n", msg, ERR_reason_error_string(ssl_err)); +#endif + } + //ERR_print_errors_fp(file); + int err = 5; + ERR_print_errors_cb(&error_callback, &err); +} \ No newline at end of file diff --git a/evaluation-libraries/openssl/client/client.h b/evaluation-libraries/openssl/client/client.h new file mode 100644 index 0000000..659b4f4 --- /dev/null +++ b/evaluation-libraries/openssl/client/client.h @@ -0,0 +1,13 @@ +#include +#include +#include +#include +#include +#include +#include + +void PrintSSLError(FILE *file, const char *msg, int ssl_err, int ret); + +static void InfoCallback(const SSL *ssl, int type, int value); + +static int error_callback(const char *str, size_t len, void *err); \ No newline at end of file diff --git a/evaluation-libraries/openssl/docker-compose-boringssl.yml b/evaluation-libraries/openssl/docker-compose-boringssl.yml new file mode 100644 index 0000000..7e116f6 --- /dev/null +++ b/evaluation-libraries/openssl/docker-compose-boringssl.yml @@ -0,0 +1,36 @@ +version: "3.9" +networks: + default: + name: tls-network + internal: true +services: + openssl-server-wrong-cn: + image: tls-openssl + command: + [ + "/openssl-server", + "-k", + "/etc/ssl/cert-data/wrong-cn.com.key", + "-c", + "/etc/ssl/cert-data/wrong-cn.com-chain.crt", + ] + openssl-malicious-alpn: + image: tls-openssl + command: ["/openssl-server", "-m"] + boringssl-server: + image: tls-boringssl + boringssl-client: + image: tls-boringssl + depends_on: + - boringssl-server + - openssl-server-wrong-cn + - openssl-malicious-alpn + command: + [ + "/client.sh", + "/client", + "boringssl-server", + "openssl-server-wrong-cn", + "openssl-malicious-alpn", + "6", + ] diff --git a/evaluation-libraries/openssl/docker-compose.yml b/evaluation-libraries/openssl/docker-compose.yml new file mode 100644 index 0000000..315cb4b --- /dev/null +++ b/evaluation-libraries/openssl/docker-compose.yml @@ -0,0 +1,36 @@ +version: "3.9" +networks: + default: + name: tls-network + internal: true +services: + openssl-server: + image: tls-openssl + openssl-server-wrong-cn: + image: tls-openssl + command: + [ + "/openssl-server", + "-k", + "/etc/ssl/cert-data/wrong-cn.com.key", + "-c", + "/etc/ssl/cert-data/wrong-cn.com-chain.crt", + ] + openssl-malicious-alpn: + image: tls-openssl + command: ["/openssl-server", "-m"] + openssl-client: + image: tls-openssl + depends_on: + - openssl-server + - openssl-server-wrong-cn + - openssl-malicious-alpn + command: + [ + "/client.sh", + "/openssl-client", + "openssl-server", + "openssl-server-wrong-cn", + "openssl-malicious-alpn", + "1", + ] diff --git a/evaluation-libraries/openssl/run.sh b/evaluation-libraries/openssl/run.sh new file mode 100755 index 0000000..a494150 --- /dev/null +++ b/evaluation-libraries/openssl/run.sh @@ -0,0 +1,3 @@ +./build.sh +docker-compose up --exit-code-from openssl-client --remove-orphans +docker-compose -f docker-compose-boringssl.yml -p "boringssl" up --exit-code-from boringssl-client --remove-orphans \ No newline at end of file diff --git a/evaluation-libraries/openssl/server/CMakeLists.txt b/evaluation-libraries/openssl/server/CMakeLists.txt new file mode 100644 index 0000000..d6e2a52 --- /dev/null +++ b/evaluation-libraries/openssl/server/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.0.0) +project(server VERSION 0.1.0) + +find_package(OpenSSL REQUIRED) +add_executable(server server.c) +target_link_libraries(server ${CMAKE_DL_LIBS} ssl crypto pthread dl) +target_compile_options(server PRIVATE -Wall -Wextra) diff --git a/evaluation-libraries/openssl/server/server.c b/evaluation-libraries/openssl/server/server.c new file mode 100644 index 0000000..f3074c6 --- /dev/null +++ b/evaluation-libraries/openssl/server/server.c @@ -0,0 +1,226 @@ +#include "server.h" + +char *alpn = "http/1.1"; +char *servername = "tls-server.com"; +char *cert = "/etc/ssl/cert-data/tls-server.com.crt"; +char *key = "/etc/ssl/cert-data/tls-server.com.key"; + +const uint16_t port = 4433; + +int malicious_alpn = 0; + +int err, ret; + +int main(int argc, char **argv) { + // Disable buffering on stdout so docker output is shown + setbuf(stdout, NULL); + + /* Get commandline arguments */ + int opt; + while ((opt = getopt(argc, argv, "a:s:c:k:m")) != -1) { + switch (opt) { + case 'a': + alpn = optarg; + break; + case 's': + servername = optarg; + break; + case 'c': + cert = optarg; + break; + case 'k': + key = optarg; + break; + case 'm': + malicious_alpn = 1; + break; + default: + fprintf(stderr, "Usage: %s [-a alpn] [-s servername] [-c certfile] [-k keyfile] \n", + argv[0]); + exit(EXIT_FAILURE); + } + } + printf("Parameters alpn=%s servername=%s cert=%s key=%s port=%d \n", alpn, servername, cert, key, port); + + int sock; +#if defined(OPENSSL_IS_BORINGSSL) || OPENSSL_VERSION_NUMBER >= 0x10100000L + SSL_CTX *ctx = SSL_CTX_new(TLS_method()); +#else + SSL_CTX *ctx = SSL_CTX_new(TLSv1_2_method()); +#endif + /* Set TLS Version */ +#ifdef TLS1_3_VERSION + if (!SSL_CTX_set_max_proto_version(ctx, TLS1_3_VERSION)) { + exit(2); + } + if (!SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION)) { + exit(2); + } +#endif + + // Enable alpn callback function + SSL_CTX_set_alpn_select_cb(ctx, alpn_cb, NULL); + + // Enable SNI callback function + SSL_CTX_set_tlsext_servername_callback(ctx, sni_cb); + + // Load certificate chain and key from file + if (SSL_CTX_use_certificate_chain_file(ctx, cert) <= 0) { + ERR_print_errors_fp(stderr); + exit(EXIT_FAILURE); + } + if (SSL_CTX_use_PrivateKey_file(ctx, key, SSL_FILETYPE_PEM) <= 0) { + ERR_print_errors_fp(stderr); + exit(EXIT_FAILURE); + } + + sock = create_socket(port); + + /* Handle connections */ + while (1) { + struct sockaddr_in addr; + unsigned int len = sizeof(addr); + SSL *ssl; + + int client = accept(sock, (struct sockaddr *)&addr, &len); + if (client < 0) { + perror("Unable to accept"); + exit(EXIT_FAILURE); + } + + ssl = SSL_new(ctx); + SSL_set_fd(ssl, client); + + SSL_accept(ssl); + // err = SSL_get_error(ssl, 0); + // printf("ERROR: %d \n", err); + // if (err == SSL_ERROR_SYSCALL || err == SSL_ERROR_SSL) { + // SSL_free(ssl); + // close(client); + // continue; + // } + + // get message from client + char buff[1536] = {}; + len = SSL_read(ssl, buff, sizeof(buff)); + if(len <= 0) { + int errorvalue = SSL_get_error(ssl, len); + if (errorvalue != SSL_ERROR_NONE) { + printf("Errorvalue: %d return of read: %d \n", errorvalue, len); + } + + SSL_free(ssl); + close(client); + + continue; + } + printf("%s \n", buff); + // err = SSL_get_error(ssl, 0); + // printf("ERROR: %d \n", err); + // if (err == SSL_ERROR_SYSCALL || err == SSL_ERROR_SSL) { + // SSL_free(ssl); + // close(client); + // continue; + // } + + // send message to client + char *message = "Hello from Server!\n"; + + len = SSL_write(ssl, message, strlen(message)); + if (len <= 0) { + SSL_free(ssl); + close(client); + continue; + } + // err = SSL_get_error(ssl, 0); + // printf("ERROR: %d \n", err); + // if (err == SSL_ERROR_SYSCALL || err == SSL_ERROR_SSL) { + // SSL_free(ssl); + // close(client); + // continue; + // } + + SSL_shutdown(ssl); + SSL_free(ssl); + close(client); + } + + close(sock); + SSL_CTX_free(ctx); + return 0; +} + +#ifdef OPENSSL_IS_BORINGSSL +static int alpn_cb(__attribute__((unused)) SSL *ssl, const uint8_t **out, uint8_t *outlen, const uint8_t *in, unsigned inlen, __attribute__((unused)) void *arg) { +#else +static int alpn_cb(__attribute__((unused)) SSL *ssl, const unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, __attribute__((unused)) void *arg) { +#endif + // Return the alpn "invalid" in the ServerHello message, used for running Test5 + if (malicious_alpn == 1) { + unsigned char *invalidalpn = (unsigned char *)"invalid"; + out[0] = invalidalpn; + outlen[0] = 7; + return SSL_TLSEXT_ERR_OK; + } + + // Format alpn to wire-format ("http/1.1" -> "8http/1.1") + unsigned char *alpn_formatted; + size_t alpn_formatted_len = strlen(alpn) + 1; + alpn_formatted = calloc(alpn_formatted_len, sizeof(unsigned char)); + alpn_formatted[0] = (unsigned char)strlen(alpn); + for (size_t i = 1; i <= strlen(alpn); i++) { + alpn_formatted[i] = alpn[i - 1]; + } + + if (SSL_select_next_proto((unsigned char **)out, outlen, alpn_formatted, alpn_formatted_len, in, inlen) == OPENSSL_NPN_NEGOTIATED) { + printf("ALPN: %s \n", *out); + return SSL_TLSEXT_ERR_OK; + } else { + printf("INVALID ALPN: %s \n", *out); + return SSL_TLSEXT_ERR_ALERT_FATAL; + } +} + +static int sni_cb(SSL *s, __attribute__((unused)) int *al, __attribute__((unused)) void *arg) { + const char *servername_received = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name); + + if (servername_received != NULL) { + if (strcasecmp(servername_received, servername) == 0) { + printf("SNI: %s \n", servername_received); + return SSL_TLSEXT_ERR_OK; + } else { + printf("INVALID SNI: %s \n", servername_received); + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + } else { + printf("no SNI sent by client, continuing. \n"); + return SSL_TLSEXT_ERR_OK; + } +} + +int create_socket(uint16_t p) { + int s; + struct sockaddr_in addr; + + addr.sin_family = AF_INET; + addr.sin_port = htons(p); + addr.sin_addr.s_addr = htonl(INADDR_ANY); + + s = socket(AF_INET, SOCK_STREAM, 0); + if (s < 0) { + perror("Unable to create socket"); + exit(EXIT_FAILURE); + } + + if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + perror("Unable to bind"); + exit(EXIT_FAILURE); + } + + if (listen(s, 1) < 0) { + perror("Unable to listen"); + exit(EXIT_FAILURE); + } + + return s; +} diff --git a/evaluation-libraries/openssl/server/server.h b/evaluation-libraries/openssl/server/server.h new file mode 100644 index 0000000..cf623af --- /dev/null +++ b/evaluation-libraries/openssl/server/server.h @@ -0,0 +1,19 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int create_socket(uint16_t p); + +#ifdef OPENSSL_IS_BORINGSSL +static int alpn_cb(SSL *ssl, const uint8_t **out, uint8_t *out_len, const uint8_t *in, unsigned in_len, void *arg); +#else +static int alpn_cb(SSL *ssl, const unsigned char **out, unsigned char *out_len, const unsigned char *in, unsigned int in_len, void *arg); +#endif + +static int sni_cb(SSL *s, int *al, void *arg); diff --git a/evaluation-libraries/run-everything.sh b/evaluation-libraries/run-everything.sh new file mode 100755 index 0000000..f1ddfe9 --- /dev/null +++ b/evaluation-libraries/run-everything.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +# validate current path +CURRENT=`pwd` +BASENAME=`basename "$CURRENT"` +if [ "$BASENAME" != "evaluation-libraries" ]; then + echo "Please start from the evaluation-libraries folder" + exit +fi + +RED='\033[0;31m ' +GREEN='\033[0;32m ' +NC='\033[0m' # No Color + + +./build-everything.sh + +# go into every library folder +# 1. run containers and tests +# 2. get results file from docker container +# 3. append them to the results file on the host +rm results +for library in bearssl botan gnutls java golang mbedtls openssl wolfssl rustls ; do + (cd "$library" + ./run.sh + containerid=$(docker-compose ps -q $library-client) + echo "Getting results file from container :$containerid" + docker cp $containerid:/results results-temp + echo -e "${NC}$library" >> ../results + cat results-temp >> ../results + rm results-temp + ); +done + +# boringssl is included in the openssl folder so we need to get the file manually +cd openssl +containerid=$(docker-compose -f docker-compose-boringssl.yml -p "boringssl" ps -q boringssl-client) +echo "Getting results file from container :$containerid" +docker cp $containerid:/results results-temp +echo -e "${NC}boringssl" >> ../results +cat results-temp >> ../results +rm results-temp +cd .. + +cat results +#remove colors from output +sed -r "s/\x1B\[([0-9]{1,3}(;[0-9]{1,2})?)?[mGK]//g" results > results-temp +mv results-temp results diff --git a/evaluation-libraries/rustls/Cargo.toml b/evaluation-libraries/rustls/Cargo.toml new file mode 100644 index 0000000..5457677 --- /dev/null +++ b/evaluation-libraries/rustls/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "alpaca-rustls" +version = "0.0.1" +edition = "2018" +description = "Rustls example." +publish = false + +[dependencies] +rustls = "0.20.0" +rustls-pemfile = "0.2.0" +clap = "~2.27.0" +mio = { version = "0.7", features = ["os-poll", "tcp"] } + + +[[bin]] +name = "server" +path = "server/server.rs" + +[[bin]] +name = "client" +path = "client/client.rs" \ No newline at end of file diff --git a/evaluation-libraries/rustls/Dockerfile b/evaluation-libraries/rustls/Dockerfile new file mode 100644 index 0000000..2ca96ca --- /dev/null +++ b/evaluation-libraries/rustls/Dockerfile @@ -0,0 +1,15 @@ +# syntax=docker/dockerfile:1 +FROM tls-baseimage as tls-rustls +RUN apk add cargo +WORKDIR /build +ADD server /build/server +ADD client /build/client +ADD Cargo.toml /build/Cargo.toml +RUN cargo build --release + +RUN mv /build/target/release/server /server +RUN mv /build/target/release/client /client + +COPY --from=tls-openssl /openssl-client /openssl-client +WORKDIR / +CMD ["/server"] diff --git a/evaluation-libraries/rustls/LICENSE b/evaluation-libraries/rustls/LICENSE new file mode 100644 index 0000000..f8e5e5e --- /dev/null +++ b/evaluation-libraries/rustls/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/evaluation-libraries/rustls/README.md b/evaluation-libraries/rustls/README.md new file mode 100644 index 0000000..4713b1f --- /dev/null +++ b/evaluation-libraries/rustls/README.md @@ -0,0 +1,12 @@ +# rustls example with strict sni and strict alpn + +Tested with rustls 0.20.0 + +based on https://github.com/rustls/rustls/blob/main/rustls-mio/examples/ + +needs tls-baseimage already in docker + + +```bash +./run.sh +``` \ No newline at end of file diff --git a/evaluation-libraries/rustls/build.sh b/evaluation-libraries/rustls/build.sh new file mode 100755 index 0000000..f8d3a32 --- /dev/null +++ b/evaluation-libraries/rustls/build.sh @@ -0,0 +1 @@ +docker build --build-arg VERSION=0.20.0 . -t tls-rustls -f Dockerfile \ No newline at end of file diff --git a/evaluation-libraries/rustls/client/client.rs b/evaluation-libraries/rustls/client/client.rs new file mode 100644 index 0000000..a038ae5 --- /dev/null +++ b/evaluation-libraries/rustls/client/client.rs @@ -0,0 +1,412 @@ +extern crate clap; +use clap::{Arg, App}; + +use std::sync::Arc; + +use mio; +use mio::net::{TcpStream}; +use std::fs; +use std::io; +use std::io::{BufReader, Read, Write}; + +use std::process; +use std::collections; +use rustls; +use rustls::{RootCertStore}; +use std::sync::Mutex; +use std::convert::TryFrom; + +const CLIENT: mio::Token = mio::Token(0); + +/// This encapsulates the TCP-level connection, some connection +/// state, and the underlying TLS-level session. +struct TlsClient { + socket: TcpStream, + closing: bool, + clean_closure: bool, + tls_conn: rustls::ClientConnection, +} + +impl TlsClient { + fn new( + sock: TcpStream, + server_name: rustls::ServerName, + cfg: Arc, + ) -> TlsClient { + TlsClient { + socket: sock, + closing: false, + clean_closure: false, + tls_conn: rustls::ClientConnection::new(cfg, server_name).unwrap(), + } + } + + /// Handles events sent to the TlsClient by mio::Poll + fn ready(&mut self, ev: &mio::event::Event) { + assert_eq!(ev.token(), CLIENT); + + if ev.is_readable() { + self.do_read(); + } + + if ev.is_writable() { + self.do_write(); + } + + if self.is_closed() { + println!("Connection closed"); + process::exit(if self.clean_closure { 0 } else { 1 }); + } + } + + /// We're ready to do a read. + fn do_read(&mut self) { + // Read TLS data. This fails if the underlying TCP connection + // is broken. + match self.tls_conn.read_tls(&mut self.socket) { + Err(error) => { + if error.kind() == io::ErrorKind::WouldBlock { + return; + } + println!("TLS read error: {:?}", error); + self.closing = true; + return; + } + + // If we're ready but there's no data: EOF. + Ok(0) => { + println!("EOF"); + self.closing = true; + self.clean_closure = true; + return; + } + + Ok(_) => {} + }; + + // Reading some TLS data might have yielded new TLS + // messages to process. Errors from this indicate + // TLS protocol problems and are fatal. + let io_state = match self.tls_conn.process_new_packets() { + Ok(io_state) => io_state, + Err(err) => { + println!("TLS error: {:?}", err); + self.closing = true; + return; + } + }; + + // Having read some TLS data, and processed any new messages, + // we might have new plaintext as a result. + // + // Read it and then write it to stdout. + if io_state.plaintext_bytes_to_read() > 0 { + let mut plaintext = Vec::new(); + plaintext.resize(io_state.plaintext_bytes_to_read(), 0u8); + self.tls_conn + .reader() + .read(&mut plaintext) + .unwrap(); + io::stdout() + .write_all(&plaintext) + .unwrap(); + } + + // If wethat fails, the peer might have started a clean TLS-level + // session closure. + if io_state.peer_has_closed() { + self.clean_closure = true; + self.closing = true; + return; + } + } + + fn do_write(&mut self) { + self.tls_conn + .write_tls(&mut self.socket) + .unwrap(); + } + + /// Registers self as a 'listener' in mio::Registry + fn register(&mut self, registry: &mio::Registry) { + let interest = self.event_set(); + registry + .register(&mut self.socket, CLIENT, interest) + .unwrap(); + } + + /// Reregisters self as a 'listener' in mio::Registry. + fn reregister(&mut self, registry: &mio::Registry) { + let interest = self.event_set(); + registry + .reregister(&mut self.socket, CLIENT, interest) + .unwrap(); + } + + /// Use wants_read/wants_write to register for different mio-level + /// IO readiness events. + fn event_set(&self) -> mio::Interest { + let rd = self.tls_conn.wants_read(); + let wr = self.tls_conn.wants_write(); + + if rd && wr { + mio::Interest::READABLE | mio::Interest::WRITABLE + } else if wr { + mio::Interest::WRITABLE + } else { + mio::Interest::READABLE + } + } + + fn is_closed(&self) -> bool { + self.closing + } +} +impl io::Write for TlsClient { + fn write(&mut self, bytes: &[u8]) -> io::Result { + self.tls_conn.writer().write(bytes) + } + + fn flush(&mut self) -> io::Result<()> { + self.tls_conn.writer().flush() + } +} + +impl io::Read for TlsClient { + fn read(&mut self, bytes: &mut [u8]) -> io::Result { + self.tls_conn.reader().read(bytes) + } +} + +/// This is an example cache for client session data. +/// It optionally dumps cached data to a file, but otherwise +/// is just in-memory. +/// +/// Note that the contents of such a file are extremely sensitive. +/// Don't write this stuff to disk in production code. +struct PersistCache { + cache: Mutex, Vec>>, + filename: Option, +} + +impl PersistCache { + /// Make a new cache. If filename is Some, load the cache + /// from it and flush changes back to that file. + fn _new(filename: &Option) -> Self { + let cache = PersistCache { + cache: Mutex::new(collections::HashMap::new()), + filename: filename.clone(), + }; + if cache.filename.is_some() { + cache.load(); + } + cache + } + + /// If we have a filename, save the cache contents to it. + fn save(&self) { + use rustls::internal::msgs::base::PayloadU16; + use rustls::internal::msgs::codec::Codec; + + if self.filename.is_none() { + return; + } + + let mut file = + fs::File::create(self.filename.as_ref().unwrap()).expect("cannot open cache file"); + + for (key, val) in self.cache.lock().unwrap().iter() { + let mut item = Vec::new(); + let key_pl = PayloadU16::new(key.clone()); + let val_pl = PayloadU16::new(val.clone()); + key_pl.encode(&mut item); + val_pl.encode(&mut item); + file.write_all(&item).unwrap(); + } + } + + /// We have a filename, so replace the cache contents from it. + fn load(&self) { + use rustls::internal::msgs::base::PayloadU16; + use rustls::internal::msgs::codec::{Codec, Reader}; + + let mut file = match fs::File::open(self.filename.as_ref().unwrap()) { + Ok(f) => f, + Err(_) => return, + }; + let mut data = Vec::new(); + file.read_to_end(&mut data).unwrap(); + + let mut cache = self.cache.lock().unwrap(); + cache.clear(); + let mut rd = Reader::init(&data); + + while rd.any_left() { + let key_pl = PayloadU16::read(&mut rd).unwrap(); + let val_pl = PayloadU16::read(&mut rd).unwrap(); + cache.insert(key_pl.0, val_pl.0); + } + } +} + +impl rustls::client::StoresClientSessions for PersistCache { + /// put: insert into in-memory cache, and perhaps persist to disk. + fn put(&self, key: Vec, value: Vec) -> bool { + self.cache + .lock() + .unwrap() + .insert(key, value); + self.save(); + true + } + + /// get: from in-memory cache + fn get(&self, key: &[u8]) -> Option> { + self.cache + .lock() + .unwrap() + .get(key) + .cloned() + } +} + +// TODO: um, well, it turns out that openssl s_client/s_server +// that we use for testing doesn't do ipv6. So we can't actually +// test ipv6 and hence kill this. +fn lookup_ipv4(host: &str, port: u16) -> std::net::SocketAddr { + use std::net::ToSocketAddrs; + + let addrs = (host, port).to_socket_addrs().unwrap(); + for addr in addrs { + if let std::net::SocketAddr::V4(_) = addr { + return addr; + } + } + + unreachable!("Cannot lookup address"); +} + +#[cfg(feature = "dangerous_configuration")] +mod danger { + use super::rustls; + + pub struct NoCertificateVerification {} + + impl rustls::client::ServerCertVerifier for NoCertificateVerification { + fn verify_server_cert( + &self, + _end_entity: &rustls::Certificate, + _intermediates: &[rustls::Certificate], + _server_name: &rustls::ServerName, + _scts: &mut dyn Iterator, + _ocsp: &[u8], + _now: std::time::SystemTime, + ) -> Result { + Ok(rustls::client::ServerCertVerified::assertion()) + } + } +} + +// #[cfg(feature = "dangerous_configuration")] +// fn apply_dangerous_options(args: &Args, cfg: &mut rustls::ClientConfig) { +// if args.flag_insecure { +// cfg.dangerous() +// .set_certificate_verifier(Arc::new(danger::NoCertificateVerification {})); +// } +// } + +// #[cfg(not(feature = "dangerous_configuration"))] +// fn apply_dangerous_options(args: &Args, _: &mut rustls::ClientConfig) { +// if args.flag_insecure { +// panic!("This build does not support --insecure."); +// } +// } + +/// Parse some arguments, then make a TLS client connection +/// somewhere. +fn main() { + let matches = App::new("alpaca-rustls") + .version("1.0") + .arg(Arg::with_name("cert") + .short("c") + .long("cert") + .takes_value(true)) + .arg(Arg::with_name("host") + .short("h") + .long("host") + .takes_value(true)) + .arg(Arg::with_name("servername") + .short("s") + .long("servername") + .takes_value(true)) + .arg(Arg::with_name("alpn") + .short("a") + .long("alpn") + .takes_value(true)) + .get_matches(); + + let cert = matches.value_of("cert").unwrap_or("/etc/ssl/cert-data/ca.crt"); + let host = matches.value_of("host").unwrap_or("127.0.0.1"); + let servername = matches.value_of("servername").unwrap_or("tls-server.com"); + let alpn = matches.value_of("alpn").unwrap_or("http/1.1"); + + println!("Parameters alpn={} servername={} cert={} host={} port={} ", alpn, servername, cert, host, "4433"); + + let addr = lookup_ipv4(host, 4433); + + let mut root_store = RootCertStore::empty(); + let certfile = fs::File::open(&cert).expect("Cannot open CA file"); + let mut reader = BufReader::new(certfile); + root_store.add_parsable_certificates(&rustls_pemfile::certs(&mut reader).unwrap()); + + let suites = rustls::ALL_CIPHER_SUITES.to_vec(); + let versions = rustls::DEFAULT_VERSIONS.to_vec(); + + let mut config = rustls::ClientConfig::builder() + .with_cipher_suites(&suites) + .with_safe_default_kx_groups() + .with_protocol_versions(&versions) + .expect("inconsistent cipher-suite/versions selected") + .with_root_certificates(root_store) + .with_no_client_auth(); + + config.key_log = Arc::new(rustls::KeyLogFile::new()); + + //config.enable_sni = false; + + config.alpn_protocols = vec![alpn.as_bytes().to_vec()]; + //config.max_fragment_size = args.flag_max_frag_size; + + //apply_dangerous_options(args, &mut config); + + let test = Arc::new(config); + + + let sock = TcpStream::connect(addr).unwrap(); + let dnslookup = rustls::ServerName::try_from(servername).expect("invalid DNS name"); + + // let server_name = args + // .arg_hostname + // .as_str() + // .try_into() + // .expect("invalid DNS name"); + let mut tlsclient = TlsClient::new(sock, dnslookup, test); + + let message = format!("Hello from Client!\n"); + tlsclient + .write_all(message.as_bytes()) + .unwrap(); + + let mut poll = mio::Poll::new().unwrap(); + let mut events = mio::Events::with_capacity(32); + tlsclient.register(poll.registry()); + + loop { + poll.poll(&mut events, None).unwrap(); + + for ev in events.iter() { + tlsclient.ready(&ev); + tlsclient.reregister(poll.registry()); + } + } +} \ No newline at end of file diff --git a/evaluation-libraries/rustls/docker-compose.yml b/evaluation-libraries/rustls/docker-compose.yml new file mode 100644 index 0000000..a61bf80 --- /dev/null +++ b/evaluation-libraries/rustls/docker-compose.yml @@ -0,0 +1,21 @@ +version: "3.9" +networks: + default: + name: tls-network + internal: true +services: + rustls-server: + image: tls-rustls + openssl-server-wrong-cn: + image: tls-openssl + command: [ "/openssl-server", "-k", "/etc/ssl/cert-data/wrong-cn.com.key", "-c" , "/etc/ssl/cert-data/wrong-cn.com-chain.crt"] + openssl-malicious-alpn: + image: tls-openssl + command: [ "/openssl-server", "-m"] + rustls-client: + image: tls-rustls + command: [ "./client.sh", "/client", "rustls-server", "openssl-server-wrong-cn", "openssl-malicious-alpn" ,"1"] + depends_on: + - rustls-server + - openssl-server-wrong-cn + - openssl-malicious-alpn diff --git a/evaluation-libraries/rustls/run.sh b/evaluation-libraries/rustls/run.sh new file mode 100755 index 0000000..b09b619 --- /dev/null +++ b/evaluation-libraries/rustls/run.sh @@ -0,0 +1,2 @@ +./build.sh +docker-compose up --abort-on-container-exit --exit-code-from rustls-client --remove-orphans diff --git a/evaluation-libraries/rustls/server/server.rs b/evaluation-libraries/rustls/server/server.rs new file mode 100644 index 0000000..284870f --- /dev/null +++ b/evaluation-libraries/rustls/server/server.rs @@ -0,0 +1,463 @@ +extern crate clap; +use clap::{Arg, App}; + +use std::sync::Arc; + +use mio; +use mio::net::{TcpListener, TcpStream}; +use std::collections::HashMap; + +use std::fs; +use std::io; +use std::io::{BufReader, Read, Write}; +use std::net; + + +use rustls; + +// Token for our listening socket. +const LISTENER: mio::Token = mio::Token(0); + +/// This binds together a TCP listening socket, some outstanding +/// connections, and a TLS server configuration. +struct TlsServer { + server: TcpListener, + connections: HashMap, + next_id: usize, + tls_config: Arc, +} + +impl TlsServer { + fn new(server: TcpListener, cfg: Arc) -> Self { + TlsServer { + server, + connections: HashMap::new(), + next_id: 2, + tls_config: cfg, + } + } + + fn accept(&mut self, registry: &mio::Registry) -> Result<(), io::Error> { + loop { + match self.server.accept() { + Ok((socket, addr)) => { + println!("Accepting new connection from {:?}", addr); + + let tls_conn = + rustls::ServerConnection::new(Arc::clone(&self.tls_config)).unwrap(); + + let token = mio::Token(self.next_id); + self.next_id += 1; + + let mut connection = OpenConnection::new(socket, token, tls_conn); + + connection.register(registry); + self.connections + .insert(token, connection); + } + Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => return Ok(()), + Err(err) => { + println!( + "encountered error while accepting connection; err={:?}", + err + ); + return Err(err); + } + } + } + } + + fn conn_event(&mut self, registry: &mio::Registry, event: &mio::event::Event) { + let token = event.token(); + + if self.connections.contains_key(&token) { + self.connections + .get_mut(&token) + .unwrap() + .ready(registry, event); + + if self.connections[&token].is_closed() { + self.connections.remove(&token); + } + } + } +} + +/// This is a connection which has been accepted by the server, +/// and is currently being served. +/// +/// It has a TCP-level stream, a TLS-level connection state, and some +/// other state/metadata. +struct OpenConnection { + socket: TcpStream, + token: mio::Token, + closing: bool, + closed: bool, + tls_conn: rustls::ServerConnection, + back: Option, +} + +/// Open a plaintext TCP-level connection for forwarded connections. +fn open_back() -> Option { + None +} + +/// This used to be conveniently exposed by mio: map EWOULDBLOCK +/// errors to something less-errory. +fn try_read(r: io::Result) -> io::Result> { + match r { + Ok(len) => Ok(Some(len)), + Err(e) => { + if e.kind() == io::ErrorKind::WouldBlock { + Ok(None) + } else { + Err(e) + } + } + } +} + +impl OpenConnection { + fn new( + socket: TcpStream, + token: mio::Token, + tls_conn: rustls::ServerConnection, + ) -> OpenConnection { + let back = open_back(); + OpenConnection { + socket, + token, + closing: false, + closed: false, + tls_conn, + back, + } + } + + /// We're a connection, and we have something to do. + fn ready(&mut self, registry: &mio::Registry, ev: &mio::event::Event) { + + let servername = rustls::ServerConnection::sni_hostname(&self.tls_conn); + + if servername.is_some() { + if servername.unwrap().eq("tls-server.com") { + println!("SNI :{}", servername.unwrap()); + } + else { + println!("INVALID SNI :{}", servername.unwrap()); + self.tls_conn.send_close_notify(); + let _ = self + .socket + .shutdown(net::Shutdown::Both); + self.close_back(); + self.closed = true; + //self.deregister(registry); + } + } + + // If we're readable: read some TLS. Then + // see if that yielded new plaintext. Then + // see if the backend is readable too. + if ev.is_readable() { + self.do_tls_read(); + self.try_plain_read(); + self.try_back_read(); + } + + if ev.is_writable() { + self.do_tls_write_and_handle_error(); + } + + if self.closing { + let _ = self + .socket + .shutdown(net::Shutdown::Both); + self.close_back(); + self.closed = true; + self.deregister(registry); + } else { + self.reregister(registry); + } + } + + // Close the backend connection for forwarded sessions. + fn close_back(&mut self) { + if self.back.is_some() { + let back = self.back.as_mut().unwrap(); + back.shutdown(net::Shutdown::Both) + .unwrap(); + } + self.back = None; + } + + fn do_tls_read(&mut self) { + // Read some TLS data. + match self.tls_conn.read_tls(&mut self.socket) { + Err(err) => { + if let io::ErrorKind::WouldBlock = err.kind() { + return; + } + + println!("read error {:?}", err); + self.closing = true; + return; + } + Ok(0) => { + println!("eof"); + self.closing = true; + return; + } + Ok(_) => {} + }; + + // Process newly-received TLS messages. + if let Err(err) = self.tls_conn.process_new_packets() { + println!("cannot process packet: {:?}", err); + + // last gasp write to send any alerts + self.do_tls_write_and_handle_error(); + + self.closing = true; + } + } + + fn try_plain_read(&mut self) { + // Read and process all available plaintext. + if let Ok(io_state) = self.tls_conn.process_new_packets() { + if io_state.plaintext_bytes_to_read() > 0 { + let mut buf = Vec::new(); + buf.resize(io_state.plaintext_bytes_to_read(), 0u8); + + self.tls_conn + .reader() + .read(&mut buf) + .unwrap(); + + println!("plaintext read {:?}", buf.len()); + self.incoming_plaintext(&buf); + } + } + } + + fn try_back_read(&mut self) { + if self.back.is_none() { + return; + } + + // Try a non-blocking read. + let mut buf = [0u8; 1024]; + let back = self.back.as_mut().unwrap(); + let rc = try_read(back.read(&mut buf)); + + if rc.is_err() { + println!("backend read failed: {:?}", rc); + self.closing = true; + return; + } + + let maybe_len = rc.unwrap(); + + // If we have a successful but empty read, that's an EOF. + // Otherwise, we shove the data into the TLS session. + match maybe_len { + Some(len) if len == 0 => { + println!("back eof"); + self.closing = true; + } + Some(len) => { + self.tls_conn + .writer() + .write_all(&buf[..len]) + .unwrap(); + } + None => {} + }; + } + + /// Process some amount of received plaintext. + fn incoming_plaintext(&mut self, _buf: &[u8]) { + // self.tls_conn + // .writer() + // .write_all(buf) + // .unwrap(); + + let response = b"Hello from Server!\n"; + self.tls_conn + .writer() + .write_all(response) + .unwrap(); + self.tls_conn.send_close_notify(); + } + + fn tls_write(&mut self) -> io::Result { + self.tls_conn.write_tls(&mut self.socket) + } + + fn do_tls_write_and_handle_error(&mut self) { + let rc = self.tls_write(); + if rc.is_err() { + println!("write failed {:?}", rc); + self.closing = true; + return; + } + } + + fn register(&mut self, registry: &mio::Registry) { + let event_set = self.event_set(); + registry + .register(&mut self.socket, self.token, event_set) + .unwrap(); + + if self.back.is_some() { + registry + .register( + self.back.as_mut().unwrap(), + self.token, + mio::Interest::READABLE, + ) + .unwrap(); + } + } + + fn reregister(&mut self, registry: &mio::Registry) { + let event_set = self.event_set(); + registry + .reregister(&mut self.socket, self.token, event_set) + .unwrap(); + } + + fn deregister(&mut self, registry: &mio::Registry) { + registry + .deregister(&mut self.socket) + .unwrap(); + + if self.back.is_some() { + registry + .deregister(self.back.as_mut().unwrap()) + .unwrap(); + } + } + + /// What IO events we're currently waiting for, + /// based on wants_read/wants_write. + fn event_set(&self) -> mio::Interest { + let rd = self.tls_conn.wants_read(); + let wr = self.tls_conn.wants_write(); + + if rd && wr { + mio::Interest::READABLE | mio::Interest::WRITABLE + } else if wr { + mio::Interest::WRITABLE + } else { + mio::Interest::READABLE + } + } + + fn is_closed(&self) -> bool { + self.closed + } +} + +fn load_certs(filename: &str) -> Vec { + let certfile = fs::File::open(filename).expect("cannot open certificate file"); + let mut reader = BufReader::new(certfile); + rustls_pemfile::certs(&mut reader) + .unwrap() + .iter() + .map(|v| rustls::Certificate(v.clone())) + .collect() +} + +fn load_private_key(filename: &str) -> rustls::PrivateKey { + let keyfile = fs::File::open(filename).expect("cannot open private key file"); + let mut reader = BufReader::new(keyfile); + + loop { + match rustls_pemfile::read_one(&mut reader).expect("cannot parse private key .pem file") { + Some(rustls_pemfile::Item::RSAKey(key)) => return rustls::PrivateKey(key), + Some(rustls_pemfile::Item::PKCS8Key(key)) => return rustls::PrivateKey(key), + None => break, + _ => {} + } + } + + panic!( + "no keys found in {:?} (encrypted keys not supported)", + filename + ); +} + +fn main() { + let matches = App::new("alpaca-rustls") + .version("1.0") + .author("tls-server author") + .arg(Arg::with_name("cert") + .short("c") + .long("cert") + .takes_value(true)) + .arg(Arg::with_name("key") + .short("k") + .long("key") + .takes_value(true)) + .get_matches(); + + let cert = matches.value_of("cert").unwrap_or("/etc/ssl/cert-data/tls-server.com-chain.crt"); + let key = matches.value_of("key").unwrap_or("/etc/ssl/cert-data/tls-server.com.key"); + + let addr: net::SocketAddr = "0.0.0.0:4433".parse().unwrap(); + + let client_auth = rustls::server::NoClientAuth::new(); + let suites = rustls::ALL_CIPHER_SUITES.to_vec(); + let versions = rustls::ALL_VERSIONS.to_vec(); + + let certs = load_certs(cert); + let privkey = load_private_key(key); + + let mut config = rustls::ServerConfig::builder() + .with_cipher_suites(&suites) + .with_safe_default_kx_groups() + .with_protocol_versions(&versions) + .expect("inconsistent cipher-suites/versions specified") + .with_client_cert_verifier(client_auth) + .with_single_cert(certs, privkey) + .expect("bad certificates/private key"); + + config.key_log = Arc::new(rustls::KeyLogFile::new()); + + // let protocols = Vec::new(); + // protocols.push("http/1.1"); + let protocols = "http/1.1"; + config.alpn_protocols = vec![protocols.as_bytes().to_vec()]; + + // config.alpn_protocols = args + // .flag_a + // .iter() + // .map(|a| a.as_bytes().to_vec()) + // .collect::>(); + + let test = Arc::new(config); + + let mut listener = TcpListener::bind(addr).expect("cannot listen on port"); + let mut poll = mio::Poll::new().unwrap(); + poll.registry() + .register(&mut listener, LISTENER, mio::Interest::READABLE) + .unwrap(); + + let mut tlsserv = TlsServer::new(listener, test); + + let mut events = mio::Events::with_capacity(256); + loop { + poll.poll(&mut events, None).unwrap(); + + for event in events.iter() { + match event.token() { + LISTENER => { + tlsserv + .accept(poll.registry()) + .expect("error accepting socket"); + } + _ => tlsserv.conn_event(poll.registry(), &event), + } + } + } +} \ No newline at end of file diff --git a/evaluation-libraries/wolfssl/CMakeLists.txt b/evaluation-libraries/wolfssl/CMakeLists.txt new file mode 100644 index 0000000..90eadb4 --- /dev/null +++ b/evaluation-libraries/wolfssl/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.0.0) +project(alpaca-wolfssl VERSION 0.1.0) + +add_subdirectory(client) +add_subdirectory(server) diff --git a/evaluation-libraries/wolfssl/Dockerfile b/evaluation-libraries/wolfssl/Dockerfile new file mode 100644 index 0000000..14384f9 --- /dev/null +++ b/evaluation-libraries/wolfssl/Dockerfile @@ -0,0 +1,19 @@ +# syntax=docker/dockerfile:1 +FROM tls-baseimage as tls-wolfssl +ARG VERSION=4.8.1-stable +WORKDIR /build +RUN git clone --depth=1 --branch=v${VERSION} https://github.com/wolfSSL/wolfssl +WORKDIR /build/wolfssl +RUN ./autogen.sh +RUN ./configure --prefix=/build/ --enable-static --enable-sni --enable-alpn +RUN make +WORKDIR /build +ADD server /build/server +ADD client /build/client +ADD CMakeLists.txt /build/CMakeLists.txt +RUN cmake . .. && make +RUN mv /build/server/server / +RUN mv /build/client/client / +COPY --from=tls-openssl /openssl-client /openssl-client +WORKDIR / +CMD ["/server"] diff --git a/evaluation-libraries/wolfssl/LICENSE b/evaluation-libraries/wolfssl/LICENSE new file mode 100644 index 0000000..5393a81 --- /dev/null +++ b/evaluation-libraries/wolfssl/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. \ No newline at end of file diff --git a/evaluation-libraries/wolfssl/README.md b/evaluation-libraries/wolfssl/README.md new file mode 100644 index 0000000..6567fdf --- /dev/null +++ b/evaluation-libraries/wolfssl/README.md @@ -0,0 +1,12 @@ +# wolfssl example with strict sni and strict alpn + +Tested with wolfssl 4.8.1-stable + +Based on https://github.com/wolfSSL/wolfssl-examples + +needs tls-baseimage already in docker + + +```bash +./run.sh +``` \ No newline at end of file diff --git a/evaluation-libraries/wolfssl/build.sh b/evaluation-libraries/wolfssl/build.sh new file mode 100755 index 0000000..368679d --- /dev/null +++ b/evaluation-libraries/wolfssl/build.sh @@ -0,0 +1 @@ +docker build --build-arg VERSION=4.8.1-stable . -t tls-wolfssl -f Dockerfile \ No newline at end of file diff --git a/evaluation-libraries/wolfssl/client/CMakeLists.txt b/evaluation-libraries/wolfssl/client/CMakeLists.txt new file mode 100644 index 0000000..a033c3e --- /dev/null +++ b/evaluation-libraries/wolfssl/client/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.0.0) +project(client VERSION 0.1.0) + +add_library(wolfssl STATIC IMPORTED) +set_target_properties(wolfssl PROPERTIES + IMPORTED_LOCATION "${CMAKE_SOURCE_DIR}/wolfssl/src/.libs/libwolfssl.a" + INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_SOURCE_DIR}/wolfssl" + ) +add_executable(client client.c) +target_link_libraries(client wolfssl m) +target_compile_options(client PRIVATE -Wall -Wextra) diff --git a/evaluation-libraries/wolfssl/client/client.c b/evaluation-libraries/wolfssl/client/client.c new file mode 100644 index 0000000..57db3ca --- /dev/null +++ b/evaluation-libraries/wolfssl/client/client.c @@ -0,0 +1,182 @@ +/* client-tls.c + * + * Copyright (C) 2006-2020 wolfSSL Inc. + * + * This file is part of wolfSSL. (formerly known as CyaSSL) + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ +#include +#include +#include + +/* wolfSSL */ +#include +#include + +#include "client.h" + +//#define HAVE_ALPN +//#define HAVE_SNI + +int main(int argc, char **argv) { + char *host = "localhost"; + char *servername = "tls-server.com"; + char *port = "4433"; + char *alpn = "http/1.1"; + char *cert = "/etc/ssl/certs/ca.crt"; + + /* Get commandline arguments */ + int opt; + while ((opt = getopt(argc, argv, "a:s:c:h:p")) != -1) { + switch (opt) { + case 'a': + alpn = optarg; + break; + case 's': + servername = optarg; + break; + case 'h': + host = optarg; + break; + case 'p': + port = optarg; + break; + case 'c': + cert = optarg; + break; + default: + fprintf(stderr, "Usage: %s [-a alpn] [-s servername] [-h ip] [-p port] [-c certificate] \n", argv[0]); + exit(EXIT_FAILURE); + } + } + printf("Parameters alpn=%s servername=%s cert=%s host=%s port=%s \n", alpn, servername, cert, host, port); + + int sockfd; + char buff[256]; + int len; + int ret; + + /* declare wolfSSL objects */ + WOLFSSL_CTX *ctx; + WOLFSSL *ssl; + + sockfd = tcp_connect(host, port); + + /* Initialize wolfSSL */ + if ((ret = wolfSSL_Init()) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: Failed to initialize the library\n"); + goto socket_cleanup; + } + + /* Create and initialize WOLFSSL_CTX */ + if ((ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())) == NULL) { + fprintf(stderr, "ERROR: failed to create WOLFSSL_CTX\n"); + ret = -1; + goto socket_cleanup; + } + + /* Load client certificates into WOLFSSL_CTX */ + if ((ret = wolfSSL_CTX_load_verify_locations(ctx, cert, NULL)) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to load %s, please check the file.\n", + cert); + goto ctx_cleanup; + } + + /* Create a WOLFSSL object */ + if ((ssl = wolfSSL_new(ctx)) == NULL) { + fprintf(stderr, "ERROR: failed to create WOLFSSL object\n"); + ret = -1; + goto ctx_cleanup; + } + + /* Attach wolfSSL to the socket */ + if ((ret = wolfSSL_set_fd(ssl, sockfd)) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: Failed to set the file descriptor\n"); + goto cleanup; + } + + /* set ALPN */ + if (wolfSSL_UseALPN(ssl, alpn, sizeof(alpn), WOLFSSL_ALPN_FAILED_ON_MISMATCH) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to set ALPN \n"); + goto cleanup; + } + + /* set SNI */ + ret = wolfSSL_UseSNI(ssl, WOLFSSL_SNI_HOST_NAME, servername, strlen(servername)); + if (ret != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to set SNI \n"); + goto cleanup; + } + + /* hostname verification */ + ret = wolfSSL_check_domain_name(ssl, servername); + if (ret != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: setting hostname verification \n"); + goto cleanup; + } + + /* Connect to wolfSSL on the server side */ + if ((ret = wolfSSL_connect(ssl)) != WOLFSSL_SUCCESS) { + char buffer[WOLFSSL_MAX_ERROR_SZ]; + int err = wolfSSL_get_error(ssl, ret); + char *error_string = wolfSSL_ERR_error_string(err, buffer); + fprintf(stderr, "wolfSSL_connect error = %s\n", error_string); + if (strstr(error_string, "Unrecognized protocol name Error") != NULL) { + ret = 120; + } + if (strstr(error_string, "peer subject name mismatch") != NULL) { + ret = 42; + } + goto cleanup; + } + + /* Send message to server */ + char *message = "Hello from Client!\n"; + strcpy(buff, message); + len = strlen(message); + if ((ret = wolfSSL_write(ssl, buff, len)) != len) { + fprintf(stderr, "ERROR: failed to write entire message\n"); + fprintf(stderr, "%d bytes of %d bytes were sent", ret, (int)len); + goto cleanup; + } + + /* Read the server data into our buff array */ + memset(buff, 0, sizeof(buff)); + if ((ret = wolfSSL_read(ssl, buff, sizeof(buff) - 1)) == -1) { + fprintf(stderr, "ERROR: failed to read\n"); + goto cleanup; + } + printf("%s\n", buff); + + /* Bidirectional shutdown */ + while (wolfSSL_shutdown(ssl) == SSL_SHUTDOWN_NOT_DONE) { + //printf("Shutdown not complete\n"); + } + + printf("Connection closed.\n"); + + ret = 0; + + /* Cleanup and return */ +cleanup: + wolfSSL_free(ssl); /* Free the wolfSSL object */ +ctx_cleanup: + wolfSSL_CTX_free(ctx); /* Free the wolfSSL context object */ + wolfSSL_Cleanup(); /* Cleanup the wolfSSL environment */ +socket_cleanup: + close(sockfd); /* Close the connection to the server */ + return ret; /* Return reporting a success */ +} \ No newline at end of file diff --git a/evaluation-libraries/wolfssl/client/client.h b/evaluation-libraries/wolfssl/client/client.h new file mode 100644 index 0000000..8aa1d7a --- /dev/null +++ b/evaluation-libraries/wolfssl/client/client.h @@ -0,0 +1,36 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int tcp_connect(const char *hostname, const char *port) { + int sockfd, portno; + struct sockaddr_in serv_addr; + struct hostent *server; + portno = atoi(port); + sockfd = socket(AF_INET, SOCK_STREAM, 0); + if (sockfd < 0) + printf("ERROR opening socket"); + server = gethostbyname(hostname); + if (server == NULL) { + fprintf(stderr, "ERROR, no such host\n"); + exit(0); + } + bzero((char *)&serv_addr, sizeof(serv_addr)); + serv_addr.sin_family = AF_INET; + bcopy((char *)server->h_addr, + (char *)&serv_addr.sin_addr.s_addr, + server->h_length); + serv_addr.sin_port = htons(portno); + int err = connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); + if (err < 0) { + fprintf(stderr, "Connect error\n"); + exit(1); + } + return sockfd; +} \ No newline at end of file diff --git a/evaluation-libraries/wolfssl/docker-compose.yml b/evaluation-libraries/wolfssl/docker-compose.yml new file mode 100644 index 0000000..a6790ef --- /dev/null +++ b/evaluation-libraries/wolfssl/docker-compose.yml @@ -0,0 +1,21 @@ +version: "3.9" +networks: + default: + name: tls-network + internal: true +services: + wolfssl-server: + image: tls-wolfssl + openssl-server-wrong-cn: + image: tls-openssl + command: [ "/openssl-server", "-k", "/etc/ssl/cert-data/wrong-cn.com.key", "-c" , "/etc/ssl/cert-data/wrong-cn.com-chain.crt"] + openssl-malicious-alpn: + image: tls-openssl + command: [ "/openssl-server", "-m"] + wolfssl-client: + image: tls-wolfssl + command: [ "./client.sh", "/client", "wolfssl-server", "openssl-server-wrong-cn", "openssl-malicious-alpn" ,"1"] + depends_on: + - wolfssl-server + - openssl-server-wrong-cn + - openssl-malicious-alpn diff --git a/evaluation-libraries/wolfssl/run.sh b/evaluation-libraries/wolfssl/run.sh new file mode 100755 index 0000000..b43d9c4 --- /dev/null +++ b/evaluation-libraries/wolfssl/run.sh @@ -0,0 +1,2 @@ +./build.sh +docker-compose up --abort-on-container-exit --exit-code-from wolfssl-client --remove-orphans diff --git a/evaluation-libraries/wolfssl/server/CMakeLists.txt b/evaluation-libraries/wolfssl/server/CMakeLists.txt new file mode 100644 index 0000000..714b088 --- /dev/null +++ b/evaluation-libraries/wolfssl/server/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.0.0) +project(server VERSION 0.1.0) + +add_library(wolfssl STATIC IMPORTED) +set_target_properties(wolfssl PROPERTIES + IMPORTED_LOCATION "${CMAKE_SOURCE_DIR}/wolfssl/src/.libs/libwolfssl.a" + INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_SOURCE_DIR}/wolfssl" + ) +add_executable(server server.c) +target_link_libraries(server wolfssl m) +target_compile_options(server PRIVATE -Wall -Wextra) diff --git a/evaluation-libraries/wolfssl/server/server.c b/evaluation-libraries/wolfssl/server/server.c new file mode 100644 index 0000000..1597ed7 --- /dev/null +++ b/evaluation-libraries/wolfssl/server/server.c @@ -0,0 +1,199 @@ +/* server-tls.c + * + * Copyright (C) 2006-2020 wolfSSL Inc. + * + * This file is part of wolfSSL. (formerly known as CyaSSL) + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include "server.h" + +#include +#include +#include + +/* wolfSSL */ +#include +#include + +int main(int argc, char **argv) { + char *servername = "tls-server.com"; + uint16_t port = 4433; + char *alpn = "http/1.1"; + char *cert = "/etc/ssl/cert-data/tls-server.com-chain.crt"; + char *key = "/etc/ssl/cert-data/tls-server.com.key"; + + int sockfd = SOCKET_INVALID; + int connd = SOCKET_INVALID; + struct sockaddr_in clientAddr; + socklen_t size = sizeof(clientAddr); + char buff[256]; + int len; + int ret; + const char *reply = "Hello from Server!\n"; + + /* Get commandline arguments */ + int opt; + while ((opt = getopt(argc, argv, "a:s:c:k:p")) != -1) { + switch (opt) { + case 'a': + alpn = optarg; + break; + case 's': + servername = optarg; + break; + case 'k': + key = optarg; + break; + case 'p': + port = strtol(optarg, NULL, 10); + break; + case 'c': + cert = optarg; + break; + default: + fprintf(stderr, "Usage: %s [-a alpn] [-s servername] [-k keyfile] [-p port] [-c certificate] \n", argv[0]); + exit(EXIT_FAILURE); + } + } + printf("Parameters alpn=%s servername=%s cert=%s key=%s port=%d \n", alpn, servername, cert, key, port); + + /* declare wolfSSL objects */ + WOLFSSL_CTX *ctx = NULL; + WOLFSSL *ssl = NULL; + + /* Initialize wolfSSL */ + wolfSSL_Init(); + + /* Create and initialize WOLFSSL_CTX */ + if ((ctx = wolfSSL_CTX_new(wolfTLSv1_2_server_method())) == NULL) { + fprintf(stderr, "ERROR: failed to create WOLFSSL_CTX\n"); + ret = -1; + goto exit; + } + + /* Load server certificates into WOLFSSL_CTX */ + if ((ret = wolfSSL_CTX_use_certificate_chain_file(ctx, cert)) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to load %s, please check the file.\n", cert); + goto exit; + } + + /* Load server key into WOLFSSL_CTX */ + if ((ret = wolfSSL_CTX_use_PrivateKey_file(ctx, key, SSL_FILETYPE_PEM)) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to load %s, please check the file.\n", key); + goto exit; + } + + /* set SNI */ + ret = wolfSSL_CTX_UseSNI(ctx, WOLFSSL_SNI_HOST_NAME, servername, strlen(servername)); + if (ret != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to set SNI \n"); + goto exit; + } + + sockfd = create_socket(port); + listen(sockfd, 1024); + + for (;;) { + fprintf(stderr, "Waiting for a connection...\n"); + + /* Accept client connections */ + if ((connd = accept(sockfd, (struct sockaddr *)&clientAddr, &size)) == -1) { + fprintf(stderr, "ERROR: failed to accept the connection\n"); + continue; + } + + /* Create a WOLFSSL object */ + if ((ssl = wolfSSL_new(ctx)) == NULL) { + fprintf(stderr, "ERROR: failed to create WOLFSSL object\n"); + continue; + } + + /* set ALPN */ + if (wolfSSL_UseALPN(ssl, alpn, sizeof(alpn), WOLFSSL_ALPN_FAILED_ON_MISMATCH) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to set ALPN \n"); + wolfSSL_shutdown(ssl); + continue; + } + + /* Attach wolfSSL to the socket */ + wolfSSL_set_fd(ssl, connd); + + /* Establish TLS connection */ + ret = wolfSSL_accept(ssl); + if (ret != WOLFSSL_SUCCESS) { + char buffer[WOLFSSL_MAX_ERROR_SZ]; + fprintf(stderr, "wolfSSL_accept error = %s\n", wolfSSL_ERR_error_string(wolfSSL_get_error(ssl, ret), buffer)); + wolfSSL_shutdown(ssl); + continue; + } + + //fprintf(stderr, "Client connected successfully \n"); + + /* Read the client data into our buff array */ + memset(buff, 0, sizeof(buff)); + if ((ret = wolfSSL_read(ssl, buff, sizeof(buff) - 1)) == -1) { + fprintf(stderr, "ERROR: failed to read\n"); + wolfSSL_shutdown(ssl); + continue; + } + + /* Print to stdout any data the client sends */ + fprintf(stderr, "%s\n", buff); + + /* Check for server shutdown command */ + if (strncmp(buff, "shutdown", 8) == 0) { + printf("Shutdown command issued!\n"); + wolfSSL_shutdown(ssl); + continue; + } + + /* Write our reply into buff */ + memset(buff, 0, sizeof(buff)); + memcpy(buff, reply, strlen(reply)); + len = strnlen(buff, sizeof(buff)); + + /* Reply back to the client */ + if ((ret = wolfSSL_write(ssl, buff, len)) != len) { + fprintf(stderr, "ERROR: failed to write\n"); + wolfSSL_shutdown(ssl); + continue; + } + + /* Notify the client that the connection is ending */ + wolfSSL_shutdown(ssl); + fprintf(stderr, "Connection closed.\n"); + + /* Cleanup after this connection */ + wolfSSL_free(ssl); /* Free the wolfSSL object */ + close(connd); /* Close the connection to the client */ + } + + ret = 0; +exit: + /* Cleanup and return */ + if (ssl) + wolfSSL_free(ssl); /* Free the wolfSSL object */ + if (connd != SOCKET_INVALID) + close(connd); /* Close the connection to the client */ + if (sockfd != SOCKET_INVALID) + close(sockfd); /* Close the socket listening for clients */ + if (ctx) + wolfSSL_CTX_free(ctx); /* Free the wolfSSL context object */ + wolfSSL_Cleanup(); /* Cleanup the wolfSSL environment */ + + return ret; /* Return reporting a success */ +} \ No newline at end of file diff --git a/evaluation-libraries/wolfssl/server/server.h b/evaluation-libraries/wolfssl/server/server.h new file mode 100644 index 0000000..5ceeae3 --- /dev/null +++ b/evaluation-libraries/wolfssl/server/server.h @@ -0,0 +1,24 @@ +#include +#include +#include +#include +#include + +int create_socket(uint16_t port) { + int listen_sd; + struct sockaddr_in sa_serv; + int optval = 1; + /* Socket operations */ + listen_sd = socket(AF_INET, SOCK_STREAM, 0); + + memset(&sa_serv, '\0', sizeof(sa_serv)); + sa_serv.sin_family = AF_INET; + sa_serv.sin_addr.s_addr = INADDR_ANY; + sa_serv.sin_port = htons(port); + + setsockopt(listen_sd, SOL_SOCKET, SO_REUSEADDR, (void *)&optval, sizeof(int)); + + bind(listen_sd, (struct sockaddr *)&sa_serv, sizeof(sa_serv)); + + return listen_sd; +} diff --git a/evaluation-servers/.gitignore b/evaluation-servers/.gitignore new file mode 100644 index 0000000..6b1c132 --- /dev/null +++ b/evaluation-servers/.gitignore @@ -0,0 +1,4 @@ +*tar.gz +httpd-* +nginx-* +sendmail-* \ No newline at end of file diff --git a/evaluation-servers/README.md b/evaluation-servers/README.md new file mode 100644 index 0000000..41ace9f --- /dev/null +++ b/evaluation-servers/README.md @@ -0,0 +1,35 @@ +# alpaca-server +Testing TLS Servers ALPN and SNI Implementation. + +Scans each Server with TLS-Scanner https://github.com/tls-attacker/TLS-Scanner + +## Requirements +- docker and docker-compose +- baseimage Docker containers from evaluation-libraries + +---------------- +## Running Servers +1. Build the baseimage from evaluation-libraries. +2. Build the TLS-Scanner container with ``./build.sh`` +3. Go into any subdirectory and do ``./run.sh`` + +## Server overview and versions tested +'strict' means the server rejects the connection if he doesn't recognize the ALPN or SNI sent. +| Server | ALPN |SNI | +| ------------- | ------------- | ------------- | +| apache 2.4.51 | not strict | not strict | +| nginx 1.21.4 | strict | not strict | +| lighttpd 1.4.63 | strict | not strict | +| postfix/smtpd 3.6.2 | ------------ | not strict | +| openSMTPD 6.8.0 | ------------ | ------------ | +| sendmail 8.17.1 | ------------ | ------------ | +| exim 4.95 | strict | ------------ | +| Courier 5.10 | strict | not strict | +| Dovecot 2.3.13 | ------------ | not strict | +| pure-ftpd 1.0.49 | ------------ | not strict | +| cyrus 3.4.2-1 | strict in master only https | not strict | +| ProFTPD 1.3.8rc2 | ------------ | strict | +| vsftpd 3.0.5 | strict | strict | +| filezilla server 1.1.0 | strict | not strict | + + diff --git a/evaluation-servers/apache/Dockerfile b/evaluation-servers/apache/Dockerfile new file mode 100644 index 0000000..1e63ce7 --- /dev/null +++ b/evaluation-servers/apache/Dockerfile @@ -0,0 +1,5 @@ +FROM httpd:2.4.51 as tls-apache +COPY ./apache.conf /usr/local/apache2/conf/httpd.conf +COPY --from=tls-baseimage /etc/ssl/cert-data/tls-server.com-chain.crt /usr/local/apache2/conf/server-chain.crt +COPY --from=tls-baseimage /etc/ssl/cert-data/tls-server.com.crt /usr/local/apache2/conf/server.crt +COPY --from=tls-baseimage /etc/ssl/cert-data/tls-server.com.key /usr/local/apache2/conf/server.key \ No newline at end of file diff --git a/evaluation-servers/apache/apache.conf b/evaluation-servers/apache/apache.conf new file mode 100644 index 0000000..d1629cd --- /dev/null +++ b/evaluation-servers/apache/apache.conf @@ -0,0 +1,45 @@ +LoadModule socache_shmcb_module modules/mod_socache_shmcb.so +LoadModule authn_file_module modules/mod_authn_file.so +LoadModule authn_core_module modules/mod_authn_core.so +LoadModule authz_host_module modules/mod_authz_host.so +LoadModule authz_groupfile_module modules/mod_authz_groupfile.so +LoadModule authz_core_module modules/mod_authz_core.so +LoadModule authz_user_module modules/mod_authz_user.so +LoadModule reqtimeout_module modules/mod_reqtimeout.so +LoadModule filter_module modules/mod_filter.so +LoadModule mime_module modules/mod_mime.so +LoadModule env_module modules/mod_env.so +LoadModule setenvif_module modules/mod_setenvif.so +LoadModule version_module modules/mod_version.so +LoadModule unixd_module modules/mod_unixd.so +LoadModule headers_module modules/mod_headers.so +LoadModule mpm_event_module modules/mod_mpm_event.so +LoadModule log_config_module modules/mod_log_config.so +LoadModule ssl_module modules/mod_ssl.so +LoadModule status_module modules/mod_status.so +LoadModule dir_module modules/mod_dir.so +LoadModule alias_module modules/mod_alias.so +LoadModule autoindex_module modules/mod_autoindex.so +LoadModule http2_module modules/mod_http2.so +Include conf/extra/httpd-ssl.conf + + +User daemon +Group daemon + + +ServerRoot "/usr/local/apache2" +ServerName tls-server:4433 + +SSLProtocol +TLSv1.2 +TLSv1.3 + +Listen 4433 + + Protocols h2 http/1.1 + DocumentRoot "/usr/local/apache2/htdocs" + ServerName tls-server + SSLEngine on + SSLCertificateFile /usr/local/apache2/conf/server.crt + SSLCertificateKeyFile /usr/local/apache2/conf/server.key + SSLCertificateChainFile /usr/local/apache2/conf/server-chain.crt + diff --git a/evaluation-servers/apache/build.sh b/evaluation-servers/apache/build.sh new file mode 100755 index 0000000..6178687 --- /dev/null +++ b/evaluation-servers/apache/build.sh @@ -0,0 +1 @@ +docker build . -t tls-apache diff --git a/evaluation-servers/apache/docker-compose.yml b/evaluation-servers/apache/docker-compose.yml new file mode 100644 index 0000000..ae5a710 --- /dev/null +++ b/evaluation-servers/apache/docker-compose.yml @@ -0,0 +1,13 @@ +version: "3.9" +networks: + default: + name: tls-network + internal: true +services: + httpd: + image: tls-apache + scanner: + image: tlsscanner + depends_on: + - httpd + command: [ "-connect", "httpd:4433", "-server_name", "tls-server.com", "-scanDetail", "QUICK"] diff --git a/evaluation-servers/apache/run.sh b/evaluation-servers/apache/run.sh new file mode 100755 index 0000000..36ab020 --- /dev/null +++ b/evaluation-servers/apache/run.sh @@ -0,0 +1,2 @@ +./build.sh +docker-compose up --remove-orphans diff --git a/evaluation-servers/build.sh b/evaluation-servers/build.sh new file mode 100755 index 0000000..318af17 --- /dev/null +++ b/evaluation-servers/build.sh @@ -0,0 +1,4 @@ +git clone https://github.com/tls-attacker/TLS-Scanner.git +cd TLS-Scanner +git submodule update --init --recursive +docker build . -t tlsscanner \ No newline at end of file diff --git a/evaluation-servers/courier/Dockerfile b/evaluation-servers/courier/Dockerfile new file mode 100644 index 0000000..1698580 --- /dev/null +++ b/evaluation-servers/courier/Dockerfile @@ -0,0 +1,40 @@ +FROM tls-baseimage-archlinux + +# Avoid ERROR: invoke-rc.d: policy-rc.d denied execution of start. +#RUN echo "#!/bin/sh\nexit 0" > /usr/sbin/policy-rc.d + +RUN pacman -Syu --noconfirm + +USER build + +## Version 1.1.5-2 + +WORKDIR /src/ +RUN git clone https://aur.archlinux.org/courier-unicode.git +WORKDIR /src/courier-unicode +RUN git checkout b08066fde2b4147076cb3201888fc2ee68eed19c +RUN makepkg -si --noconfirm + +WORKDIR /src/ +RUN git clone https://aur.archlinux.org/courier-authlib.git +WORKDIR /src/courier-authlib +RUN git checkout 125c9823c551500428857a503f2d4a3b795aa589 +RUN makepkg -si --noconfirm + +WORKDIR /src/ +RUN git clone https://aur.archlinux.org/courier-mta.git +WORKDIR /src/courier-mta +RUN git checkout 359ca3946091a4634d1c6aab60df2e079cdde08 +RUN makepkg -si --noconfirm + +USER root +#ARG DEBIAN_FRONTEND=noninteractive +#RUN apt-get update && apt-get install -yq courier-imap +RUN cp /etc/ssl/cert-data/tls-server.com-chain.crt /etc/courier/imapd.cert +RUN cp /etc/ssl/cert-data/tls-server.com-chain.crt /etc/courier/pop3d.cert +RUN cp /etc/ssl/cert-data/tls-server.com.key /etc/courier/imapd.key +RUN cp /etc/ssl/cert-data/tls-server.com.key /etc/courier/pop3d.key +ADD start.sh /root/ +ADD imapd-ssl /etc/courier/ +RUN chmod +x /root/start.sh +CMD ["/root/start.sh"] \ No newline at end of file diff --git a/evaluation-servers/courier/build.sh b/evaluation-servers/courier/build.sh new file mode 100755 index 0000000..a4c7ae1 --- /dev/null +++ b/evaluation-servers/courier/build.sh @@ -0,0 +1,2 @@ +docker build . -t tls-courier + diff --git a/evaluation-servers/courier/docker-compose.yml b/evaluation-servers/courier/docker-compose.yml new file mode 100644 index 0000000..e932164 --- /dev/null +++ b/evaluation-servers/courier/docker-compose.yml @@ -0,0 +1,13 @@ +version: "3.9" +networks: + default: + name: tls-network + internal: true +services: + courier: + image: tls-courier + scanner: + image: tlsscanner + depends_on: + - courier + command: [ "-connect", "courier:993", "-server_name", "tls-server.com", "-scanDetail", "QUICK"] \ No newline at end of file diff --git a/evaluation-servers/courier/imapd-ssl b/evaluation-servers/courier/imapd-ssl new file mode 100644 index 0000000..f53ef69 --- /dev/null +++ b/evaluation-servers/courier/imapd-ssl @@ -0,0 +1,331 @@ +##VERSION: $Id: f6b3b133f021939f6a08728c42dd07a35cf2c8fc-20210515145926$ +# +# imapd-ssl created from imapd-ssl.dist by sysconftool +# +# Do not alter lines that begin with ##, they are used when upgrading +# this configuration. +# +# Copyright 2000 - 2021 Double Precision, Inc. See COPYING for +# distribution information. +# +# This configuration file sets various options for the Courier-IMAP server +# when used to handle SSL IMAP connections. +# +# SSL and non-SSL connections are handled by a dedicated instance of the +# couriertcpd daemon. If you are accepting both SSL and non-SSL IMAP +# connections, you will start two instances of couriertcpd, one on the +# IMAP port 143, and another one on the IMAP-SSL port 993. +# +# Download OpenSSL from http://www.openssl.org/ +# +##NAME: SSLPORT:1 +# +# Options in the imapd-ssl configuration file AUGMENT the options in the +# imapd configuration file. First the imapd configuration file is read, +# then the imapd-ssl configuration file, so we do not have to redefine +# anything. +# +# However, some things do have to be redefined. The port number is +# specified by SSLPORT, instead of PORT. The default port is port 993. +# +# Multiple port numbers can be separated by commas. When multiple port +# numbers are used it is possibly to select a specific IP address for a +# given port as "ip.port". For example, "127.0.0.1.900,192.168.0.1.900" +# accepts connections on port 900 on IP addresses 127.0.0.1 and 192.168.0.1 +# The SSLADDRESS setting is a default for ports that do not have +# a specified IP address. + +SSLPORT=993 + +##NAME: SSLADDRESS:0 +# +# Address to listen on, can be set to a single IP address. +# +# SSLADDRESS=127.0.0.1 + +SSLADDRESS=0 + +##NAME: SSLPIDFILE:0 +# +# That's the SSL IMAP port we'll listen on. +# Feel free to redefine MAXDAEMONS, TCPDOPTS, and MAXPERIP. + +SSLPIDFILE=/run/courier/imapd-ssl.pid + +##NAME: SSLLOGGEROPTS:0 +# +# courierlogger(1) options. +# + +SSLLOGGEROPTS="-name=imapd-ssl" + +##NAME: IMAPDSSLSTART:0 +# +# Different pid files, so that both instances of couriertcpd can coexist +# happily. +# +# You can also redefine IMAP_CAPABILITY, although I can't +# think of why you'd want to do that. +# +# +# Ok, the following settings are new to imapd-ssl: +# +# Whether or not to start IMAP over SSL on simap port: + +IMAPDSSLSTART=NO + +##NAME: IMAPDSTARTTLS:0 +# +# Whether or not to implement IMAP STARTTLS extension instead: + +IMAPDSTARTTLS=YES + +##NAME: IMAP_TLS_REQUIRED:1 +# +# Set IMAP_TLS_REQUIRED to 1 if you REQUIRE STARTTLS for everyone. +# (this option advertises the LOGINDISABLED IMAP capability, until STARTTLS +# is issued). + +IMAP_TLS_REQUIRED=1 + + +######################################################################### +# +# The following variables configure IMAP over SSL. If OpenSSL or GnuTLS +# is available during configuration, the couriertls helper gets compiled, and +# upon installation a dummy TLS_CERTFILE gets generated. +# +# WARNING: Peer certificate verification has NOT yet been tested. Proceed +# at your own risk. Only the basic SSL/TLS functionality is known to be +# working. Keep this in mind as you play with the following variables. +# +##NAME: COURIERTLS:0 +# + +COURIERTLS=/usr/bin/couriertls + +##NAME: TLS_PRIORITY:0 +# +# GnuTLS setting only (use TLS_CIPHER_LIST for OpenSSL) +# +# Set TLS protocol priority settings +# +# DEFAULT: NORMAL:-CTYPE-OPENPGP +# +# This setting is also used to select the available ciphers. +# +# The actual list of available ciphers depend on the options GnuTLS was +# compiled against. The possible ciphers are: +# +# AES256, 3DES, AES128, ARC128, ARC40, RC2, DES, NULL +# +# Also, the following aliases: +# +# HIGH -- all ciphers that use more than a 128 bit key size +# MEDIUM -- all ciphers that use a 128 bit key size +# LOW -- all ciphers that use fewer than a 128 bit key size, the NULL cipher +# is not included +# ALL -- all ciphers except the NULL cipher +# +TLS_PRIORITY="NORMAL:-VERS-SSL3.0:-VERS-TLS1.0:-VERS-TLS1.1:+VERS-TLS1.2" +# See GnuTLS documentation, gnutls_priority_init(3) for additional +# documentation. + +##NAME: TLS_PROTOCOL:0 +# +# TLS_PROTOCOL sets the protocol version. The possible versions are: +# +# OpenSSL: +# +# TLSv1 - TLS 1.0, or higher. +# TLSv1.1 - TLS1.1, or higher. +# TLSv1.1++ TLS1.1, or higher, without client-initiated renegotiation. +# TLSv1.2 - TLS1.2, or higher. +# TLSv1.2++ TLS1.2, or higher, without client-initiated renegotiation. +# +# The default value is TLSv1 + +##NAME: TLS_CIPHER_LIST:0 +# +# OpenSSL only (use TLS_PRIORITY for GnuTLS): +# +# TLS_CIPHER_LIST optionally sets the list of ciphers to be used by the +# OpenSSL library. In most situations you can leave TLS_CIPHER_LIST +# undefined +# +# TLS_CIPHER_LIST="TLSv1:HIGH:!LOW:!MEDIUM:!EXP:!NULL:!aNULL@STRENGTH" +# +# See the OpenSSL ciphers(1) manual page for the format of this setting. + +##NAME: TLS_STARTTLS_PROTOCOL:0 +# +# TLS_STARTTLS_PROTOCOL is used instead of TLS_PROTOCOL for the IMAP STARTTLS +# extension, as opposed to IMAP over SSL on port 993. +# +# It takes the same values for OpenSSL as TLS_PROTOCOL +TLS_PROTOCOL="TLSv1.2" +TLS_STARTTLS_PROTOCOL="TLSv1.2" + +##NAME: TLS_MIN_DH_BITS:0 +# +# TLS_MIN_DH_BITS=n +# +# GnuTLS only: +# +# Set the minimum number of acceptable bits for a DH key exchange. +# +# GnuTLS's compiled-in default is 727 bits (as of GnuTLS 1.6.3). Some server +# have been encountered that offer 512 bit keys. You may have to set +# TLS_MIN_DH_BITS=512 here, if necessary. + +##NAME: TLS_TIMEOUT:0 +# TLS_TIMEOUT is currently not implemented, and reserved for future use. +# This is supposed to be an inactivity timeout, but its not yet implemented. +# + +##NAME: TLS_CERTFILE:0 +# +# TLS_CERTFILE - certificate to use. TLS_CERTFILE must be owned +# by the "courier" user, and must not be world-readable. +# +# VIRTUAL HOSTS ON THE SAME IP ADDRESS. +# +# Install each certificate $TLS_CERTFILE.domain, so if TLS_CERTFILE is set to +# /etc/certificate.pem, then you'll need to install the actual certificate +# files as /etc/certificate.pem.www.example.com, +# /etc/certificate.pem.www.domain.com and so on. Then, create a link from +# $TLS_CERTFILE to whichever certificate you consider to be the main one, +# for example: +# /etc/certificate.pem => /etc/certificate.pem.www.example.com +# +# IP-BASED VIRTUAL HOSTS: +# +# There may be a need to support older SSL/TLS client that don't support +# virtual hosts on the same IP address, and require a dedicated IP address +# for each SSL/TLS host. If so, install each certificate file as +# $TLS_CERTFILE.aaa.bbb.ccc.ddd, where "aaa.bbb.ccc.ddd" is the IP address +# for the certificate's domain name. So, if TLS_CERTFILE is set to +# /etc/certificate.pem, then you'll need to install the actual certificate +# files as /etc/certificate.pem.192.168.0.2, /etc/certificate.pem.192.168.0.3 +# and so on, for each IP address. +# +# In all cases, $TLS_CERTFILE needs to be linked to one of the existing +# certificate files. + +TLS_CERTFILE=/etc/ssl/cert-data/tls-server-chain.crt + +##NAME: TLS_PRIVATE_KEYFILE:0 +# +# TLS_PRIVATE_KEYFILE - SSL/TLS private key for decrypting peer data. +# This file must be owned by the "courier" user, and must not be world +# readable, and must be accessible without a pass-phrase, i.e. it must not +# be encrypted. +# +# By default, courier generates SSL/TLS certifice including private key +# and install it in TLS_CERTFILE path, so TLS_PRIVATE_KEYFILE is completely +# optional. If TLS_PRIVATE_KEYFILE is not set (default), TLS_CERTFILE is +# treated as certificate including private key file. +# +# If you get SSL/TLS certificate and private key from trusted certificate +# authority(CA) and want to install them separately, TLS_PRIVATE_KEYFILE can +# be used as private key file path setting. +# +# VIRTUAL HOSTS ON THE SAME IP ADDRESS. +# +# $TLS_PRIVATE_KEYFILE.domain and $TLS_CERTFILE.domain are a pair. +# If you use VIRTUAL HOST feature on TLS_CERTFILE setting, you must set pair +# private key as $TLS_PRIVATE_KEYFILE.domain. Then, create a link from +# $TLS_PRIVATE_KEYFILE to whichever private key you consider to be the main one. +# for example: +# /etc/tls_private_keyfile.pem => /etc/tls_private_keyfile.pem.www.example.com +# +# IP-BASED VIRTUAL HOSTS: +# +# Just described on "VIRTUAL HOSTS ON THE SAME IP ADDRESS" above, +# $TLS_PRIVATE_KEYFILE.aaa.bbb.ccc.ddd and $TLS_CERTFILE.aaa.bbb.ccc.ddd are +# a pair. If TLS_PRIVATE_KEYFILE is set to /etc/tls_private_keyfile.pem, +# then you'll need to install the actual certificate files as +# /etc/tls_private_keyfile.pem.192.168.0.2, /etc/tls_private_keyfile.192.168.0.3 +# and so on, for each IP address. +# +# In all cases, $TLS_PRIVATE_KEYFILE needs to be linked to one of the existing +# certificate files. +# +TLS_PRIVATE_KEYFILE=/etc/ssl/cert-data/tls-server.key + +##NAME: TLS_DHPARAMS:0 +# +# TLS_DHPARAMS - DH parameter file. +# +TLS_DHPARAMS=/usr/share/dhparams.pem + +##NAME: TLS_TRUSTCERTS:0 +# +# TLS_TRUSTCERTS=pathname - load trusted certificates from pathname. +# pathname can be a file or a directory. If a file, the file should +# contain a list of trusted certificates, in PEM format. If a +# directory, the directory should contain the trusted certificates, +# in PEM format, one per file and hashed using OpenSSL's c_rehash +# script. TLS_TRUSTCERTS is used by SSL/TLS clients (by specifying +# the -domain option) and by SSL/TLS servers (TLS_VERIFYPEER is set +# to PEER or REQUIREPEER). +# + +TLS_TRUSTCERTS=/etc/ssl/certs/ + +##NAME: TLS_VERIFYPEER:0 +# +# TLS_VERIFYPEER - how to verify client certificates. The possible values of +# this setting are: +# +# NONE - do not verify anything +# +# PEER - verify the client certificate, if one's presented +# +# REQUIREPEER - require a client certificate, fail if one's not presented +# +# +TLS_VERIFYPEER=NONE + +##NAME: TLS_EXTERNAL:0 +# +# To enable SSL certificate-based authentication: +# +# 1) TLS_TRUSTCERTS must be set to a pathname that holds your certificate +# authority's SSL certificate +# +# 2) TLS_VERIFYPEER=PEER or TLS_VERIFYPEER=REQUIREPEER (the later settings +# requires all SSL clients to present a certificate, and rejects +# SSL/TLS connections without a valid cert). +# +# 3) Set TLS_EXTERNAL, below, to the subject field that holds the login ID. +# Example: +# +# TLS_EXTERNAL=emailaddress +# +# The above example retrieves the login ID from the "emailaddress" subject +# field. The certificate's emailaddress subject must match exactly the login +# ID in the courier-authlib database. + +##NAME: TLS_CACHE:1 +# +# A TLS/SSL session cache may slightly improve response for IMAP clients +# that open multiple SSL sessions to the server. TLS_CACHEFILE will be +# automatically created, TLS_CACHESIZE bytes long, and used as a cache +# buffer. + +TLS_CACHEFILE=/var/spool/courier/couriersslimapcache +TLS_CACHESIZE=524288 + +##NAME: TLS_ALPN:0 +# +# Application level protocol negotiation should be enabled by default, and +# should be commented out only in case of compatibility issues. + +TLS_ALPN=imap + +##NAME: MAILDIRPATH:0 +# +# MAILDIRPATH - directory name of the maildir directory. +# +MAILDIRPATH=Maildir \ No newline at end of file diff --git a/evaluation-servers/courier/run.sh b/evaluation-servers/courier/run.sh new file mode 100755 index 0000000..36ab020 --- /dev/null +++ b/evaluation-servers/courier/run.sh @@ -0,0 +1,2 @@ +./build.sh +docker-compose up --remove-orphans diff --git a/evaluation-servers/courier/smtpd.conf b/evaluation-servers/courier/smtpd.conf new file mode 100644 index 0000000..2a9e45e --- /dev/null +++ b/evaluation-servers/courier/smtpd.conf @@ -0,0 +1,26 @@ +# $OpenBSD: smtpd.conf,v 1.10 2018/05/24 11:40:17 gilles Exp $ + +# This is the smtpd server system-wide configuration file. +# See smtpd.conf(5) for more information. + +table aliases file:/etc/aliases + +pki tls-server.com cert "/etc/ssl/cert-data/tls-server.com-chain.crt" +pki tls-server.com key "/etc/ssl/cert-data/tls-server.com.key" + +# To accept external mail, replace with: listen on all +# +#listen on 0.0.0.0 smtps + +listen on 0.0.0.0 tls pki tls-server.com +listen on 0.0.0.0 port 465 smtps pki tls-server.com +listen on 0.0.0.0 port 587 tls-require pki tls-server.com + +action "local" maildir alias +action "relay" relay + +# Uncomment the following to accept external mail for domain "example.org" +# +# match from any for domain "example.org" action "local" +match for local action "local" +match from local for any action "relay" \ No newline at end of file diff --git a/evaluation-servers/courier/start.sh b/evaluation-servers/courier/start.sh new file mode 100644 index 0000000..80fa888 --- /dev/null +++ b/evaluation-servers/courier/start.sh @@ -0,0 +1,9 @@ +#!/bin/bash +mkdir -p /run/courier/authdaemon +touch /run/courier/authdaemon/pid.lock +touch /run/courier/imapd-ssl.pid.lock +makeimapaccess +/usr/sbin/authdaemond start +/usr/sbin/imapd start +/usr/sbin/imapd-ssl start +while true; do sleep 1000; done \ No newline at end of file diff --git a/evaluation-servers/cyrus/Dockerfile b/evaluation-servers/cyrus/Dockerfile new file mode 100644 index 0000000..70ed1d4 --- /dev/null +++ b/evaluation-servers/cyrus/Dockerfile @@ -0,0 +1,40 @@ +FROM tls-baseimage-archlinux + +RUN pacman -Syu --noconfirm + +USER build + +#Fixed to Version 3.4.2 + +WORKDIR /src/ +RUN git clone https://aur.archlinux.org/perl-pod-pom.git +WORKDIR /src/perl-pod-pom +RUN git checkout 2699d4b77c2fb0573b1445968afaca4aa36299d4 +RUN makepkg -si --noconfirm + +WORKDIR /src/ +RUN git clone https://aur.archlinux.org/perl-pod-pom-view-restructured.git +WORKDIR /src/perl-pod-pom-view-restructured +RUN git checkout 80138742fbb711e3a56b4018ce4dcc7f0ec1b212 +RUN makepkg -si --noconfirm + +WORKDIR /src/ +RUN git clone https://aur.archlinux.org/cyrus-imapd.git +WORKDIR /src/cyrus-imapd +RUN git checkout a1e53ee172fc37431080dd3ff3685ffd2a2378e5 +#remove pgp key from PKGBUILD +RUN sed -i '/^validpgpkeys/d' PKGBUILD +RUN sed -i 's/{,.sig}//g' PKGBUILD +RUN sed -i '/'SKIP'/d' PKGBUILD +RUN makepkg -si --noconfirm + +USER root + +RUN echo "tls-server" >> /etc/hostname +RUN echo "tls-server 127.0.0.1" >> /etc/hosts + +ADD cyrus.conf /etc/cyrus/cyrus.conf +ADD imapd.conf /etc/cyrus/imapd.conf + +CMD ["/usr/lib/cyrus/master"] + diff --git a/evaluation-servers/cyrus/build.sh b/evaluation-servers/cyrus/build.sh new file mode 100755 index 0000000..4591b31 --- /dev/null +++ b/evaluation-servers/cyrus/build.sh @@ -0,0 +1,2 @@ +docker build . -t tls-cyrus + diff --git a/evaluation-servers/cyrus/cyrus.asc b/evaluation-servers/cyrus/cyrus.asc new file mode 100644 index 0000000..378b95b --- /dev/null +++ b/evaluation-servers/cyrus/cyrus.asc @@ -0,0 +1,17 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBFU5pZUBCAC+m05W9nJnBkrfFO9I+iimF1WCsSZNFoASJ3WEeZxIkOQO9BZj +aKf8EP/nK7nEfNGZ2m+OrAtQU/+I8Sk1ppHuwZgENLvRzLsBGbv80kDKBw31Nd1f +sCpVQs4b8zlohXjq0UN8tT5NcGJnGE7ahoOHzJk/0Ll76oVmOZvSw+WHBp1945m2 +Q8CbIbfmyuv7NF6GtGDVilPeIPsDnh5w5usjpKsxjYHKpy6Rtf4MbcCLtkRbHFra +KJD+xum0PgPdCAEEbQsSXQgwOd0TZ59avRVVef674PjWqIuudUGUhJ/f9OWOj7LG +6QgJR6yvCy7Bc2eAN4RnIIzaUZGaJDKDCNozABEBAAG0ImVsbGllIHRpbW9uZXkg +PGVsbGllQGZhc3RtYWlsLmNvbT6JATgEEwECACIFAlU5pZUCGwMGCwkIBwMCBhUI +AgkKCwQWAgMBAh4BAheAAAoJEFVPBP6zY3jgb9gH/3GPDLGybo7SYZMtBmfe+Udf +tcRkTtH+o2pf2rh6KwPhhEDuOXWVCIUPWXsWIVU2K5Y8AdBIHOEoSUp3n8juV57I +u9CfDI718/WaHgEpYrq5DqyROAFr+sGahcb6C40+V/CeUSAmKVhFGniuALUSAQ+B +XVj/i2EAFNg/5ALkPYDnDYDqm7Ak6odDbktYQz987y38sg3EMC/2wi2EoOG1VWeG +twFD8HKmXZw+u6cYtFh9K1hOBZm+PhLHr3h1MHTuWYeBKkT3YqaGtXMwi704LlNr +HU8beOHSNBSsVYJ61B4kgBA7p+qnx6xIpU2KfAJl8cgjCYwrq8yo+Lm9TazagfM= +=dIwC +-----END PGP PUBLIC KEY BLOCK----- \ No newline at end of file diff --git a/evaluation-servers/cyrus/cyrus.conf b/evaluation-servers/cyrus/cyrus.conf new file mode 100644 index 0000000..629c2f4 --- /dev/null +++ b/evaluation-servers/cyrus/cyrus.conf @@ -0,0 +1,55 @@ +# standard standalone server implementation + +START { + # do not delete this entry! + recover cmd="ctl_cyrusdb -r" +} + +# UNIX sockets start with a slash and are put into /run/cyrus/socket +SERVICES { + # add or remove based on preferences + imap cmd="imapd" listen="imap" prefork=0 + imaps cmd="imapd -s" listen="imaps" prefork=0 + #pop3 cmd="pop3d" listen="pop3" prefork=0 + #pop3s cmd="pop3d -s" listen="pop3s" prefork=0 + sieve cmd="timsieved" listen="sieve" prefork=0 + + # these are only necessary if receiving/exporting usenet via NNTP +# nntp cmd="nntpd" listen="nntp" prefork=0 +# nntps cmd="nntpd -s" listen="nntps" prefork=0 + + # these are only necessary if using HTTP for CalDAV, CardDAV, or RSS + #http cmd="httpd" listen="http" prefork=0 + https cmd="httpd -s" listen="https" prefork=0 + + # at least one LMTP is required for delivery +# lmtp cmd="lmtpd" listen="lmtp" prefork=0 + lmtpunix cmd="lmtpd" listen="/run/cyrus/socket/lmtp" prefork=0 + + # this is requied if using socketmap +# smmap cmd="smmapd" listen="/run/cyrus/socket/smmap" prefork=0 + + # this is required if using notifications +# notify cmd="notifyd" listen="/run/cyrus/socket/notify" proto="udp" prefork=1 +} + +EVENTS { + # this is required + checkpoint cmd="ctl_cyrusdb -c" period=30 + + # this is only necessary if using duplicate delivery suppression, + # Sieve or NNTP + delprune cmd="cyr_expire -E 3" at=0400 + + # Expire data older than 28 days. + deleteprune cmd="cyr_expire -E 4 -D 28" at=0430 + expungeprune cmd="cyr_expire -E 4 -X 28" at=0445 + + # this is only necessary if caching TLS sessions + tlsprune cmd="tls_prune" at=0400 +} + +DAEMON { + # this is only necessary if using idled for IMAP IDLE +# idled cmd="idled" +} \ No newline at end of file diff --git a/evaluation-servers/cyrus/docker-compose.yml b/evaluation-servers/cyrus/docker-compose.yml new file mode 100644 index 0000000..33f17c2 --- /dev/null +++ b/evaluation-servers/cyrus/docker-compose.yml @@ -0,0 +1,13 @@ +version: "3.9" +networks: + default: + name: tls-network + internal: true +services: + cyrus: + image: tls-cyrus + scanner: + image: tlsscanner + depends_on: + - cyrus + command: [ "-connect", "cyrus:993", "-server_name", "tls-server.com", "-scanDetail", "QUICK"] \ No newline at end of file diff --git a/evaluation-servers/cyrus/imapd.conf b/evaluation-servers/cyrus/imapd.conf new file mode 100644 index 0000000..88cea46 --- /dev/null +++ b/evaluation-servers/cyrus/imapd.conf @@ -0,0 +1,130 @@ +# Suggested minimal imapd.conf +# See imapd.conf(5) for more information and more options + +# Space-separated users who have admin rights for all services. +# NB: THIS MUST BE CONFIGURED +admins: cyrus + +################################################################### +## File, socket and DB location settings. +################################################################### + +# Configuration directory +configdirectory: /var/lib/cyrus + +# Directories for proc and lock files +proc_path: /run/cyrus/proc +mboxname_lockpath: /run/cyrus/lock + +# Locations for DB files +# The following DB are recreated upon initialization, so should live in +# ephemeral storage for best performance. +duplicate_db_path: /run/cyrus/deliver.db +ptscache_db_path: /run/cyrus/ptscache.db +statuscache_db_path: /run/cyrus/statuscache.db +tls_sessions_db_path: /run/cyrus/tls_sessions.db + +# Which partition to use for default mailboxes +defaultpartition: default +partition-default: /var/spool/cyrus/mail + +# If sieveusehomedir is false (the default), this directory is searched +# for Sieve scripts. +sievedir: /var/spool/sieve + +################################################################### +## Important: KEEP THESE IN SYNC WITH cyrus.conf +################################################################### + +lmtpsocket: /run/cyrus/socket/lmtp +idlesocket: /run/cyrus/socket/idle +notifysocket: /run/cyrus/socket/notify + +# Syslog prefix. Defaults to cyrus (so logging is done as cyrus/imap +# etc.) +syslog_prefix: cyrus + +################################################################### +## Server behaviour settings +################################################################### + +# Space-separated list of HTTP modules that will be enabled in +# httpd(8). This option has no effect on modules that are disabled at +# compile time due to missing dependencies (e.g. libical). +# +# Allowed values: caldav, carddav, domainkey, ischedule, rss +httpmodules: caldav carddav + +# If enabled, the partitions will also be hashed, in addition to the +# hashing done on configuration directories. This is recommended if one +# partition has a very bushy mailbox tree. +hashimapspool: true + +# Enable virtual domains +# and set default domain to localhost +virtdomains: yes +defaultdomain: tls-server + +# Use these credentials to run services +cyrus_user: cyrus +cyrus_group: mail + +################################################################### +## User experience settings +################################################################### + +# Minimum time between POP mail fetches in minutes +popminpoll: 1 + +################################################################### +## User Authentication settings +################################################################### + +# Allow plaintext logins by default (SASL PLAIN) +allowplaintext: no + +################################################################### +## SASL library options (these are handled directly by the SASL +## libraries, refer to SASL documentation for an up-to-date list of +## these) +################################################################### + +# The mechanism(s) used by the server to verify plaintext passwords. +# Possible values are "saslauthd", "auxprop", "pwcheck" and +# "alwaystrue". They are tried in order, you can specify more than one, +# separated by spaces. +sasl_pwcheck_method: saslauthd + +# If enabled, the SASL library will automatically create authentication +# secrets when given a plaintext password. Refer to SASL documentation +sasl_auto_transition: no + +################################################################### +## SSL/TLS Options +################################################################### + +# File containing the global certificate used for ALL services (imap, +# pop3, lmtp, sieve) +tls_server_cert: /etc/ssl/cert-data/tls-server-chain.crt + +# File containing the private key belonging to the global server +# certificate. +tls_server_key: /etc/ssl/cert-data/tls-server.key + + +# File containing one or more Certificate Authority (CA) certificates. +#tls_client_ca_file: /etc/ssl/certs/cyrus-imapd-ca.pem + +# Path to directory with certificates of CAs. +tls_client_ca_dir: /etc/ssl/certs + +# The length of time (in minutes) that a TLS session will be cached for +# later reuse. The maximum value is 1440 (24 hours), the default. A +# value of 0 will disable session caching. +tls_session_timeout: 1440 + +tls_ciphers: ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 +tls_required: 1 +tls_prefer_server_ciphers: 1 +tls_versions: tls1_2 tls1_3 +servername: tls-server diff --git a/evaluation-servers/cyrus/run.sh b/evaluation-servers/cyrus/run.sh new file mode 100755 index 0000000..36ab020 --- /dev/null +++ b/evaluation-servers/cyrus/run.sh @@ -0,0 +1,2 @@ +./build.sh +docker-compose up --remove-orphans diff --git a/evaluation-servers/dovecot/Dockerfile b/evaluation-servers/dovecot/Dockerfile new file mode 100644 index 0000000..1ae1bcc --- /dev/null +++ b/evaluation-servers/dovecot/Dockerfile @@ -0,0 +1,6 @@ +FROM tls-baseimagedebian +RUN apt-get update && apt-get install -y dovecot-imapd dovecot-pop3d +RUN cp /etc/ssl/cert-data/tls-server.com-chain.crt /etc/dovecot/private/dovecot.pem +RUN cp /etc/ssl/cert-data/tls-server.com.key /etc/dovecot/private/dovecot.key +RUN echo "ssl_min_protocol = TLSv1.2" >> /etc/dovecot/conf.d/10-ssl.conf +CMD ["dovecot", "-F"] \ No newline at end of file diff --git a/evaluation-servers/dovecot/build.sh b/evaluation-servers/dovecot/build.sh new file mode 100755 index 0000000..961bca6 --- /dev/null +++ b/evaluation-servers/dovecot/build.sh @@ -0,0 +1 @@ +docker build . -t tls-dovecot diff --git a/evaluation-servers/dovecot/docker-compose.yml b/evaluation-servers/dovecot/docker-compose.yml new file mode 100644 index 0000000..1051543 --- /dev/null +++ b/evaluation-servers/dovecot/docker-compose.yml @@ -0,0 +1,13 @@ +version: "3.9" +networks: + default: + name: tls-network + internal: true +services: + dovecot: + image: tls-dovecot + scanner: + image: tlsscanner + depends_on: + - dovecot + command: [ "-connect", "dovecot:993", "-server_name", "tls-server.com", "-scanDetail", "QUICK"] \ No newline at end of file diff --git a/evaluation-servers/dovecot/run.sh b/evaluation-servers/dovecot/run.sh new file mode 100755 index 0000000..36ab020 --- /dev/null +++ b/evaluation-servers/dovecot/run.sh @@ -0,0 +1,2 @@ +./build.sh +docker-compose up --remove-orphans diff --git a/evaluation-servers/exim/Dockerfile b/evaluation-servers/exim/Dockerfile new file mode 100644 index 0000000..46dc188 --- /dev/null +++ b/evaluation-servers/exim/Dockerfile @@ -0,0 +1,11 @@ +FROM tls-baseimage +RUN apk add exim +ADD exim.conf /etc/exim/exim.conf + +RUN chmod 777 /etc/ssl/cert-data/tls-server.com-chain.crt +RUN chmod 777 /etc/ssl/cert-data/tls-server.com.key + +USER exim +#CMD ["exim", "-bd", "-d-all+pid", "-q30m"] +ENTRYPOINT ["exim"] +CMD ["-bd", "-v", "-oP", "/dev/null"] \ No newline at end of file diff --git a/evaluation-servers/exim/build.sh b/evaluation-servers/exim/build.sh new file mode 100755 index 0000000..95e654c --- /dev/null +++ b/evaluation-servers/exim/build.sh @@ -0,0 +1 @@ +docker build . -t tls-exim diff --git a/evaluation-servers/exim/docker-compose.yml b/evaluation-servers/exim/docker-compose.yml new file mode 100644 index 0000000..d85b793 --- /dev/null +++ b/evaluation-servers/exim/docker-compose.yml @@ -0,0 +1,14 @@ +version: "3.9" +networks: + default: + name: tls-network + internal: true +services: + exim: + image: tls-exim + hostname: tls-exim + scanner: + image: tlsscanner + depends_on: + - exim + command: [ "-connect", "exim:465", "-server_name", "tls-server.com", "-scanDetail", "QUICK"] \ No newline at end of file diff --git a/evaluation-servers/exim/exim.conf b/evaluation-servers/exim/exim.conf new file mode 100644 index 0000000..52bc199 --- /dev/null +++ b/evaluation-servers/exim/exim.conf @@ -0,0 +1,1034 @@ +###################################################################### +# Runtime configuration file for Exim # +###################################################################### + + +# This is a default configuration file which will operate correctly in +# uncomplicated installations. Please see the manual for a complete list +# of all the runtime configuration options that can be included in a +# configuration file. There are many more than are mentioned here. The +# manual is in the file doc/spec.txt in the Exim distribution as a plain +# ASCII file. Other formats (PostScript, Texinfo, HTML, PDF) are available +# from the Exim ftp sites. The manual is also online at the Exim website. + + +# This file is divided into several parts, all but the first of which are +# headed by a line starting with the word "begin". Only those parts that +# are required need to be present. Blank lines, and lines starting with # +# are ignored. + + +########### IMPORTANT ########## IMPORTANT ########### IMPORTANT ########### +# # +# Whenever you change Exim's configuration file, you *must* remember to # +# HUP the Exim daemon, because it will not pick up the new configuration # +# until you do. However, any other Exim processes that are started, for # +# example, a process started by an MUA in order to send a message, will # +# see the new configuration as soon as it is in place. # +# # +# You do not need to HUP the daemon for changes in auxiliary files that # +# are referenced from this file. They are read every time they are used. # +# # +# It is usually a good idea to test a new configuration for syntactic # +# correctness before installing it (for example, by running the command # +# "exim -C /config/file.new -bV"). # +# # +########### IMPORTANT ########## IMPORTANT ########### IMPORTANT ########### + + + +###################################################################### +# MACROS # +###################################################################### +# + +# If you want to use a smarthost instead of sending directly to recipient +# domains, uncomment this macro definition and set a real hostname. +# An appropriately privileged user can then redirect email on the command-line +# in emergencies, via -D. +# +# ROUTER_SMARTHOST=MAIL.HOSTNAME.FOR.CENTRAL.SERVER.EXAMPLE + +###################################################################### +# MAIN CONFIGURATION SETTINGS # +###################################################################### +# + +# Specify your host's canonical name here. This should normally be the fully +# qualified "official" name of your host. If this option is not set, the +# uname() function is called to obtain the name. In many cases this does +# the right thing and you need not set anything explicitly. + +# primary_hostname = + + +# The next three settings create two lists of domains and one list of hosts. +# These lists are referred to later in this configuration using the syntax +# +local_domains, +relay_to_domains, and +relay_from_hosts, respectively. They +# are all colon-separated lists: + +domainlist local_domains = @ : tls-server.com +domainlist relay_to_domains = +hostlist relay_from_hosts = localhost +# (We rely upon hostname resolution working for localhost, because the default +# uncommented configuration needs to work in IPv4-only environments.) + +# Most straightforward access control requirements can be obtained by +# appropriate settings of the above options. In more complicated situations, +# you may need to modify the Access Control Lists (ACLs) which appear later in +# this file. + +# The first setting specifies your local domains, for example: +# +# domainlist local_domains = my.first.domain : my.second.domain +# +# You can use "@" to mean "the name of the local host", as in the default +# setting above. This is the name that is specified by primary_hostname, +# as specified above (or defaulted). If you do not want to do any local +# deliveries, remove the "@" from the setting above. If you want to accept mail +# addressed to your host's literal IP address, for example, mail addressed to +# "user@[192.168.23.44]", you can add "@[]" as an item in the local domains +# list. You also need to uncomment "allow_domain_literals" below. This is not +# recommended for today's Internet. + +# The second setting specifies domains for which your host is an incoming relay. +# If you are not doing any relaying, you should leave the list empty. However, +# if your host is an MX backup or gateway of some kind for some domains, you +# must set relay_to_domains to match those domains. For example: +# +# domainlist relay_to_domains = *.myco.com : my.friend.org +# +# This will allow any host to relay through your host to those domains. +# See the section of the manual entitled "Control of relaying" for more +# information. + +# The third setting specifies hosts that can use your host as an outgoing relay +# to any other host on the Internet. Such a setting commonly refers to a +# complete local network as well as the localhost. For example: +# +# hostlist relay_from_hosts = <; 127.0.0.1 ; ::1 ; 192.168.0.0/16 +# +# The "/16" is a bit mask (CIDR notation), not a number of hosts. Note that you +# have to include 127.0.0.1 if you want to allow processes on your host to send +# SMTP mail by using the loopback address. A number of MUAs use this method of +# sending mail. Often, connections are made to "localhost", which might be ::1 +# on IPv6-enabled hosts. Do not forget CIDR for your IPv6 networks. + +# All three of these lists may contain many different kinds of item, including +# wildcarded names, regular expressions, and file lookups. See the reference +# manual for details. The lists above are used in the access control lists for +# checking incoming messages. The names of these ACLs are defined here: + +acl_smtp_rcpt = acl_check_rcpt +.ifdef _HAVE_PRDR +acl_smtp_data_prdr = acl_check_prdr +.endif +acl_smtp_data = acl_check_data + +# You should not change those settings until you understand how ACLs work. + + +# If you are running a version of Exim that was compiled with the content- +# scanning extension, you can cause incoming messages to be automatically +# scanned for viruses. You have to modify the configuration in two places to +# set this up. The first of them is here, where you define the interface to +# your scanner. This example is typical for ClamAV; see the manual for details +# of what to set for other virus scanners. The second modification is in the +# acl_check_data access control list (see below). + +# av_scanner = clamd:/tmp/clamd + + +# For spam scanning, there is a similar option that defines the interface to +# SpamAssassin. You do not need to set this if you are using the default, which +# is shown in this commented example. As for virus scanning, you must also +# modify the acl_check_data access control list to enable spam scanning. + +# spamd_address = 127.0.0.1 783 + + +# If Exim is compiled with support for TLS, you may want to change the +# following option so that Exim disallows certain clients from makeing encrypted +# connections. The default is to allow all. +# In the authenticators section below, there are template configurations for +# plaintext username/password authentication. This kind of authentication is +# only safe when used within a TLS connection, so the authenticators will only +# work if TLS is allowed here. + +# This is equivalent to the default. + +tls_advertise_hosts = * + +# Specify the location of the Exim server's TLS certificate and private key. +# The private key must not be encrypted (password protected). You can put +# the certificate and private key in the same file, in which case you only +# need the first setting, or in separate files, in which case you need both +# options. + +tls_certificate = /etc/ssl/cert-data/tls-server.com-chain.crt +tls_privatekey = /etc/ssl/cert-data/tls-server.com.key + +# For OpenSSL, prefer EC- over RSA-authenticated ciphers +.ifdef _HAVE_OPENSSL +tls_require_ciphers = ECDSA:RSA:!COMPLEMENTOFDEFAULT +.endif + +# Don't offer resumption to (most) MUAs, who we don't want to reuse +# tickets. Once the TLS extension for vended ticket numbers comes +# though, re-examine since resumption on a single-use ticket is still a benefit. +.ifdef _HAVE_TLS_RESUME +tls_resumption_hosts = ${if inlist {$received_port}{587:465} {:}{*}} +.endif + +# In order to support roaming users who wish to send email from anywhere, +# you may want to make Exim listen on other ports as well as port 25, in +# case these users need to send email from a network that blocks port 25. +# The standard port for this purpose is port 587, the "message submission" +# port. See RFC 4409 for details. Microsoft MUAs cannot be configured to +# talk the message submission protocol correctly, so if you need to support +# them you should also allow TLS-on-connect on the traditional but +# non-standard port 465. + +daemon_smtp_ports = 25 : 465 : 587 +tls_on_connect_ports = 465 + + +# Specify the domain you want to be added to all unqualified addresses +# here. An unqualified address is one that does not contain an "@" character +# followed by a domain. For example, "caesar@rome.example" is a fully qualified +# address, but the string "caesar" (i.e. just a login name) is an unqualified +# email address. Unqualified addresses are accepted only from local callers by +# default. See the recipient_unqualified_hosts option if you want to permit +# unqualified addresses from remote sources. If this option is not set, the +# primary_hostname value is used for qualification. + +# qualify_domain = + + +# If you want unqualified recipient addresses to be qualified with a different +# domain to unqualified sender addresses, specify the recipient domain here. +# If this option is not set, the qualify_domain value is used. + +# qualify_recipient = + + +# The following line must be uncommented if you want Exim to recognize +# addresses of the form "user@[10.11.12.13]" that is, with a "domain literal" +# (an IP address) instead of a named domain. The RFCs still require this form, +# but it makes little sense to permit mail to be sent to specific hosts by +# their IP address in the modern Internet. This ancient format has been used +# by those seeking to abuse hosts by using them for unwanted relaying. If you +# really do want to support domain literals, uncomment the following line, and +# see also the "domain_literal" router below. + +# allow_domain_literals + + +# No deliveries will ever be run under the uids of users specified by +# never_users (a colon-separated list). An attempt to do so causes a panic +# error to be logged, and the delivery to be deferred. This is a paranoic +# safety catch. There is an even stronger safety catch in the form of the +# FIXED_NEVER_USERS setting in the configuration for building Exim. The list of +# users that it specifies is built into the binary, and cannot be changed. The +# option below just adds additional users to the list. The default for +# FIXED_NEVER_USERS is "root", but just to be absolutely sure, the default here +# is also "root". + +# Note that the default setting means you cannot deliver mail addressed to root +# as if it were a normal user. This isn't usually a problem, as most sites have +# an alias for root that redirects such mail to a human administrator. + +never_users = root + + +# The setting below causes Exim to do a reverse DNS lookup on all incoming +# IP calls, in order to get the true host name. If you feel this is too +# expensive, you can specify the networks for which a lookup is done, or +# remove the setting entirely. + +#host_lookup = + + +# The setting below causes Exim to try to initialize the system resolver +# library with DNSSEC support. It has no effect if your library lacks +# DNSSEC support. + +dns_dnssec_ok = 1 + + +# The settings below cause Exim to make RFC 1413 (ident) callbacks +# for all incoming SMTP calls. You can limit the hosts to which these +# calls are made, and/or change the timeout that is used. If you set +# the timeout to zero, all RFC 1413 calls are disabled. RFC 1413 calls +# are cheap and can provide useful information for tracing problem +# messages, but some hosts and firewalls have problems with them. +# This can result in a timeout instead of an immediate refused +# connection, leading to delays on starting up SMTP sessions. +# (The default was reduced from 30s to 5s for release 4.61. and to +# disabled for release 4.86) +# +#rfc1413_hosts = * +#rfc1413_query_timeout = 5s + + +# Enable an efficiency feature. We advertise the feature; clients +# may request to use it. For multi-recipient mails we then can +# reject or accept per-user after the message is received. +# This supports recipient-dependent content filtering; without it +# you have to temp-reject any recipients after the first that have +# incompatible filtering, and do the filtering in the data ACL. +# Even with this enabled, you must support the old style for peers +# not flagging support for PRDR (visible via $prdr_requested). +# +.ifdef _HAVE_PRDR +prdr_enable = true +.endif + + +# By default, Exim expects all envelope addresses to be fully qualified, that +# is, they must contain both a local part and a domain. If you want to accept +# unqualified addresses (just a local part) from certain hosts, you can specify +# these hosts by setting one or both of +# +# sender_unqualified_hosts = +# recipient_unqualified_hosts = +# +# to control sender and recipient addresses, respectively. When this is done, +# unqualified addresses are qualified using the settings of qualify_domain +# and/or qualify_recipient (see above). + + +# Unless you run a high-volume site you probably want more logging +# detail than the default. Adjust to suit. + +log_selector = +smtp_protocol_error +smtp_syntax_error \ + +tls_certificate_verified + + +# If you want Exim to support the "percent hack" for certain domains, +# uncomment the following line and provide a list of domains. The "percent +# hack" is the feature by which mail addressed to x%y@z (where z is one of +# the domains listed) is locally rerouted to x@y and sent on. If z is not one +# of the "percent hack" domains, x%y is treated as an ordinary local part. This +# hack is rarely needed nowadays; you should not enable it unless you are sure +# that you really need it. +# +# percent_hack_domains = +# +# As well as setting this option you will also need to remove the test +# for local parts containing % in the ACL definition below. + + +# When Exim can neither deliver a message nor return it to sender, it "freezes" +# the delivery error message (aka "bounce message"). There are also other +# circumstances in which messages get frozen. They will stay on the queue for +# ever unless one of the following options is set. + +# This option unfreezes frozen bounce messages after two days, tries +# once more to deliver them, and ignores any delivery failures. + +ignore_bounce_errors_after = 2d + +# This option cancels (removes) frozen messages that are older than a week. + +timeout_frozen_after = 7d + + +# By default, messages that are waiting on Exim's queue are all held in a +# single directory called "input" which is itself within Exim's spool +# directory. (The default spool directory is specified when Exim is built, and +# is often /var/spool/exim/.) Exim works best when its queue is kept short, but +# there are circumstances where this is not always possible. If you uncomment +# the setting below, messages on the queue are held in 62 subdirectories of +# "input" instead of all in the same directory. The subdirectories are called +# 0, 1, ... A, B, ... a, b, ... z. This has two benefits: (1) If your file +# system degrades with many files in one directory, this is less likely to +# happen; (2) Exim can process the queue one subdirectory at a time instead of +# all at once, which can give better performance with large queues. + +# split_spool_directory = true + + +# If you're in a part of the world where ASCII is not sufficient for most +# text, then you're probably familiar with RFC2047 message header extensions. +# By default, Exim adheres to the specification, including a limit of 76 +# characters to a line, with encoded words fitting within a line. +# If you wish to use decoded headers in message filters in such a way +# that successful decoding of malformed messages matters, you may wish to +# configure Exim to be more lenient. +# +# check_rfc2047_length = false +# +# In particular, the Exim maintainers have had multiple reports of problems +# from Russian administrators of issues until they disable this check, +# because of some popular, yet buggy, mail composition software. + + +# If you wish to be strictly RFC compliant, or if you know you'll be +# exchanging email with systems that are not 8-bit clean, then you may +# wish to disable advertising 8BITMIME. Uncomment this option to do so. + +# accept_8bitmime = false + + +# Exim does not make use of environment variables itself. However, +# libraries that Exim uses (e.g. LDAP) depend on specific environment settings. +# There are two lists: keep_environment for the variables we trust, and +# add_environment for variables we want to set to a specific value. +# Note that TZ is handled separately by the timezone runtime option +# and TIMEZONE_DEFAULT buildtime option. + +# keep_environment = ^LDAP +# add_environment = PATH=/usr/bin::/bin + + + +###################################################################### +# ACL CONFIGURATION # +# Specifies access control lists for incoming SMTP mail # +###################################################################### + +begin acl + +# This access control list is used for every RCPT command in an incoming +# SMTP message. The tests are run in order until the address is either +# accepted or denied. + +acl_check_rcpt: + + # Accept if the source is local SMTP (i.e. not over TCP/IP). We do this by + # testing for an empty sending host field. + + accept hosts = : + control = dkim_disable_verify + + ############################################################################# + # The following section of the ACL is concerned with local parts that contain + # @ or % or ! or / or | or dots in unusual places. + # + # The characters other than dots are rarely found in genuine local parts, but + # are often tried by people looking to circumvent relaying restrictions. + # Therefore, although they are valid in local parts, these rules lock them + # out, as a precaution. + # + # Empty components (two dots in a row) are not valid in RFC 2822, but Exim + # allows them because they have been encountered. (Consider local parts + # constructed as "firstinitial.secondinitial.familyname" when applied to + # someone like me, who has no second initial.) However, a local part starting + # with a dot or containing /../ can cause trouble if it is used as part of a + # file name (e.g. for a mailing list). This is also true for local parts that + # contain slashes. A pipe symbol can also be troublesome if the local part is + # incorporated unthinkingly into a shell command line. + # + # Two different rules are used. The first one is stricter, and is applied to + # messages that are addressed to one of the local domains handled by this + # host. The line "domains = +local_domains" restricts it to domains that are + # defined by the "domainlist local_domains" setting above. The rule blocks + # local parts that begin with a dot or contain @ % ! / or |. If you have + # local accounts that include these characters, you will have to modify this + # rule. + + deny message = Restricted characters in address + domains = +local_domains + local_parts = ^[.] : ^.*[@%!/|] + + # The second rule applies to all other domains, and is less strict. The line + # "domains = !+local_domains" restricts it to domains that are NOT defined by + # the "domainlist local_domains" setting above. The exclamation mark is a + # negating operator. This rule allows your own users to send outgoing + # messages to sites that use slashes and vertical bars in their local parts. + # It blocks local parts that begin with a dot, slash, or vertical bar, but + # allows these characters within the local part. However, the sequence /../ + # is barred. The use of @ % and ! is blocked, as before. The motivation here + # is to prevent your users (or your users' viruses) from mounting certain + # kinds of attack on remote sites. + + deny message = Restricted characters in address + domains = !+local_domains + local_parts = ^[./|] : ^.*[@%!] : ^.*/\\.\\./ + ############################################################################# + + # Accept mail to postmaster in any local domain, regardless of the source, + # and without verifying the sender. + + accept local_parts = postmaster + domains = +local_domains + + # Deny unless the sender address can be verified. + + require verify = sender + + # Reject all RCPT commands after too many bad recipients + # This is partly a defense against spam abuse and partly attacker abuse. + # Real senders should manage, by the time they get to 10 RCPT directives, + # to have had at least half of them be real addresses. + # + # This is a lightweight check and can protect you against repeated + # invocations of more heavy-weight checks which would come after it. + + deny condition = ${if and {\ + {>{$rcpt_count}{10}}\ + {<{$recipients_count}{${eval:$rcpt_count/2}}} }} + message = Rejected for too many bad recipients + logwrite = REJECT [$sender_host_address]: bad recipient count high [${eval:$rcpt_count-$recipients_count}] + + # Accept if the message comes from one of the hosts for which we are an + # outgoing relay. It is assumed that such hosts are most likely to be MUAs, + # so we set control=submission to make Exim treat the message as a + # submission. It will fix up various errors in the message, for example, the + # lack of a Date: header line. If you are actually relaying out out from + # MTAs, you may want to disable this. If you are handling both relaying from + # MTAs and submissions from MUAs you should probably split them into two + # lists, and handle them differently. + + # Recipient verification is omitted here, because in many cases the clients + # are dumb MUAs that don't cope well with SMTP error responses. If you are + # actually relaying out from MTAs, you should probably add recipient + # verification here. + + # Note that, by putting this test before any DNS black list checks, you will + # always accept from these hosts, even if they end up on a black list. The + # assumption is that they are your friends, and if they get onto a black + # list, it is a mistake. + + accept hosts = +relay_from_hosts + control = submission + control = dkim_disable_verify + + # Accept if the message arrived over an authenticated connection, from + # any host. Again, these messages are usually from MUAs, so recipient + # verification is omitted, and submission mode is set. And again, we do this + # check before any black list tests. + + accept authenticated = * + control = submission + control = dkim_disable_verify + + # Insist that any other recipient address that we accept is either in one of + # our local domains, or is in a domain for which we explicitly allow + # relaying. Any other domain is rejected as being unacceptable for relaying. + + require message = relay not permitted + domains = +local_domains : +relay_to_domains + + # We also require all accepted addresses to be verifiable. This check will + # do local part verification for local domains, but only check the domain + # for remote domains. The only way to check local parts for the remote + # relay domains is to use a callout (add /callout), but please read the + # documentation about callouts before doing this. + + require verify = recipient + + ############################################################################# + # There are no default checks on DNS black lists because the domains that + # contain these lists are changing all the time. However, here are two + # examples of how you can get Exim to perform a DNS black list lookup at this + # point. The first one denies, whereas the second just warns. + # + # deny dnslists = black.list.example + # message = rejected because $sender_host_address is in a black list at $dnslist_domain\n$dnslist_text + # + # warn dnslists = black.list.example + # add_header = X-Warning: $sender_host_address is in a black list at $dnslist_domain + # log_message = found in $dnslist_domain + ############################################################################# + + ############################################################################# + # This check is commented out because it is recognized that not every + # sysadmin will want to do it. If you enable it, the check performs + # Client SMTP Authorization (csa) checks on the sending host. These checks + # do DNS lookups for SRV records. The CSA proposal is currently (May 2005) + # an Internet draft. You can, of course, add additional conditions to this + # ACL statement to restrict the CSA checks to certain hosts only. + # + # require verify = csa + ############################################################################# + + ############################################################################# + # If doing per-user content filtering then recipients with filters different + # to the first recipient must be deferred unless the sender talks PRDR. + # + # defer !condition = $prdr_requested + # condition = ${if > {0}{$recipients_count}} + # condition = ${if !eq {$acl_m_content_filter} \ + # {${lookup PER_RCPT_CONTENT_FILTER}}} + # warn !condition = $prdr_requested + # condition = ${if > {0}{$recipients_count}} + # set acl_m_content_filter = ${lookup PER_RCPT_CONTENT_FILTER} + ############################################################################# + + # At this point, the address has passed all the checks that have been + # configured, so we accept it unconditionally. + + accept + + +# This ACL is used once per recipient, for multi-recipient messages, if +# we advertised PRDR. It can be used to perform receipient-dependent +# header- and body- based filtering and rejections. +# We set a variable to record that PRDR was active used, so that checking +# in the data ACL can be skipped. + +.ifdef _HAVE_PRDR +acl_check_prdr: + warn set acl_m_did_prdr = y + + ############################################################################# + # do lookup on filtering, with $local_part@$domain, deny on filter match + # + # deny set acl_m_content_filter = ${lookup PER_RCPT_CONTENT_FILTER} + # condition = ... + ############################################################################# + + accept +.endif + +# This ACL is used after the contents of a message have been received. This +# is the ACL in which you can test a message's headers or body, and in +# particular, this is where you can invoke external virus or spam scanners. +# Some suggested ways of configuring these tests are shown below, commented +# out. Without any tests, this ACL accepts all messages. If you want to use +# such tests, you must ensure that Exim is compiled with the content-scanning +# extension (WITH_CONTENT_SCAN=yes in Local/Makefile). + +acl_check_data: + + # Deny if the message contains an overlong line. Per the standards + # we should never receive one such via SMTP. + # + deny condition = ${if > {$max_received_linelength}{998}} + message = maximum allowed line length is 998 octets, \ + got $max_received_linelength + + # Deny if the headers contain badly-formed addresses. + # + deny !verify = header_syntax + message = header syntax + log_message = header syntax ($acl_verify_message) + + # Deny if the message contains a virus. Before enabling this check, you + # must install a virus scanner and set the av_scanner option above. + # + # deny malware = * + # message = This message contains a virus ($malware_name). + + # Add headers to a message if it is judged to be spam. Before enabling this, + # you must install SpamAssassin. You may also need to set the spamd_address + # option above. + # + # warn spam = nobody + # add_header = X-Spam_score: $spam_score\n\ + # X-Spam_score_int: $spam_score_int\n\ + # X-Spam_bar: $spam_bar\n\ + # X-Spam_report: $spam_report + + ############################################################################# + # No more tests if PRDR was actively used. + # accept condition = ${if def:acl_m_did_prdr} + # + # To get here, all message recipients must have identical per-user + # content filtering (enforced by RCPT ACL). Do lookup for filter + # and deny on match. + # + # deny set acl_m_content_filter = ${lookup PER_RCPT_CONTENT_FILTER} + # condition = ... + ############################################################################# + + + # Accept the message. + + accept + + + +###################################################################### +# ROUTERS CONFIGURATION # +# Specifies how addresses are handled # +###################################################################### +# THE ORDER IN WHICH THE ROUTERS ARE DEFINED IS IMPORTANT! # +# An address is passed to each router in turn until it is accepted. # +###################################################################### + +begin routers + +# This router routes to remote hosts over SMTP by explicit IP address, +# when an email address is given in "domain literal" form, for example, +# . The RFCs require this facility. However, it is +# little-known these days, and has been exploited by evil people seeking +# to abuse SMTP relays. Consequently it is commented out in the default +# configuration. If you uncomment this router, you also need to uncomment +# allow_domain_literals above, so that Exim can recognize the syntax of +# domain literal addresses. + +# domain_literal: +# driver = ipliteral +# domains = ! +local_domains +# transport = remote_smtp + + +# This router can be used when you want to send all mail to a +# server which handles DNS lookups for you; an ISP will typically run such +# a server for their customers. The hostname in route_data comes from the +# macro defined at the top of the file. If not defined, then we'll use the +# dnslookup router below instead. +# Beware that the hostname is specified again in the Transport. + +.ifdef ROUTER_SMARTHOST + +smarthost: + driver = manualroute + domains = ! +local_domains + transport = smarthost_smtp + route_data = ROUTER_SMARTHOST + ignore_target_hosts = <; 0.0.0.0 ; 127.0.0.0/8 ; ::1 + no_more + +.else + +# This router routes addresses that are not in local domains by doing a DNS +# lookup on the domain name. The exclamation mark that appears in "domains = ! +# +local_domains" is a negating operator, that is, it can be read as "not". The +# recipient's domain must not be one of those defined by "domainlist +# local_domains" above for this router to be used. +# +# If the router is used, any domain that resolves to 0.0.0.0 or to a loopback +# interface address (127.0.0.0/8) is treated as if it had no DNS entry. Note +# that 0.0.0.0 is the same as 0.0.0.0/32, which is commonly treated as the +# local host inside the network stack. It is not 0.0.0.0/0, the default route. +# If the DNS lookup fails, no further routers are tried because of the no_more +# setting, and consequently the address is unrouteable. + +dnslookup: + driver = dnslookup + domains = ! +local_domains + transport = remote_smtp + ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8 +# if ipv6-enabled then instead use: +# ignore_target_hosts = <; 0.0.0.0 ; 127.0.0.0/8 ; ::1 + no_more + +# This closes the ROUTER_SMARTHOST ifdef around the choice of routing for +# off-site mail. +.endif + + +# The remaining routers handle addresses in the local domain(s), that is those +# domains that are defined by "domainlist local_domains" above. + + +# This router handles aliasing using a linearly searched alias file with the +# name /etc/mail/aliases. When this configuration is installed automatically, +# the name gets inserted into this file from whatever is set in Exim's +# build-time configuration. The default path is the traditional /etc/mail/aliases. +# If you install this configuration by hand, you need to specify the correct +# path in the "data" setting below. +# +##### NB You must ensure that the alias file exists. It used to be the case +##### NB that every Unix had that file, because it was the Sendmail default. +##### NB These days, there are systems that don't have it. Your aliases +##### NB file should at least contain an alias for "postmaster". +# +# If any of your aliases expand to pipes or files, you will need to set +# up a user and a group for these deliveries to run under. You can do +# this by uncommenting the "user" option below (changing the user name +# as appropriate) and adding a "group" option if necessary. Alternatively, you +# can specify "user" on the transports that are used. Note that the transports +# listed below are the same as are used for .forward files; you might want +# to set up different ones for pipe and file deliveries from aliases. + +system_aliases: + driver = redirect + allow_fail + allow_defer + data = ${lookup{$local_part}lsearch{/etc/mail/aliases}} +# user = exim + file_transport = address_file + pipe_transport = address_pipe + + +# This router handles forwarding using traditional .forward files in users' +# home directories. If you want it also to allow mail filtering when a forward +# file starts with the string "# Exim filter" or "# Sieve filter", uncomment +# the "allow_filter" option. + +# The no_verify setting means that this router is skipped when Exim is +# verifying addresses. Similarly, no_expn means that this router is skipped if +# Exim is processing an EXPN command. + +# If you want this router to treat local parts with suffixes introduced by "-" +# or "+" characters as if the suffixes did not exist, uncomment the two local_ +# part_suffix options. Then, for example, xxxx-foo@your.domain will be treated +# in the same way as xxxx@your.domain by this router. Because this router is +# not used for verification, if you choose to uncomment those options, then you +# will *need* to make the same change to the localuser router. (There are +# other approaches, if this is undesirable, but they add complexity). + +# The check_ancestor option means that if the forward file generates an +# address that is an ancestor of the current one, the current one gets +# passed on instead. This covers the case where A is aliased to B and B +# has a .forward file pointing to A. + +# The three transports specified at the end are those that are used when +# forwarding generates a direct delivery to a file, or to a pipe, or sets +# up an auto-reply, respectively. + +userforward: + driver = redirect + check_local_user +# local_part_suffix = +* : -* +# local_part_suffix_optional + file = $home/.forward +# allow_filter + no_verify + no_expn + check_ancestor + file_transport = address_file + pipe_transport = address_pipe + reply_transport = address_reply + + +# This router matches local user mailboxes. If the router fails, the error +# message is "Unknown user". + +# If you want this router to treat local parts with suffixes introduced by "-" +# or "+" characters as if the suffixes did not exist, uncomment the two local_ +# part_suffix options. Then, for example, xxxx-foo@your.domain will be treated +# in the same way as xxxx@your.domain by this router. + +localuser: + driver = accept + check_local_user +# local_part_suffix = +* : -* +# local_part_suffix_optional + transport = local_delivery + cannot_route_message = Unknown user + + + +###################################################################### +# TRANSPORTS CONFIGURATION # +###################################################################### +# ORDER DOES NOT MATTER # +# Only one appropriate transport is called for each delivery. # +###################################################################### + +# A transport is used only when referenced from a router that successfully +# handles an address. + +begin transports + + +# This transport is used for delivering messages over SMTP connections. + +remote_smtp: + driver = smtp +.ifdef _HAVE_TLS_RESUME + tls_resumption_hosts = * +.endif + + +# This transport is used for delivering messages to a smarthost, if the +# smarthost router is enabled. This starts from the same basis as +# "remote_smtp" but then turns on various security options, because +# we assume that if you're told "use smarthost.example.org as the smarthost" +# then there will be TLS available, with a verifiable certificate for that +# hostname, using decent TLS. + +smarthost_smtp: + driver = smtp + multi_domain + # +.ifdef _HAVE_TLS + # Comment out any of these which you have to, then file a Support + # request with your smarthost provider to get things fixed: + hosts_require_tls = * + tls_verify_hosts = * + # As long as tls_verify_hosts is enabled, this this will have no effect, + # but if you have to comment it out then this will at least log whether + # you succeed or not: + tls_try_verify_hosts = * + # + # The SNI name should match the name which we'll expect to verify; + # many mail systems don't use SNI and this doesn't matter, but if it does, + # we need to send a name which the remote site will recognize. + # This _should_ be the name which the smarthost operators specified as + # the hostname for sending your mail to. + tls_sni = tls-server.com + + tls_alpn = "smtp" + hosts_require_alpn = * +.ifdef _HAVE_OPENSSL + tls_require_ciphers = HIGH:!MD5:!SHA1:-TLSv1.0:-SSLv3:-TLSv1.1 + #openssl_options = "+no_tlsv1_1" +.endif +.ifdef _HAVE_GNUTLS + tls_require_ciphers = SECURE192:-VERS-SSL3.0:-VERS-TLS1.0:-VERS-TLS1.1-TLS1.2 +.endif +.ifdef _HAVE_TLS_RESUME + tls_resumption_hosts = * +.endif +.endif + + + + +# This transport is used for local delivery to user mailboxes in traditional +# BSD mailbox format. By default it will be run under the uid and gid of the +# local user, and requires the sticky bit to be set on the /var/mail directory. +# Some systems use the alternative approach of running mail deliveries under a +# particular group instead of using the sticky bit. The commented options below +# show how this can be done. + +local_delivery: + driver = appendfile + file = /var/mail/$local_part_data + delivery_date_add + envelope_to_add + return_path_add +# group = mail +# mode = 0660 + + +# This transport is used for handling pipe deliveries generated by alias or +# .forward files. If the pipe generates any standard output, it is returned +# to the sender of the message as a delivery error. Set return_fail_output +# instead of return_output if you want this to happen only when the pipe fails +# to complete normally. You can set different transports for aliases and +# forwards if you want to - see the references to address_pipe in the routers +# section above. + +address_pipe: + driver = pipe + return_output + + +# This transport is used for handling deliveries directly to files that are +# generated by aliasing or forwarding. + +address_file: + driver = appendfile + delivery_date_add + envelope_to_add + return_path_add + + +# This transport is used for handling autoreplies generated by the filtering +# option of the userforward router. + +address_reply: + driver = autoreply + + + +###################################################################### +# RETRY CONFIGURATION # +###################################################################### + +begin retry + +# This single retry rule applies to all domains and all errors. It specifies +# retries every 15 minutes for 2 hours, then increasing retry intervals, +# starting at 1 hour and increasing each time by a factor of 1.5, up to 16 +# hours, then retries every 6 hours until 4 days have passed since the first +# failed delivery. + +# WARNING: If you do not have any retry rules at all (this section of the +# configuration is non-existent or empty), Exim will not do any retries of +# messages that fail to get delivered at the first attempt. The effect will +# be to treat temporary errors as permanent. Therefore, DO NOT remove this +# retry rule unless you really don't want any retries. + +# Address or Domain Error Retries +# ----------------- ----- ------- + +* * F,2h,15m; G,16h,1h,1.5; F,4d,6h + + + +###################################################################### +# REWRITE CONFIGURATION # +###################################################################### + +# There are no rewriting specifications in this default configuration file. + +begin rewrite + + + +###################################################################### +# AUTHENTICATION CONFIGURATION # +###################################################################### + +# The following authenticators support plaintext username/password +# authentication using the standard PLAIN mechanism and the traditional +# but non-standard LOGIN mechanism, with Exim acting as the server. +# PLAIN and LOGIN are enough to support most MUA software. +# +# These authenticators are not complete: you need to change the +# server_condition settings to specify how passwords are verified. +# They are set up to offer authentication to the client only if the +# connection is encrypted with TLS, so you also need to add support +# for TLS. See the global configuration options section at the start +# of this file for more about TLS. +# +# The default RCPT ACL checks for successful authentication, and will accept +# messages from authenticated users from anywhere on the Internet. + +# special sections always start with a "begin" +begin authenticators + + PLAIN: + # authentication protocol is plain text + driver = plaintext + # authentication is offered as plain text + public_name = PLAIN + server_prompts = : + # authentication is successful, if the password provided ($auth3) + # equals the password found in a lookup file for user ($auth2) + server_condition = ${if eq{$auth3}{${lookup{$auth2}dbm{/etc/authpwd}}}} + server_set_id = $auth2 + # only offer plain text authentication after TLS is been negotiated + server_advertise_condition = ${if def:tls_in_cipher} + +# PLAIN authentication has no server prompts. The client sends its +# credentials in one lump, containing an authorization ID (which we do not +# use), an authentication ID, and a password. The latter two appear as +# $auth2 and $auth3 in the configuration and should be checked against a +# valid username and password. In a real configuration you would typically +# use $auth2 as a lookup key, and compare $auth3 against the result of the +# lookup, perhaps using the crypteq{}{} condition. + +#PLAIN: +# driver = plaintext +# server_set_id = $auth2 +# server_prompts = : +# server_condition = Authentication is not yet configured +# server_advertise_condition = ${if def:tls_in_cipher } + +# LOGIN authentication has traditional prompts and responses. There is no +# authorization ID in this mechanism, so unlike PLAIN the username and +# password are $auth1 and $auth2. Apart from that you can use the same +# server_condition setting for both authenticators. + +#LOGIN: +# driver = plaintext +# server_set_id = $auth1 +# server_prompts = <| Username: | Password: +# server_condition = Authentication is not yet configured +# server_advertise_condition = ${if def:tls_in_cipher } + + +###################################################################### +# CONFIGURATION FOR local_scan() # +###################################################################### + +# If you have built Exim to include a local_scan() function that contains +# tables for private options, you can define those options here. Remember to +# uncomment the "begin" line. It is commented by default because it provokes +# an error with Exim binaries that are not built with LOCAL_SCAN_HAS_OPTIONS +# set in the Local/Makefile. + +# begin local_scan + + +# End of Exim configuration file \ No newline at end of file diff --git a/evaluation-servers/exim/run.sh b/evaluation-servers/exim/run.sh new file mode 100755 index 0000000..36ab020 --- /dev/null +++ b/evaluation-servers/exim/run.sh @@ -0,0 +1,2 @@ +./build.sh +docker-compose up --remove-orphans diff --git a/evaluation-servers/filezilla-server/Dockerfile b/evaluation-servers/filezilla-server/Dockerfile new file mode 100644 index 0000000..0595f87 --- /dev/null +++ b/evaluation-servers/filezilla-server/Dockerfile @@ -0,0 +1,27 @@ +FROM tls-baseimage + + +# RUN cat /etc/ssl/cert-data/tls-server.com.key >> /etc/ssl/private/pure-ftpd.pem +# RUN cat /etc/ssl/cert-data/tls-server.com-chain.crt >> /etc/ssl/private/pure-ftpd.pem +RUN apk add gnutls-dev wxgtk-dev pugixml-dev libexecinfo-dev + +ARG LIBVERSION=0.34.2 +WORKDIR /build +RUN wget https://download.filezilla-project.org/libfilezilla/libfilezilla-${LIBVERSION}.tar.bz2 +RUN tar -xvf libfilezilla-${LIBVERSION}.tar.bz2 +WORKDIR /build/libfilezilla-${LIBVERSION} +RUN ./configure --prefix=/usr +RUN make +RUN make install +#RUN mv lib/.libs/libfilezilla.a /usr/lib/ + +ARG VERSION=1.1.0 +WORKDIR /build +RUN wget https://download.filezilla-project.org/server/FileZilla_Server_${VERSION}_src.tar.bz2 +RUN tar -xvf FileZilla_Server_${VERSION}_src.tar.bz2 +WORKDIR /build/filezilla-server-${VERSION} +RUN ./configure --prefix=/usr +RUN make +RUN mv src/server/filezilla-server /bin/ + +CMD ["filezilla-server"] \ No newline at end of file diff --git a/evaluation-servers/filezilla-server/build.sh b/evaluation-servers/filezilla-server/build.sh new file mode 100755 index 0000000..94cd35e --- /dev/null +++ b/evaluation-servers/filezilla-server/build.sh @@ -0,0 +1 @@ +docker build . -t tls-filezillaserver \ No newline at end of file diff --git a/evaluation-servers/filezilla-server/docker-compose.yml b/evaluation-servers/filezilla-server/docker-compose.yml new file mode 100644 index 0000000..3f009af --- /dev/null +++ b/evaluation-servers/filezilla-server/docker-compose.yml @@ -0,0 +1,13 @@ +version: "3.9" +networks: + default: + name: tls-network + internal: true +services: + filezillaserver: + image: tls-filezillaserver + scanner: + image: tlsscanner + depends_on: + - filezillaserver + command: [ "-connect", "filezillaserver:21", "-server_name", "tls-server.com", "-starttls", "ftp", "-scanDetail", "QUICK"] \ No newline at end of file diff --git a/evaluation-servers/filezilla-server/run.sh b/evaluation-servers/filezilla-server/run.sh new file mode 100755 index 0000000..36ab020 --- /dev/null +++ b/evaluation-servers/filezilla-server/run.sh @@ -0,0 +1,2 @@ +./build.sh +docker-compose up --remove-orphans diff --git a/evaluation-servers/lighttpd/Dockerfile b/evaluation-servers/lighttpd/Dockerfile new file mode 100644 index 0000000..772f915 --- /dev/null +++ b/evaluation-servers/lighttpd/Dockerfile @@ -0,0 +1,20 @@ +FROM tls-openssl + + +# RUN cat /etc/ssl/cert-data/tls-server.com.key >> /etc/ssl/private/pure-ftpd.pem +# RUN cat /etc/ssl/cert-data/tls-server.com-chain.crt >> /etc/ssl/private/pure-ftpd.pem +RUN apk add pcre-dev zlib-dev bsd-compat-headers + +ARG VERSION=1.4.63 +WORKDIR /build +RUN wget https://download.lighttpd.net/lighttpd/releases-1.4.x/lighttpd-${VERSION}.tar.gz +RUN tar -xvf lighttpd-${VERSION}.tar.gz +WORKDIR /build/lighttpd-${VERSION} +RUN ./autogen.sh +RUN ./configure -C --with-openssl +RUN make check +RUN make install + +ADD lighttpd.conf /etc/lighttpd.conf + +CMD ["lighttpd", "-D", "-f", "/etc/lighttpd.conf"] \ No newline at end of file diff --git a/evaluation-servers/lighttpd/Dockerfile-mbedtls b/evaluation-servers/lighttpd/Dockerfile-mbedtls new file mode 100644 index 0000000..c6715d1 --- /dev/null +++ b/evaluation-servers/lighttpd/Dockerfile-mbedtls @@ -0,0 +1,20 @@ +FROM tls-baseimage + + +# RUN cat /etc/ssl/cert-data/tls-server.com.key >> /etc/ssl/private/pure-ftpd.pem +# RUN cat /etc/ssl/cert-data/tls-server.com-chain.crt >> /etc/ssl/private/pure-ftpd.pem +RUN apk add mbedtls-dev pcre-dev zlib-dev bsd-compat-headers + +ARG VERSION=1.4.63 +WORKDIR /build +RUN wget https://download.lighttpd.net/lighttpd/releases-1.4.x/lighttpd-${VERSION}.tar.gz +RUN tar -xvf lighttpd-${VERSION}.tar.gz +WORKDIR /build/lighttpd-${VERSION} +RUN ./autogen.sh +RUN ./configure -C --with-mbedtls +RUN make check +RUN make install + +ADD lighttpd-mbedtls.conf /etc/lighttpd.conf + +CMD ["lighttpd", "-D", "-f", "/etc/lighttpd.conf"] \ No newline at end of file diff --git a/evaluation-servers/lighttpd/build.sh b/evaluation-servers/lighttpd/build.sh new file mode 100755 index 0000000..6bf2cf4 --- /dev/null +++ b/evaluation-servers/lighttpd/build.sh @@ -0,0 +1,2 @@ +docker build . -t tls-lighttpd-openssl +docker build . -t tls-lighttpd-mbedtls -f Dockerfile-mbedtls \ No newline at end of file diff --git a/evaluation-servers/lighttpd/docker-compose.yml b/evaluation-servers/lighttpd/docker-compose.yml new file mode 100644 index 0000000..fae2731 --- /dev/null +++ b/evaluation-servers/lighttpd/docker-compose.yml @@ -0,0 +1,20 @@ +version: "3.9" +networks: + default: + name: tls-network + internal: true +services: + lighttpd-openssl: + image: tls-lighttpd-openssl + lighttpd-mbedtls: + image: tls-lighttpd-mbedtls + scanner: + image: tlsscanner + depends_on: + - lighttpd-openssl + command: [ "-connect", "lighttpd-openssl:443", "-server_name", "tls-server.com", "-scanDetail", "QUICK" ] + scanner2: + image: tlsscanner + depends_on: + - lighttpd-mbedtls + command: [ "-connect", "lighttpd-mbedtls:443", "-server_name", "tls-server.com", "-scanDetail", "QUICK" ] \ No newline at end of file diff --git a/evaluation-servers/lighttpd/lighttpd-mbedtls.conf b/evaluation-servers/lighttpd/lighttpd-mbedtls.conf new file mode 100644 index 0000000..1a7fff4 --- /dev/null +++ b/evaluation-servers/lighttpd/lighttpd-mbedtls.conf @@ -0,0 +1,53 @@ +############################################################################### +# Default lighttpd.conf for Gentoo. +# $Header: /var/cvsroot/gentoo-x86/www-servers/lighttpd/files/conf/lighttpd.conf,v 1.3 2005/09/01 14:22:35 ka0ttic Exp $ +############################################################################### + +# {{{ variables +var.basedir = "/var/www/localhost" +#var.logdir = "/var/log/lighttpd" +var.statedir = "/var/lib/lighttpd" +# }}} + +# {{{ modules +# At the very least, mod_access and mod_accesslog should be enabled. +# All other modules should only be loaded if necessary. +# NOTE: the order of modules is important. +server.modules = ( + + "mod_access", + "mod_mbedtls", + + "mod_accesslog" +) + +server.document-root = var.basedir + "/htdocs" +server.pid-file = "/run/lighttpd.pid" + + +server.indexfiles = ("index.php", "index.html", + "index.htm", "default.htm") + +server.follow-symlink = "enable" + +static-file.exclude-extensions = (".php", ".pl", ".cgi", ".fcgi") + +url.access-deny = ("~", ".inc") + +$SERVER["socket"] == ":443" { + ssl.engine = "enable" + ssl.pemfile = "/etc/ssl/cert-data/tls-server.com-chain.crt" + ssl.privkey = "/etc/ssl/cert-data/tls-server.com.key" + ssl.cipher-list = "EECDH+AESGCM:AES256+EECDH:CHACHA20:!SHA1:!SHA256:!SHA384" + $HTTP["host"] == "tls-server.com" { + ssl.pemfile = "/etc/ssl/cert-data/tls-server.com-chain.crt" + ssl.privkey = "/etc/ssl/cert-data/tls-server.com.key" + } +} + + + + +#ssl.openssl.ssl-conf-cmd = ("MinProtocol" => "TLSv1.2") +#ssl.openssl.ssl-conf-cmd += ("Options" => "-ServerPreference") +#ssl.openssl.ssl-conf-cmd += ("CipherString" => "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384") \ No newline at end of file diff --git a/evaluation-servers/lighttpd/lighttpd.conf b/evaluation-servers/lighttpd/lighttpd.conf new file mode 100644 index 0000000..b4a5d32 --- /dev/null +++ b/evaluation-servers/lighttpd/lighttpd.conf @@ -0,0 +1,51 @@ +############################################################################### +# Default lighttpd.conf for Gentoo. +# $Header: /var/cvsroot/gentoo-x86/www-servers/lighttpd/files/conf/lighttpd.conf,v 1.3 2005/09/01 14:22:35 ka0ttic Exp $ +############################################################################### + +# {{{ variables +var.basedir = "/var/www/localhost" +#var.logdir = "/var/log/lighttpd" +var.statedir = "/var/lib/lighttpd" +# }}} + +# {{{ modules +# At the very least, mod_access and mod_accesslog should be enabled. +# All other modules should only be loaded if necessary. +# NOTE: the order of modules is important. +server.modules = ( + + "mod_access", + "mod_openssl", + + "mod_accesslog" +) + +server.document-root = var.basedir + "/htdocs" +server.pid-file = "/run/lighttpd.pid" + + +server.indexfiles = ("index.php", "index.html", + "index.htm", "default.htm") + +server.follow-symlink = "enable" + +static-file.exclude-extensions = (".php", ".pl", ".cgi", ".fcgi") + +url.access-deny = ("~", ".inc") + +$SERVER["socket"] == ":443" { + ssl.engine = "enable" + ssl.pemfile = "/etc/ssl/cert-data/tls-server.com-chain.crt" + ssl.privkey = "/etc/ssl/cert-data/tls-server.com.key" + ssl.cipher-list = "EECDH+AESGCM:AES256+EECDH:CHACHA20:!SHA1:!SHA256:!SHA384" + $HTTP["host"] == "tls-server.com" { + ssl.pemfile = "/etc/ssl/cert-data/tls-server.com-chain.crt" + ssl.privkey = "/etc/ssl/cert-data/tls-server.com.key" + } +} + + +#ssl.openssl.ssl-conf-cmd = ("MinProtocol" => "TLSv1.2") +#ssl.openssl.ssl-conf-cmd += ("Options" => "-ServerPreference") +#ssl.openssl.ssl-conf-cmd += ("CipherString" => "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384") \ No newline at end of file diff --git a/evaluation-servers/lighttpd/run.sh b/evaluation-servers/lighttpd/run.sh new file mode 100755 index 0000000..36ab020 --- /dev/null +++ b/evaluation-servers/lighttpd/run.sh @@ -0,0 +1,2 @@ +./build.sh +docker-compose up --remove-orphans diff --git a/evaluation-servers/nginx/Dockerfile b/evaluation-servers/nginx/Dockerfile new file mode 100644 index 0000000..a15b2d1 --- /dev/null +++ b/evaluation-servers/nginx/Dockerfile @@ -0,0 +1,16 @@ +FROM tls-baseimage as tls-nginx +ARG VERSION=1.21.4 +RUN apk add pcre-dev openssl-dev +WORKDIR /build +RUN wget http://nginx.org/download/nginx-${VERSION}.tar.gz +RUN tar -xvf nginx-${VERSION}.tar.gz +WORKDIR /build/nginx-${VERSION} +#ADD patch.diff /build/nginx-${VERSION}/patch.diff +#RUN patch src/http/modules/ngx_http_ssl_module.c +RUN mkdir /usr/local/nginx +RUN mkdir /usr/local/nginx/logs +RUN ./configure --conf-path=/etc/nginx.conf --with-http_ssl_module --without-http_gzip_module --with-http_v2_module +RUN make +ADD nginx.conf /etc/nginx.conf +RUN mv objs/nginx /usr/bin/nginx +CMD ["nginx", "-g", "daemon off;"] diff --git a/evaluation-servers/nginx/build.sh b/evaluation-servers/nginx/build.sh new file mode 100755 index 0000000..b215b85 --- /dev/null +++ b/evaluation-servers/nginx/build.sh @@ -0,0 +1 @@ +docker build . -t tls-nginx diff --git a/evaluation-servers/nginx/docker-compose.yml b/evaluation-servers/nginx/docker-compose.yml new file mode 100644 index 0000000..bf3a141 --- /dev/null +++ b/evaluation-servers/nginx/docker-compose.yml @@ -0,0 +1,13 @@ +version: "3.9" +networks: + default: + name: tls-network + internal: true +services: + nginx: + image: tls-nginx + scanner: + image: tlsscanner + depends_on: + - nginx + command: [ "-connect", "nginx:4433", "-server_name", "tls-server.com", "-scanDetail", "QUICK"] diff --git a/evaluation-servers/nginx/nginx.conf b/evaluation-servers/nginx/nginx.conf new file mode 100644 index 0000000..3d6dc3c --- /dev/null +++ b/evaluation-servers/nginx/nginx.conf @@ -0,0 +1,20 @@ +worker_processes auto; + +events { + worker_connections 1024; +} + +http { + server { + listen 4433 ssl http2; + server_name tls-server.com; + + ssl_certificate /etc/ssl/cert-data/tls-server.com-chain.crt; + ssl_certificate_key /etc/ssl/cert-data/tls-server.com.key; + ssl_protocols TLSv1.2 TLSv1.3; + + location / { + return 404; + } + } +} \ No newline at end of file diff --git a/evaluation-servers/nginx/run.sh b/evaluation-servers/nginx/run.sh new file mode 100755 index 0000000..36ab020 --- /dev/null +++ b/evaluation-servers/nginx/run.sh @@ -0,0 +1,2 @@ +./build.sh +docker-compose up --remove-orphans diff --git a/evaluation-servers/opensmtpd/Dockerfile b/evaluation-servers/opensmtpd/Dockerfile new file mode 100644 index 0000000..7ee7485 --- /dev/null +++ b/evaluation-servers/opensmtpd/Dockerfile @@ -0,0 +1,6 @@ +FROM tls-baseimagedebian +ARG DEBIAN_FRONTEND=noninteractive +RUN apt-get update && apt-get install -yq opensmtpd +ADD smtpd.conf /etc/ +RUN chmod 600 /etc/ssl/cert-data/* +CMD ["smtpd", "-d"] \ No newline at end of file diff --git a/evaluation-servers/opensmtpd/build.sh b/evaluation-servers/opensmtpd/build.sh new file mode 100755 index 0000000..c475a43 --- /dev/null +++ b/evaluation-servers/opensmtpd/build.sh @@ -0,0 +1 @@ +docker build . -t tls-opensmtpd diff --git a/evaluation-servers/opensmtpd/docker-compose.yml b/evaluation-servers/opensmtpd/docker-compose.yml new file mode 100644 index 0000000..7b0df39 --- /dev/null +++ b/evaluation-servers/opensmtpd/docker-compose.yml @@ -0,0 +1,13 @@ +version: "3.9" +networks: + default: + name: tls-network + internal: true +services: + opensmtpd: + image: tls-opensmtpd + scanner: + image: tlsscanner + depends_on: + - opensmtpd + command: [ "-connect", "opensmtpd:465", "-server_name", "tls-server.com", "-scanDetail", "QUICK"] \ No newline at end of file diff --git a/evaluation-servers/opensmtpd/run.sh b/evaluation-servers/opensmtpd/run.sh new file mode 100755 index 0000000..36ab020 --- /dev/null +++ b/evaluation-servers/opensmtpd/run.sh @@ -0,0 +1,2 @@ +./build.sh +docker-compose up --remove-orphans diff --git a/evaluation-servers/opensmtpd/smtpd.conf b/evaluation-servers/opensmtpd/smtpd.conf new file mode 100644 index 0000000..2a9e45e --- /dev/null +++ b/evaluation-servers/opensmtpd/smtpd.conf @@ -0,0 +1,26 @@ +# $OpenBSD: smtpd.conf,v 1.10 2018/05/24 11:40:17 gilles Exp $ + +# This is the smtpd server system-wide configuration file. +# See smtpd.conf(5) for more information. + +table aliases file:/etc/aliases + +pki tls-server.com cert "/etc/ssl/cert-data/tls-server.com-chain.crt" +pki tls-server.com key "/etc/ssl/cert-data/tls-server.com.key" + +# To accept external mail, replace with: listen on all +# +#listen on 0.0.0.0 smtps + +listen on 0.0.0.0 tls pki tls-server.com +listen on 0.0.0.0 port 465 smtps pki tls-server.com +listen on 0.0.0.0 port 587 tls-require pki tls-server.com + +action "local" maildir alias +action "relay" relay + +# Uncomment the following to accept external mail for domain "example.org" +# +# match from any for domain "example.org" action "local" +match for local action "local" +match from local for any action "relay" \ No newline at end of file diff --git a/evaluation-servers/postfix/Dockerfile b/evaluation-servers/postfix/Dockerfile new file mode 100644 index 0000000..541e564 --- /dev/null +++ b/evaluation-servers/postfix/Dockerfile @@ -0,0 +1,6 @@ +FROM tls-baseimage as tls-apache +RUN apk add postfix +COPY main.cf /etc/postfix/ +COPY master.cf /etc/postfix/ + +CMD ["/usr/sbin/postfix", "start-fg"] \ No newline at end of file diff --git a/evaluation-servers/postfix/build.sh b/evaluation-servers/postfix/build.sh new file mode 100755 index 0000000..3ab1ecb --- /dev/null +++ b/evaluation-servers/postfix/build.sh @@ -0,0 +1 @@ +docker build . -t tls-postfix diff --git a/evaluation-servers/postfix/docker-compose.yml b/evaluation-servers/postfix/docker-compose.yml new file mode 100644 index 0000000..a572abd --- /dev/null +++ b/evaluation-servers/postfix/docker-compose.yml @@ -0,0 +1,13 @@ +version: "3.9" +networks: + default: + name: tls-network + internal: true +services: + postfix: + image: tls-postfix + scanner: + image: tlsscanner + depends_on: + - postfix + command: [ "-connect", "postfix:465", "-server_name", "tls-server.com", "-starttls", "SMTP", "-scanDetail", "QUICK"] \ No newline at end of file diff --git a/evaluation-servers/postfix/main.cf b/evaluation-servers/postfix/main.cf new file mode 100644 index 0000000..990b6a2 --- /dev/null +++ b/evaluation-servers/postfix/main.cf @@ -0,0 +1,686 @@ +# Global Postfix configuration file. This file lists only a subset +# of all parameters. For the syntax, and for a complete parameter +# list, see the postconf(5) manual page (command: "man 5 postconf"). +# +# For common configuration examples, see BASIC_CONFIGURATION_README +# and STANDARD_CONFIGURATION_README. To find these documents, use +# the command "postconf html_directory readme_directory", or go to +# http://www.postfix.org/BASIC_CONFIGURATION_README.html etc. +# +# For best results, change no more than 2-3 parameters at a time, +# and test if Postfix still works after every change. + +# COMPATIBILITY +# +# The compatibility_level determines what default settings Postfix +# will use for main.cf and master.cf settings. These defaults will +# change over time. +# +# To avoid breaking things, Postfix will use backwards-compatible +# default settings and log where it uses those old backwards-compatible +# default settings, until the system administrator has determined +# if any backwards-compatible default settings need to be made +# permanent in main.cf or master.cf. +# +# When this review is complete, update the compatibility_level setting +# below as recommended in the RELEASE_NOTES file. +# +# The level below is what should be used with new (not upgrade) installs. +# +compatibility_level = 3.6 + +# SOFT BOUNCE +# +# The soft_bounce parameter provides a limited safety net for +# testing. When soft_bounce is enabled, mail will remain queued that +# would otherwise bounce. This parameter disables locally-generated +# bounces, and prevents the SMTP server from rejecting mail permanently +# (by changing 5xx replies into 4xx replies). However, soft_bounce +# is no cure for address rewriting mistakes or mail routing mistakes. +# +#soft_bounce = no + +# LOCAL PATHNAME INFORMATION +# +# The queue_directory specifies the location of the Postfix queue. +# This is also the root directory of Postfix daemons that run chrooted. +# See the files in examples/chroot-setup for setting up Postfix chroot +# environments on different UNIX systems. +# +queue_directory = /var/spool/postfix + +# The command_directory parameter specifies the location of all +# postXXX commands. +# +command_directory = /usr/sbin + +# The daemon_directory parameter specifies the location of all Postfix +# daemon programs (i.e. programs listed in the master.cf file). This +# directory must be owned by root. +# +daemon_directory = /usr/libexec/postfix + +# The data_directory parameter specifies the location of Postfix-writable +# data files (caches, random numbers). This directory must be owned +# by the mail_owner account (see below). +# +data_directory = /var/lib/postfix + +# QUEUE AND PROCESS OWNERSHIP +# +# The mail_owner parameter specifies the owner of the Postfix queue +# and of most Postfix daemon processes. Specify the name of a user +# account THAT DOES NOT SHARE ITS USER OR GROUP ID WITH OTHER ACCOUNTS +# AND THAT OWNS NO OTHER FILES OR PROCESSES ON THE SYSTEM. In +# particular, don't specify nobody or daemon. PLEASE USE A DEDICATED +# USER. +# +mail_owner = postfix + +# The default_privs parameter specifies the default rights used by +# the local delivery agent for delivery to external file or command. +# These rights are used in the absence of a recipient user context. +# DO NOT SPECIFY A PRIVILEGED USER OR THE POSTFIX OWNER. +# +#default_privs = nobody + +# INTERNET HOST AND DOMAIN NAMES +# +# The myhostname parameter specifies the internet hostname of this +# mail system. The default is to use the fully-qualified domain name +# from gethostname(). $myhostname is used as a default value for many +# other configuration parameters. +# +#myhostname = host.domain.tld +#myhostname = virtual.domain.tld + +# The mydomain parameter specifies the local internet domain name. +# The default is to use $myhostname minus the first component. +# $mydomain is used as a default value for many other configuration +# parameters. +# +#mydomain = domain.tld + +# SENDING MAIL +# +# The myorigin parameter specifies the domain that locally-posted +# mail appears to come from. The default is to append $myhostname, +# which is fine for small sites. If you run a domain with multiple +# machines, you should (1) change this to $mydomain and (2) set up +# a domain-wide alias database that aliases each user to +# user@that.users.mailhost. +# +# For the sake of consistency between sender and recipient addresses, +# myorigin also specifies the default domain name that is appended +# to recipient addresses that have no @domain part. +# +#myorigin = $myhostname +#myorigin = $mydomain + +# RECEIVING MAIL + +# The inet_interfaces parameter specifies the network interface +# addresses that this mail system receives mail on. By default, +# the software claims all active interfaces on the machine. The +# parameter also controls delivery of mail to user@[ip.address]. +# +# See also the proxy_interfaces parameter, for network addresses that +# are forwarded to us via a proxy or network address translator. +# +# Note: you need to stop/start Postfix when this parameter changes. +# +#inet_interfaces = all +#inet_interfaces = $myhostname +#inet_interfaces = $myhostname, localhost + +# The proxy_interfaces parameter specifies the network interface +# addresses that this mail system receives mail on by way of a +# proxy or network address translation unit. This setting extends +# the address list specified with the inet_interfaces parameter. +# +# You must specify your proxy/NAT addresses when your system is a +# backup MX host for other domains, otherwise mail delivery loops +# will happen when the primary MX host is down. +# +#proxy_interfaces = +#proxy_interfaces = 1.2.3.4 + +# The mydestination parameter specifies the list of domains that this +# machine considers itself the final destination for. +# +# These domains are routed to the delivery agent specified with the +# local_transport parameter setting. By default, that is the UNIX +# compatible delivery agent that lookups all recipients in /etc/passwd +# and /etc/aliases or their equivalent. +# +# The default is $myhostname + localhost.$mydomain + localhost. On +# a mail domain gateway, you should also include $mydomain. +# +# Do not specify the names of virtual domains - those domains are +# specified elsewhere (see VIRTUAL_README). +# +# Do not specify the names of domains that this machine is backup MX +# host for. Specify those names via the relay_domains settings for +# the SMTP server, or use permit_mx_backup if you are lazy (see +# STANDARD_CONFIGURATION_README). +# +# The local machine is always the final destination for mail addressed +# to user@[the.net.work.address] of an interface that the mail system +# receives mail on (see the inet_interfaces parameter). +# +# Specify a list of host or domain names, /file/name or type:table +# patterns, separated by commas and/or whitespace. A /file/name +# pattern is replaced by its contents; a type:table is matched when +# a name matches a lookup key (the right-hand side is ignored). +# Continue long lines by starting the next line with whitespace. +# +# See also below, section "REJECTING MAIL FOR UNKNOWN LOCAL USERS". +# +#mydestination = $myhostname, localhost.$mydomain, localhost +#mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain +#mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain, +# mail.$mydomain, www.$mydomain, ftp.$mydomain + +# REJECTING MAIL FOR UNKNOWN LOCAL USERS +# +# The local_recipient_maps parameter specifies optional lookup tables +# with all names or addresses of users that are local with respect +# to $mydestination, $inet_interfaces or $proxy_interfaces. +# +# If this parameter is defined, then the SMTP server will reject +# mail for unknown local users. This parameter is defined by default. +# +# To turn off local recipient checking in the SMTP server, specify +# local_recipient_maps = (i.e. empty). +# +# The default setting assumes that you use the default Postfix local +# delivery agent for local delivery. You need to update the +# local_recipient_maps setting if: +# +# - You define $mydestination domain recipients in files other than +# /etc/passwd, /etc/aliases, or the $virtual_alias_maps files. +# For example, you define $mydestination domain recipients in +# the $virtual_mailbox_maps files. +# +# - You redefine the local delivery agent in master.cf. +# +# - You redefine the "local_transport" setting in main.cf. +# +# - You use the "luser_relay", "mailbox_transport", or "fallback_transport" +# feature of the Postfix local delivery agent (see local(8)). +# +# Details are described in the LOCAL_RECIPIENT_README file. +# +# Beware: if the Postfix SMTP server runs chrooted, you probably have +# to access the passwd file via the proxymap service, in order to +# overcome chroot restrictions. The alternative, having a copy of +# the system passwd file in the chroot jail is just not practical. +# +# The right-hand side of the lookup tables is conveniently ignored. +# In the left-hand side, specify a bare username, an @domain.tld +# wild-card, or specify a user@domain.tld address. +# +#local_recipient_maps = unix:passwd.byname $alias_maps +#local_recipient_maps = proxy:unix:passwd.byname $alias_maps +#local_recipient_maps = + +# The unknown_local_recipient_reject_code specifies the SMTP server +# response code when a recipient domain matches $mydestination or +# ${proxy,inet}_interfaces, while $local_recipient_maps is non-empty +# and the recipient address or address local-part is not found. +# +# The default setting is 550 (reject mail) but it is safer to start +# with 450 (try again later) until you are certain that your +# local_recipient_maps settings are OK. +# +unknown_local_recipient_reject_code = 550 + +# TRUST AND RELAY CONTROL + +# The mynetworks parameter specifies the list of "trusted" SMTP +# clients that have more privileges than "strangers". +# +# In particular, "trusted" SMTP clients are allowed to relay mail +# through Postfix. See the smtpd_recipient_restrictions parameter +# in postconf(5). +# +# You can specify the list of "trusted" network addresses by hand +# or you can let Postfix do it for you (which is the default). +# +# By default (mynetworks_style = subnet), Postfix "trusts" SMTP +# clients in the same IP subnetworks as the local machine. +# On Linux, this works correctly only with interfaces specified +# with the "ifconfig" command. +# +# Specify "mynetworks_style = class" when Postfix should "trust" SMTP +# clients in the same IP class A/B/C networks as the local machine. +# Don't do this with a dialup site - it would cause Postfix to "trust" +# your entire provider's network. Instead, specify an explicit +# mynetworks list by hand, as described below. +# +# Specify "mynetworks_style = host" when Postfix should "trust" +# only the local machine. +# +#mynetworks_style = class +#mynetworks_style = subnet +#mynetworks_style = host + +# Alternatively, you can specify the mynetworks list by hand, in +# which case Postfix ignores the mynetworks_style setting. +# +# Specify an explicit list of network/netmask patterns, where the +# mask specifies the number of bits in the network part of a host +# address. +# +# You can also specify the absolute pathname of a pattern file instead +# of listing the patterns here. Specify type:table for table-based lookups +# (the value on the table right-hand side is not used). +# +mynetworks = 172.16.0.0/12 +#mynetworks = $config_directory/mynetworks +#mynetworks = hash:/etc/postfix/network_table + +# The relay_domains parameter restricts what destinations this system will +# relay mail to. See the smtpd_recipient_restrictions description in +# postconf(5) for detailed information. +# +# By default, Postfix relays mail +# - from "trusted" clients (IP address matches $mynetworks) to any destination, +# - from "untrusted" clients to destinations that match $relay_domains or +# subdomains thereof, except addresses with sender-specified routing. +# The default relay_domains value is $mydestination. +# +# In addition to the above, the Postfix SMTP server by default accepts mail +# that Postfix is final destination for: +# - destinations that match $inet_interfaces or $proxy_interfaces, +# - destinations that match $mydestination +# - destinations that match $virtual_alias_domains, +# - destinations that match $virtual_mailbox_domains. +# These destinations do not need to be listed in $relay_domains. +# +# Specify a list of hosts or domains, /file/name patterns or type:name +# lookup tables, separated by commas and/or whitespace. Continue +# long lines by starting the next line with whitespace. A file name +# is replaced by its contents; a type:name table is matched when a +# (parent) domain appears as lookup key. +# +# NOTE: Postfix will not automatically forward mail for domains that +# list this system as their primary or backup MX host. See the +# permit_mx_backup restriction description in postconf(5). +# +#relay_domains = $mydestination + +# INTERNET OR INTRANET + +# The relayhost parameter specifies the default host to send mail to +# when no entry is matched in the optional transport(5) table. When +# no relayhost is given, mail is routed directly to the destination. +# +# On an intranet, specify the organizational domain name. If your +# internal DNS uses no MX records, specify the name of the intranet +# gateway host instead. +# +# In the case of SMTP, specify a domain, host, host:port, [host]:port, +# [address] or [address]:port; the form [host] turns off MX lookups. +# +# If you're connected via UUCP, see also the default_transport parameter. +# +#relayhost = $mydomain +#relayhost = [gateway.my.domain] +#relayhost = [mailserver.isp.tld] +#relayhost = uucphost +#relayhost = [an.ip.add.ress] + +# REJECTING UNKNOWN RELAY USERS +# +# The relay_recipient_maps parameter specifies optional lookup tables +# with all addresses in the domains that match $relay_domains. +# +# If this parameter is defined, then the SMTP server will reject +# mail for unknown relay users. This feature is off by default. +# +# The right-hand side of the lookup tables is conveniently ignored. +# In the left-hand side, specify an @domain.tld wild-card, or specify +# a user@domain.tld address. +# +#relay_recipient_maps = hash:/etc/postfix/relay_recipients + +# INPUT RATE CONTROL +# +# The in_flow_delay configuration parameter implements mail input +# flow control. This feature is turned on by default, although it +# still needs further development (it's disabled on SCO UNIX due +# to an SCO bug). +# +# A Postfix process will pause for $in_flow_delay seconds before +# accepting a new message, when the message arrival rate exceeds the +# message delivery rate. With the default 100 SMTP server process +# limit, this limits the mail inflow to 100 messages a second more +# than the number of messages delivered per second. +# +# Specify 0 to disable the feature. Valid delays are 0..10. +# +#in_flow_delay = 1s + +# ADDRESS REWRITING +# +# The ADDRESS_REWRITING_README document gives information about +# address masquerading or other forms of address rewriting including +# username->Firstname.Lastname mapping. + +# ADDRESS REDIRECTION (VIRTUAL DOMAIN) +# +# The VIRTUAL_README document gives information about the many forms +# of domain hosting that Postfix supports. + +# "USER HAS MOVED" BOUNCE MESSAGES +# +# See the discussion in the ADDRESS_REWRITING_README document. + +# TRANSPORT MAP +# +# See the discussion in the ADDRESS_REWRITING_README document. + +# ALIAS DATABASE +# +# The alias_maps parameter specifies the list of alias databases used +# by the local delivery agent. The default list is system dependent. +# +# On systems with NIS, the default is to search the local alias +# database, then the NIS alias database. See aliases(5) for syntax +# details. +# +# If you change the alias database, run "postalias /etc/aliases" (or +# wherever your system stores the mail alias file), or simply run +# "newaliases" to build the necessary DBM or DB file. +# +# It will take a minute or so before changes become visible. Use +# "postfix reload" to eliminate the delay. +# +#alias_maps = dbm:/etc/aliases +#alias_maps = hash:/etc/aliases +#alias_maps = hash:/etc/aliases, nis:mail.aliases +#alias_maps = netinfo:/aliases + +# The alias_database parameter specifies the alias database(s) that +# are built with "newaliases" or "sendmail -bi". This is a separate +# configuration parameter, because alias_maps (see above) may specify +# tables that are not necessarily all under control by Postfix. +# +#alias_database = dbm:/etc/aliases +#alias_database = dbm:/etc/mail/aliases +#alias_database = hash:/etc/aliases +#alias_database = hash:/etc/aliases, hash:/opt/majordomo/aliases + +# ADDRESS EXTENSIONS (e.g., user+foo) +# +# The recipient_delimiter parameter specifies the separator between +# user names and address extensions (user+foo). See canonical(5), +# local(8), relocated(5) and virtual(5) for the effects this has on +# aliases, canonical, virtual, relocated and .forward file lookups. +# Basically, the software tries user+foo and .forward+foo before +# trying user and .forward. +# +#recipient_delimiter = + + +# DELIVERY TO MAILBOX +# +# The home_mailbox parameter specifies the optional pathname of a +# mailbox file relative to a user's home directory. The default +# mailbox file is /var/spool/mail/user or /var/mail/user. Specify +# "Maildir/" for qmail-style delivery (the / is required). +# +#home_mailbox = Mailbox +#home_mailbox = Maildir/ + +# The mail_spool_directory parameter specifies the directory where +# UNIX-style mailboxes are kept. The default setting depends on the +# system type. +# +#mail_spool_directory = /var/mail +#mail_spool_directory = /var/spool/mail + +# The mailbox_command parameter specifies the optional external +# command to use instead of mailbox delivery. The command is run as +# the recipient with proper HOME, SHELL and LOGNAME environment settings. +# Exception: delivery for root is done as $default_user. +# +# Other environment variables of interest: USER (recipient username), +# EXTENSION (address extension), DOMAIN (domain part of address), +# and LOCAL (the address localpart). +# +# Unlike other Postfix configuration parameters, the mailbox_command +# parameter is not subjected to $parameter substitutions. This is to +# make it easier to specify shell syntax (see example below). +# +# Avoid shell meta characters because they will force Postfix to run +# an expensive shell process. Procmail alone is expensive enough. +# +# IF YOU USE THIS TO DELIVER MAIL SYSTEM-WIDE, YOU MUST SET UP AN +# ALIAS THAT FORWARDS MAIL FOR ROOT TO A REAL USER. +# +#mailbox_command = /some/where/procmail +#mailbox_command = /some/where/procmail -a "$EXTENSION" + +# The mailbox_transport specifies the optional transport in master.cf +# to use after processing aliases and .forward files. This parameter +# has precedence over the mailbox_command, fallback_transport and +# luser_relay parameters. +# +# Specify a string of the form transport:nexthop, where transport is +# the name of a mail delivery transport defined in master.cf. The +# :nexthop part is optional. For more details see the sample transport +# configuration file. +# +# NOTE: if you use this feature for accounts not in the UNIX password +# file, then you must update the "local_recipient_maps" setting in +# the main.cf file, otherwise the SMTP server will reject mail for +# non-UNIX accounts with "User unknown in local recipient table". +# +# Cyrus IMAP over LMTP. Specify ``lmtpunix cmd="lmtpd" +# listen="/var/imap/socket/lmtp" prefork=0'' in cyrus.conf. +#mailbox_transport = lmtp:unix:/var/imap/socket/lmtp +# +# Cyrus IMAP via command line. Uncomment the "cyrus...pipe" and +# subsequent line in master.cf. +#mailbox_transport = cyrus + +# The fallback_transport specifies the optional transport in master.cf +# to use for recipients that are not found in the UNIX passwd database. +# This parameter has precedence over the luser_relay parameter. +# +# Specify a string of the form transport:nexthop, where transport is +# the name of a mail delivery transport defined in master.cf. The +# :nexthop part is optional. For more details see the sample transport +# configuration file. +# +# NOTE: if you use this feature for accounts not in the UNIX password +# file, then you must update the "local_recipient_maps" setting in +# the main.cf file, otherwise the SMTP server will reject mail for +# non-UNIX accounts with "User unknown in local recipient table". +# +#fallback_transport = lmtp:unix:/file/name +#fallback_transport = cyrus +#fallback_transport = + +# The luser_relay parameter specifies an optional destination address +# for unknown recipients. By default, mail for unknown@$mydestination, +# unknown@[$inet_interfaces] or unknown@[$proxy_interfaces] is returned +# as undeliverable. +# +# The following expansions are done on luser_relay: $user (recipient +# username), $shell (recipient shell), $home (recipient home directory), +# $recipient (full recipient address), $extension (recipient address +# extension), $domain (recipient domain), $local (entire recipient +# localpart), $recipient_delimiter. Specify ${name?value} or +# ${name:value} to expand value only when $name does (does not) exist. +# +# luser_relay works only for the default Postfix local delivery agent. +# +# NOTE: if you use this feature for accounts not in the UNIX password +# file, then you must specify "local_recipient_maps =" (i.e. empty) in +# the main.cf file, otherwise the SMTP server will reject mail for +# non-UNIX accounts with "User unknown in local recipient table". +# +#luser_relay = $user@other.host +#luser_relay = $local@other.host +#luser_relay = admin+$local + +# JUNK MAIL CONTROLS +# +# The controls listed here are only a very small subset. The file +# SMTPD_ACCESS_README provides an overview. + +# The header_checks parameter specifies an optional table with patterns +# that each logical message header is matched against, including +# headers that span multiple physical lines. +# +# By default, these patterns also apply to MIME headers and to the +# headers of attached messages. With older Postfix versions, MIME and +# attached message headers were treated as body text. +# +# For details, see "man header_checks". +# +#header_checks = regexp:/etc/postfix/header_checks + +# FAST ETRN SERVICE +# +# Postfix maintains per-destination logfiles with information about +# deferred mail, so that mail can be flushed quickly with the SMTP +# "ETRN domain.tld" command, or by executing "sendmail -qRdomain.tld". +# See the ETRN_README document for a detailed description. +# +# The fast_flush_domains parameter controls what destinations are +# eligible for this service. By default, they are all domains that +# this server is willing to relay mail to. +# +#fast_flush_domains = $relay_domains + +# SHOW SOFTWARE VERSION OR NOT +# +# The smtpd_banner parameter specifies the text that follows the 220 +# code in the SMTP server's greeting banner. Some people like to see +# the mail version advertised. By default, Postfix shows no version. +# +# You MUST specify $myhostname at the start of the text. That is an +# RFC requirement. Postfix itself does not care. +# +#smtpd_banner = $myhostname ESMTP $mail_name +#smtpd_banner = $myhostname ESMTP $mail_name ($mail_version) + +# PARALLEL DELIVERY TO THE SAME DESTINATION +# +# How many parallel deliveries to the same user or domain? With local +# delivery, it does not make sense to do massively parallel delivery +# to the same user, because mailbox updates must happen sequentially, +# and expensive pipelines in .forward files can cause disasters when +# too many are run at the same time. With SMTP deliveries, 10 +# simultaneous connections to the same domain could be sufficient to +# raise eyebrows. +# +# Each message delivery transport has its XXX_destination_concurrency_limit +# parameter. The default is $default_destination_concurrency_limit for +# most delivery transports. For the local delivery agent the default is 2. + +#local_destination_concurrency_limit = 2 +#default_destination_concurrency_limit = 20 + +# DEBUGGING CONTROL +# +# The debug_peer_level parameter specifies the increment in verbose +# logging level when an SMTP client or server host name or address +# matches a pattern in the debug_peer_list parameter. +# +debug_peer_level = 2 + +# The debug_peer_list parameter specifies an optional list of domain +# or network patterns, /file/name patterns or type:name tables. When +# an SMTP client or server host name or address matches a pattern, +# increase the verbose logging level by the amount specified in the +# debug_peer_level parameter. +# +#debug_peer_list = 127.0.0.1 +#debug_peer_list = some.domain + +# The debugger_command specifies the external command that is executed +# when a Postfix daemon program is run with the -D option. +# +# Use "command .. & sleep 5" so that the debugger can attach before +# the process marches on. If you use an X-based debugger, be sure to +# set up your XAUTHORITY environment variable before starting Postfix. +# +debugger_command = + PATH=/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin + ddd $daemon_directory/$process_name $process_id & sleep 5 + +# If you can't use X, use this to capture the call stack when a +# daemon crashes. The result is in a file in the configuration +# directory, and is named after the process name and the process ID. +# +# debugger_command = +# PATH=/bin:/usr/bin:/usr/local/bin; export PATH; (echo cont; +# echo where) | gdb $daemon_directory/$process_name $process_id 2>&1 +# >$config_directory/$process_name.$process_id.log & sleep 5 +# +# Another possibility is to run gdb under a detached screen session. +# To attach to the screen session, su root and run "screen -r +# " where uniquely matches one of the detached +# sessions (from "screen -list"). +# +# debugger_command = +# PATH=/bin:/usr/bin:/sbin:/usr/sbin; export PATH; screen +# -dmS $process_name gdb $daemon_directory/$process_name +# $process_id & sleep 1 + +# INSTALL-TIME CONFIGURATION INFORMATION +# +# The following parameters are used when installing a new Postfix version. +# +# sendmail_path: The full pathname of the Postfix sendmail command. +# This is the Sendmail-compatible mail posting interface. +# +sendmail_path = /usr/sbin/sendmail + +# newaliases_path: The full pathname of the Postfix newaliases command. +# This is the Sendmail-compatible command to build alias databases. +# +newaliases_path = /usr/bin/newaliases + +# mailq_path: The full pathname of the Postfix mailq command. This +# is the Sendmail-compatible mail queue listing command. +# +mailq_path = /usr/bin/mailq + +# setgid_group: The group for mail submission and queue management +# commands. This must be a group name with a numerical group ID that +# is not shared with other accounts, not even with the Postfix account. +# +setgid_group = postdrop + +# html_directory: The location of the Postfix HTML documentation. +# +html_directory = no + +# manpage_directory: The location of the Postfix on-line manual pages. +# +manpage_directory = /usr/share/man + +# sample_directory: The location of the Postfix sample configuration files. +# This parameter is obsolete as of Postfix 2.1. +# +sample_directory = /etc/postfix + +# readme_directory: The location of the Postfix README files. +# +readme_directory = /usr/share/doc/postfix/readme +inet_protocols = ipv4 +meta_directory = /etc/postfix +shlib_directory = /usr/lib/postfix + +smtpd_tls_security_level=may +smtpd_tls_auth_only = yes + +#maillog_file=/dev/stdout + +smtpd_tls_cert_file = /etc/ssl/cert-data/tls-server.com-chain.crt +smtpd_tls_key_file = /etc/ssl/cert-data/tls-server.com.key \ No newline at end of file diff --git a/evaluation-servers/postfix/master.cf b/evaluation-servers/postfix/master.cf new file mode 100644 index 0000000..19b73b0 --- /dev/null +++ b/evaluation-servers/postfix/master.cf @@ -0,0 +1,139 @@ +# +# Postfix master process configuration file. For details on the format +# of the file, see the master(5) manual page (command: "man 5 master" or +# on-line: http://www.postfix.org/master.5.html). +# +# Do not forget to execute "postfix reload" after editing this file. +# +# ========================================================================== +# service type private unpriv chroot wakeup maxproc command + args +# (yes) (yes) (no) (never) (100) +# ========================================================================== +465 inet n - n - - smtpd +#smtp inet n - n - 1 postscreen +#smtpd pass - - n - - smtpd +#dnsblog unix - - n - 0 dnsblog +#tlsproxy unix - - n - 0 tlsproxy +# Choose one: enable submission for loopback clients only, or for any client. +#127.0.0.1:submission inet n - n - - smtpd +submission inet n - n - - smtpd + -o syslog_name=postfix/submission + -o smtpd_tls_security_level=encrypt + -o smtpd_sasl_auth_enable=yes +# -o syslog_name=postfix/submission +# -o smtpd_sasl_auth_enable=yes +# -o smtpd_tls_auth_only=yes +# -o smtpd_reject_unlisted_recipient=no +# -o smtpd_client_restrictions=$mua_client_restrictions +# -o smtpd_helo_restrictions=$mua_helo_restrictions +# -o smtpd_sender_restrictions=$mua_sender_restrictions +# -o smtpd_recipient_restrictions= +# -o smtpd_relay_restrictions=permit_sasl_authenticated,reject +# -o milter_macro_daemon_name=ORIGINATING +# Choose one: enable smtps for loopback clients only, or for any client. +#smtps inet n - n - - smtpd +#smtps inet n - n - - smtpd +# -o syslog_name=postfix/smtps +# -o smtpd_tls_wrappermode=yes +# -o smtpd_sasl_auth_enable=yes +# -o smtpd_reject_unlisted_recipient=no +# -o smtpd_client_restrictions=$mua_client_restrictions +# -o smtpd_helo_restrictions=$mua_helo_restrictions +# -o smtpd_sender_restrictions=$mua_sender_restrictions +# -o smtpd_recipient_restrictions= +# -o smtpd_relay_restrictions=permit_sasl_authenticated,reject +# -o milter_macro_daemon_name=ORIGINATING +#628 inet n - n - - qmqpd +pickup unix n - n 60 1 pickup +cleanup unix n - n - 0 cleanup +qmgr unix n - n 300 1 qmgr +#qmgr unix n - n 300 1 oqmgr +tlsmgr unix - - n 1000? 1 tlsmgr +rewrite unix - - n - - trivial-rewrite +bounce unix - - n - 0 bounce +defer unix - - n - 0 bounce +trace unix - - n - 0 bounce +verify unix - - n - 1 verify +flush unix n - n 1000? 0 flush +proxymap unix - - n - - proxymap +proxywrite unix - - n - 1 proxymap +smtp unix - - n - - smtp +relay unix - - n - - smtp + -o syslog_name=postfix/$service_name +# -o smtp_helo_timeout=5 -o smtp_connect_timeout=5 +showq unix n - n - - showq +error unix - - n - - error +retry unix - - n - - error +discard unix - - n - - discard +local unix - n n - - local +virtual unix - n n - - virtual +lmtp unix - - n - - lmtp +anvil unix - - n - 1 anvil +scache unix - - n - 1 scache +postlog unix-dgram n - n - 1 postlogd +# +# ==================================================================== +# Interfaces to non-Postfix software. Be sure to examine the manual +# pages of the non-Postfix software to find out what options it wants. +# +# Many of the following services use the Postfix pipe(8) delivery +# agent. See the pipe(8) man page for information about ${recipient} +# and other message envelope options. +# ==================================================================== +# +# maildrop. See the Postfix MAILDROP_README file for details. +# Also specify in main.cf: maildrop_destination_recipient_limit=1 +# +#maildrop unix - n n - - pipe +# flags=DRXhu user=vmail argv=/usr/bin/maildrop -d ${recipient} +# +# ==================================================================== +# +# Recent Cyrus versions can use the existing "lmtp" master.cf entry. +# +# Specify in cyrus.conf: +# lmtp cmd="lmtpd -a" listen="localhost:lmtp" proto=tcp4 +# +# Specify in main.cf one or more of the following: +# mailbox_transport = lmtp:inet:localhost +# virtual_transport = lmtp:inet:localhost +# +# ==================================================================== +# +# Cyrus 2.1.5 (Amos Gouaux) +# Also specify in main.cf: cyrus_destination_recipient_limit=1 +# +#cyrus unix - n n - - pipe +# flags=DRX user=cyrus argv=/cyrus/bin/deliver -e -r ${sender} -m ${extension} ${user} +# +# ==================================================================== +# +# Old example of delivery via Cyrus. +# +#old-cyrus unix - n n - - pipe +# flags=R user=cyrus argv=/cyrus/bin/deliver -e -m ${extension} ${user} +# +# ==================================================================== +# +# See the Postfix UUCP_README file for configuration details. +# +#uucp unix - n n - - pipe +# flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient) +# +# ==================================================================== +# +# Other external delivery methods. +# +#ifmail unix - n n - - pipe +# flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient) +# +#bsmtp unix - n n - - pipe +# flags=Fq. user=bsmtp argv=/usr/sbin/bsmtp -f $sender $nexthop $recipient +# +#scalemail-backend unix - n n - 2 pipe +# flags=R user=scalemail argv=/usr/lib/scalemail/bin/scalemail-store +# ${nexthop} ${user} ${extension} +# +#mailman unix - n n - - pipe +# flags=FRX user=list argv=/usr/lib/mailman/bin/postfix-to-mailman.py +# ${nexthop} ${user} diff --git a/evaluation-servers/postfix/run.sh b/evaluation-servers/postfix/run.sh new file mode 100755 index 0000000..36ab020 --- /dev/null +++ b/evaluation-servers/postfix/run.sh @@ -0,0 +1,2 @@ +./build.sh +docker-compose up --remove-orphans diff --git a/evaluation-servers/proftpd/Dockerfile b/evaluation-servers/proftpd/Dockerfile new file mode 100644 index 0000000..817c416 --- /dev/null +++ b/evaluation-servers/proftpd/Dockerfile @@ -0,0 +1,22 @@ +FROM tls-baseimage +ARG VERSION=1.3.8rc2 +ARG TLS_SERVER_NAME=tls-server.com +RUN apk add openssl-dev +WORKDIR /build +RUN wget https://github.com/proftpd/proftpd/archive/refs/tags/v${VERSION}.tar.gz +RUN tar -xvf v${VERSION}.tar.gz +WORKDIR /build/proftpd-${VERSION} +RUN ./configure --with-modules=mod_tls --with-virtualhosts +RUN make +RUN mv proftpd /bin/ + +#fix errors on start +RUN mkdir /usr/local/var/ + +RUN echo "tls-server.com" > /etc/hostname +RUN echo "tls-server.com 127.0.0.1" > /etc/hosts + +ADD proftpd.conf /etc/proftpd.conf + + +CMD ["proftpd", "-d", "10", "-n" , "-c" , "/etc/proftpd.conf"] \ No newline at end of file diff --git a/evaluation-servers/proftpd/build.sh b/evaluation-servers/proftpd/build.sh new file mode 100755 index 0000000..c476f4e --- /dev/null +++ b/evaluation-servers/proftpd/build.sh @@ -0,0 +1 @@ +docker build . -t tls-proftpd \ No newline at end of file diff --git a/evaluation-servers/proftpd/docker-compose.yml b/evaluation-servers/proftpd/docker-compose.yml new file mode 100644 index 0000000..a4977c1 --- /dev/null +++ b/evaluation-servers/proftpd/docker-compose.yml @@ -0,0 +1,13 @@ +version: "3.9" +networks: + default: + name: tls-network + internal: true +services: + tls-server.com: + image: tls-proftpd + scanner: + image: tlsscanner + depends_on: + - tls-server.com + command: [ "-connect", "tls-server.com:21", "-server_name", "tls-server.com", "-starttls", "ftp", "-scanDetail", "QUICK"] \ No newline at end of file diff --git a/evaluation-servers/proftpd/proftpd.conf b/evaluation-servers/proftpd/proftpd.conf new file mode 100644 index 0000000..635d479 --- /dev/null +++ b/evaluation-servers/proftpd/proftpd.conf @@ -0,0 +1,55 @@ +ScoreboardFile /dev/null +ServerType standalone +UseReverseDNS off + + TLSEngine on + TLSLog /usr/local/var/tls.log + TLSProtocol TLSv1.2 + TLSRequired on + TLSRSACertificateFile /etc/ssl/cert-data/tls-server.com-chain.crt + TLSRSACertificateKeyFile /etc/ssl/cert-data/tls-server.com.key + TLSVerifyClient off + TLSRenegotiate none + TLSNextProtocol on + TLSOptions StdEnvVars + + # This is a basic ProFTPD configuration file (rename it to + # 'proftpd.conf' for actual use. It establishes a single server + # and a single anonymous login. It assumes that you have a user/group + # "nobody" and "ftp" for normal operation and anon. + + ServerName "tls-server.com" + + DefaultServer on + + # Port 21 is the standard FTP port. + Port 21 + + # Bar use of SITE CHMOD by default + + DenyAll + + + # A basic anonymous configuration, no upload directories. If you do not + # want anonymous users, simply delete this entire section. + + User ftp + Group ftp + + # We want clients to be able to login with "anonymous" as well as "ftp" + UserAlias anonymous ftp + + # Limit the maximum number of anonymous logins + MaxClients 10 + + # We want 'welcome.msg' displayed at login, and '.message' displayed + # in each newly chdired directory. + DisplayLogin welcome.msg + DisplayChdir .message + + # Limit WRITE everywhere in the anonymous chroot + + DenyAll + + + diff --git a/evaluation-servers/proftpd/run.sh b/evaluation-servers/proftpd/run.sh new file mode 100755 index 0000000..36ab020 --- /dev/null +++ b/evaluation-servers/proftpd/run.sh @@ -0,0 +1,2 @@ +./build.sh +docker-compose up --remove-orphans diff --git a/evaluation-servers/pure-ftpd/Dockerfile b/evaluation-servers/pure-ftpd/Dockerfile new file mode 100644 index 0000000..c2f8d2d --- /dev/null +++ b/evaluation-servers/pure-ftpd/Dockerfile @@ -0,0 +1,17 @@ +FROM tls-baseimage +ARG VERSION=1.0.49 +RUN apk add openssl-dev +WORKDIR /build +RUN wget https://github.com/jedisct1/pure-ftpd/releases/download/${VERSION}/pure-ftpd-${VERSION}.tar.gz +RUN tar -xvf pure-ftpd-${VERSION}.tar.gz +WORKDIR /build/pure-ftpd-${VERSION} +RUN ./configure --with-tls --without-capabilities +RUN make +RUN mv src/pure-ftpd /bin/ + +RUN mkdir /etc/ssl/private + +RUN cat /etc/ssl/cert-data/tls-server.com.key >> /etc/ssl/private/pure-ftpd.pem +RUN cat /etc/ssl/cert-data/tls-server.com-chain.crt >> /etc/ssl/private/pure-ftpd.pem + +CMD ["pure-ftpd", "-Y" , "2"] \ No newline at end of file diff --git a/evaluation-servers/pure-ftpd/build.sh b/evaluation-servers/pure-ftpd/build.sh new file mode 100755 index 0000000..a283f35 --- /dev/null +++ b/evaluation-servers/pure-ftpd/build.sh @@ -0,0 +1 @@ +docker build . -t tls-pureftpd \ No newline at end of file diff --git a/evaluation-servers/pure-ftpd/docker-compose.yml b/evaluation-servers/pure-ftpd/docker-compose.yml new file mode 100644 index 0000000..6dd682e --- /dev/null +++ b/evaluation-servers/pure-ftpd/docker-compose.yml @@ -0,0 +1,13 @@ +version: "3.9" +networks: + default: + name: tls-network + internal: true +services: + pureftpd: + image: tls-pureftpd + scanner: + image: tlsscanner + depends_on: + - pureftpd + command: [ "-connect", "pureftpd:21", "-server_name", "tls-server.com", "-starttls", "ftp", "-scanDetail", "QUICK"] \ No newline at end of file diff --git a/evaluation-servers/pure-ftpd/run.sh b/evaluation-servers/pure-ftpd/run.sh new file mode 100755 index 0000000..36ab020 --- /dev/null +++ b/evaluation-servers/pure-ftpd/run.sh @@ -0,0 +1,2 @@ +./build.sh +docker-compose up --remove-orphans diff --git a/evaluation-servers/sendmail/Dockerfile b/evaluation-servers/sendmail/Dockerfile new file mode 100644 index 0000000..fb5ba5a --- /dev/null +++ b/evaluation-servers/sendmail/Dockerfile @@ -0,0 +1,39 @@ +FROM tls-baseimagedebian + +RUN apt-get update && apt-get install -yq sendmail openssl libsasl2-2 sasl2-bin + +RUN echo "tls-server.com" >> /etc/hostname +RUN echo "tls-server.com 127.0.0.1" >> /etc/hosts + +RUN echo "include(\`/etc/mail/tls/starttls.m4')dnl" >> /etc/mail/sendmail.mc +RUN echo "include(\`/etc/mail/tls/starttls.m4')dnl" >> /etc/mail/submit.mc + +RUN echo "define(\`confCACERT_PATH', \`/etc/ssl/cert-data/')dnl" >> /etc/mail/sendmail.mc +RUN echo "define(\`confLOG_LEVEL', \`14')" >> /etc/mail/sendmail.mc + +RUN echo "define(\`SMART_HOST', \`tls-server.com')dnl" >> /etc/mail/sendmail.mc + +RUN echo "define(\`confSERVER_CERT', \`/etc/ssl/cert-data/tls-server.com-chain.crt')dnl" >> /etc/mail/sendmail.mc +RUN echo "define(\`confSERVER_KEY', \`/etc/ssl/cert-data/tls-server.com.key')dnl" >> /etc/mail/sendmail.mc +RUN echo "define(\`confCLIENT_CERT', \`/etc/ssl/cert-data/tls-server.com-chain.crt')dnl" >> /etc/mail/sendmail.mc +RUN echo "define(\`confCLIENT_KEY', \`/etc/ssl/cert-data/tls-server.com.key')dnl" >> /etc/mail/sendmail.mc + +RUN echo "GreetPause:192.16 0" >> /etc/mail/access +RUN echo "ClientRate:172.16 0" >> /etc/mail/access +RUN echo "GreetPause:172.16 0" >> /etc/mail/access + +RUN sed -i 's/127.0.0.1/0.0.0.0/' /etc/mail/sendmail.mc + +RUN chmod 600 /etc/ssl/cert-data/* + +RUN yes 'y' | sendmailconfig + +RUN chmod 777 /etc/ssl/cert-data/tls-server.com-chain.crt +RUN chmod 777 /etc/ssl/cert-data/tls-server.com.key + +USER sendmail +#CMD ["exim", "-bd", "-d-all+pid", "-q30m"] +ADD start.sh /root/ +RUN chmod +x /root/start.sh +CMD ["/root/start.sh"] +#CMD ["sendmail", "-bD", "-d0.14"] \ No newline at end of file diff --git a/evaluation-servers/sendmail/build.sh b/evaluation-servers/sendmail/build.sh new file mode 100755 index 0000000..5e14e72 --- /dev/null +++ b/evaluation-servers/sendmail/build.sh @@ -0,0 +1 @@ +docker build . -t tls-sendmail diff --git a/evaluation-servers/sendmail/docker-compose.yml b/evaluation-servers/sendmail/docker-compose.yml new file mode 100644 index 0000000..50412ab --- /dev/null +++ b/evaluation-servers/sendmail/docker-compose.yml @@ -0,0 +1,16 @@ +version: "3.9" +networks: + default: + name: tls-network + internal: true +services: + tls-sendmail: + image: tls-sendmail + hostname: tls-server.com + container_name: tls-server.com + scanner: + image: tlsscanner + depends_on: + - tls-sendmail + command: [ "-connect", "tls-server.com:25", "-server_name", "tls-server.com", "-starttls", "SMTP", "-scanDetail", "QUICK"] + restart: always \ No newline at end of file diff --git a/evaluation-servers/sendmail/run.sh b/evaluation-servers/sendmail/run.sh new file mode 100755 index 0000000..36ab020 --- /dev/null +++ b/evaluation-servers/sendmail/run.sh @@ -0,0 +1,2 @@ +./build.sh +docker-compose up --remove-orphans diff --git a/evaluation-servers/sendmail/start.sh b/evaluation-servers/sendmail/start.sh new file mode 100644 index 0000000..70f6ce5 --- /dev/null +++ b/evaluation-servers/sendmail/start.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# /etc/init.d/sendmail start +# echo Started sendmail +# + +cat /etc/hostname +echo "tls-server" > /etc/hostname +echo "127.0.0.1 tls-server.com tls-server localdev localhost" > /etc/hosts + +touch /var/log/mail.info +sendmail -bD -d0.14 \ No newline at end of file diff --git a/evaluation-servers/vsftpd/Dockerfile b/evaluation-servers/vsftpd/Dockerfile new file mode 100644 index 0000000..3c64415 --- /dev/null +++ b/evaluation-servers/vsftpd/Dockerfile @@ -0,0 +1,25 @@ +FROM tls-baseimage +ARG VERSION=3.0.5 +RUN apk add vsftpd +#RUN apk add openssl-dev +#WORKDIR /build +#RUN wget https://security.appspot.com/downloads/vsftpd-${VERSION}.tar.gz +#RUN tar -xvf vsftpd-${VERSION}.tar.gz +#WORKDIR /build/vsftpd-${VERSION} +#RUN ./configure --with-modules=mod_tls --with-virtualhosts +#RUN make +#RUN mv proftpd /bin/ + +RUN mkdir /var/ftp +RUN chmod 777 /var/ftp + +RUN echo "tls-server.com" > /etc/hostname +RUN echo "tls-server.com 127.0.0.1" > /etc/hosts + +ADD vsftpd.conf /etc/vsftpd/vsftpd.conf + +ADD start.sh /root/ +RUN chmod +x /root/start.sh +CMD ["/root/start.sh"] + +#CMD ["/usr/sbin/vsftpd", "/etc/vsftpd/vsftpd.conf"] \ No newline at end of file diff --git a/evaluation-servers/vsftpd/build.sh b/evaluation-servers/vsftpd/build.sh new file mode 100755 index 0000000..9c93b01 --- /dev/null +++ b/evaluation-servers/vsftpd/build.sh @@ -0,0 +1 @@ +docker build . -t tls-vsftpd \ No newline at end of file diff --git a/evaluation-servers/vsftpd/docker-compose.yml b/evaluation-servers/vsftpd/docker-compose.yml new file mode 100644 index 0000000..d4d4191 --- /dev/null +++ b/evaluation-servers/vsftpd/docker-compose.yml @@ -0,0 +1,13 @@ +version: "3.9" +networks: + default: + name: tls-network + internal: true +services: + vsftpd: + image: tls-vsftpd + scanner: + image: tlsscanner + depends_on: + - vsftpd + command: [ "-connect", "vsftpd:21", "-server_name", "tls-server.com", "-starttls", "ftp", "-scanDetail", "QUICK"] \ No newline at end of file diff --git a/evaluation-servers/vsftpd/run.sh b/evaluation-servers/vsftpd/run.sh new file mode 100755 index 0000000..36ab020 --- /dev/null +++ b/evaluation-servers/vsftpd/run.sh @@ -0,0 +1,2 @@ +./build.sh +docker-compose up --remove-orphans diff --git a/evaluation-servers/vsftpd/start.sh b/evaluation-servers/vsftpd/start.sh new file mode 100644 index 0000000..f98b954 --- /dev/null +++ b/evaluation-servers/vsftpd/start.sh @@ -0,0 +1,7 @@ +#!/bin/bash +#echo test +touch /var/log/vsftpd.log +/usr/sbin/vsftpd /etc/vsftpd/vsftpd.conf & tail -f /var/log/vsftpd.log +#rc-update add vsftpd default +#rc-service vsftpd restart +#tail -f /var/log/vsftpd.log \ No newline at end of file diff --git a/evaluation-servers/vsftpd/vsftpd.conf b/evaluation-servers/vsftpd/vsftpd.conf new file mode 100644 index 0000000..421e79a --- /dev/null +++ b/evaluation-servers/vsftpd/vsftpd.conf @@ -0,0 +1,136 @@ +# Example config file /etc/vsftpd.conf +# +# The default compiled in settings are fairly paranoid. This sample file +# loosens things up a bit, to make the ftp daemon more usable. +# Please see vsftpd.conf.5 for all compiled in defaults. +# +# READ THIS: This example file is NOT an exhaustive list of vsftpd options. +# Please read the vsftpd.conf.5 manual page to get a full idea of vsftpd's +# capabilities. +# +# Allow anonymous FTP? (Beware - allowed by default if you comment this out). +anonymous_enable=YES +# +# Uncomment this to allow local users to log in. +#local_enable=YES +# +# Uncomment this to enable any form of FTP write command. +#write_enable=YES +# +# Default umask for local users is 077. You may wish to change this to 022, +# if your users expect that (022 is used by most other ftpd's) +#local_umask=022 +# +# Uncomment this to allow the anonymous FTP user to upload files. This only +# has an effect if the above global write enable is activated. Also, you will +# obviously need to create a directory writable by the FTP user. +#anon_upload_enable=YES +# +# Uncomment this if you want the anonymous FTP user to be able to create +# new directories. +#anon_mkdir_write_enable=YES +# +# Activate directory messages - messages given to remote users when they +# go into a certain directory. +dirmessage_enable=YES +# +# Activate logging of uploads/downloads. +xferlog_enable=YES +# +# Make sure PORT transfer connections originate from port 20 (ftp-data). +connect_from_port_20=YES +# +# If you want, you can arrange for uploaded anonymous files to be owned by +# a different user. Note! Using "root" for uploaded files is not +# recommended! +#chown_uploads=YES +#chown_username=whoever +# +# You may override where the log file goes if you like. The default is shown +# below. +#xferlog_file=/var/log/vsftpd.log +# +# If you want, you can have your log file in standard ftpd xferlog format. +# Note that the default log file location is /var/log/xferlog in this case. +#xferlog_std_format=YES +# +# You may change the default value for timing out an idle session. +#idle_session_timeout=600 +# +# You may change the default value for timing out a data connection. +#data_connection_timeout=120 +# +# It is recommended that you define on your system a unique user which the +# ftp server can use as a totally isolated and unprivileged user. +#nopriv_user=ftpsecure +# +# Enable this and the server will recognise asynchronous ABOR requests. Not +# recommended for security (the code is non-trivial). Not enabling it, +# however, may confuse older FTP clients. +#async_abor_enable=YES +# +# By default the server will pretend to allow ASCII mode but in fact ignore +# the request. Turn on the below options to have the server actually do ASCII +# mangling on files when in ASCII mode. +# Beware that on some FTP servers, ASCII support allows a denial of service +# attack (DoS) via the command "SIZE /big/file" in ASCII mode. vsftpd +# predicted this attack and has always been safe, reporting the size of the +# raw file. +# ASCII mangling is a horrible feature of the protocol. +#ascii_upload_enable=YES +#ascii_download_enable=YES +# +# You may fully customise the login banner string: +#ftpd_banner=Welcome to blah FTP service. +# +# You may specify a file of disallowed anonymous e-mail addresses. Apparently +# useful for combatting certain DoS attacks. +#deny_email_enable=YES +# (default follows) +#banned_email_file=/etc/vsftpd.banned_emails +# +# You may specify an explicit list of local users to chroot() to their home +# directory. If chroot_local_user is YES, then this list becomes a list of +# users to NOT chroot(). +# (Warning! chroot'ing can be very dangerous. If using chroot, make sure that +# the user does not have write access to the top level directory within the +# chroot) +#chroot_local_user=YES +#chroot_list_enable=YES +# (default follows) +#chroot_list_file=/etc/vsftpd.chroot_list +allow_writeable_chroot=yes +# +# You may activate the "-R" option to the builtin ls. This is disabled by +# default to avoid remote users being able to cause excessive I/O on large +# sites. However, some broken FTP clients such as "ncftp" and "mirror" assume +# the presence of the "-R" option, so there is a strong case for enabling it. +#ls_recurse_enable=YES +# +# When "listen" directive is enabled, vsftpd runs in standalone mode and +# listens on IPv4 sockets. This directive cannot be used in conjunction +# with the listen_ipv6 directive. +listen=YES +# +# This directive enables listening on IPv6 sockets. To listen on IPv4 and IPv6 +# sockets, you must run two copies of vsftpd with two configuration files. +# Make sure, that one of the listen options is commented !! +#listen_ipv6=YES + +# Set own PAM service name to detect authentication settings specified +# for vsftpd by the system package. +#pam_service_name=vsftpd + + +seccomp_sandbox=NO +local_root=/var/ftp +anon_root=/var/ftp + + +rsa_cert_file=/etc/ssl/cert-data/tls-server.com-chain.crt +rsa_private_key_file=/etc/ssl/cert-data/tls-server.com.key +ssl_enable=yes +allow_anon_ssl=yes +#debug_ssl=yes +#ssl_ciphers=TLSv1.2 +ssl_sni_hostname=tls-server.com \ No newline at end of file From 71b3e7ceaf1ca34b002d3c8c16d1df6c831315fb Mon Sep 17 00:00:00 2001 From: Jannik Hoelling Date: Tue, 18 Jan 2022 20:21:54 +0000 Subject: [PATCH 2/2] bearssl: generate ca certificate --- evaluation-libraries/bearssl/Dockerfile | 1 + evaluation-libraries/bearssl/client/client.h | 46 -------------------- 2 files changed, 1 insertion(+), 46 deletions(-) diff --git a/evaluation-libraries/bearssl/Dockerfile b/evaluation-libraries/bearssl/Dockerfile index 369ea63..f37a0ad 100644 --- a/evaluation-libraries/bearssl/Dockerfile +++ b/evaluation-libraries/bearssl/Dockerfile @@ -19,6 +19,7 @@ ADD CMakeLists.txt /build/CMakeLists.txt # generate c code from private keys and certs RUN ls /build/server/ +RUN /build/BearSSL/build/brssl ta /etc/ssl/cert-data/ca.crt | tail -n +2 >> /build/client/client.h RUN /build/BearSSL/build/brssl chain /etc/ssl/cert-data/tls-server.com-chain.crt | tail -n +2 >> /build/server/server.h RUN /build/BearSSL/build/brssl skey -C /etc/ssl/cert-data/tls-server.com.key | tail -n +2 >> /build/server/server.h diff --git a/evaluation-libraries/bearssl/client/client.h b/evaluation-libraries/bearssl/client/client.h index 6c712f0..eb0848e 100644 --- a/evaluation-libraries/bearssl/client/client.h +++ b/evaluation-libraries/bearssl/client/client.h @@ -35,52 +35,6 @@ #include #include -/* certs/ca.crt - Code generated by running "brssl ta ca.crt" -*/ -static const unsigned char TA0_DN[] = { - 0x30, 0x14, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0C, - 0x09, 0x54, 0x68, 0x65, 0x73, 0x69, 0x73, 0x20, 0x43, 0x41}; - -static const unsigned char TA0_RSA_N[] = { - 0xD2, 0x20, 0x8A, 0x5D, 0x20, 0x05, 0x4B, 0x15, 0x3D, 0x00, 0x29, 0x4A, - 0xFB, 0x95, 0x0F, 0x7A, 0x3E, 0x04, 0x61, 0xC2, 0x95, 0x85, 0x80, 0xAD, - 0xD9, 0xA8, 0xA3, 0x07, 0x22, 0xB1, 0x60, 0xA2, 0x1C, 0xA0, 0x90, 0xA0, - 0x14, 0x30, 0x45, 0x3D, 0xF6, 0xC6, 0x26, 0x5D, 0xA3, 0xE7, 0x05, 0x6A, - 0xFC, 0x5C, 0x3F, 0x8B, 0xE4, 0xF1, 0xB1, 0xD1, 0xCF, 0x43, 0x7C, 0x82, - 0x39, 0xEB, 0x81, 0xC5, 0xF9, 0x55, 0x03, 0x7E, 0x68, 0x1C, 0x6A, 0x52, - 0x1C, 0x29, 0x0B, 0x15, 0x43, 0x4B, 0x0D, 0xA7, 0x99, 0xCA, 0xBA, 0x7E, - 0xFD, 0x19, 0xB6, 0xA4, 0x00, 0xFD, 0x64, 0xE9, 0xBC, 0x87, 0xA1, 0x48, - 0xBE, 0x3F, 0x0D, 0xE0, 0xF1, 0xD7, 0xE6, 0x31, 0x99, 0x81, 0xE2, 0xC3, - 0x4B, 0x21, 0xFE, 0x6C, 0x70, 0x57, 0x9F, 0x86, 0x61, 0xA3, 0x95, 0x6A, - 0xC9, 0x0E, 0x1E, 0xE1, 0x66, 0x9F, 0x5D, 0xD2, 0xE0, 0x65, 0x6D, 0xB7, - 0xE5, 0x45, 0x93, 0xE0, 0xCA, 0x9E, 0xA5, 0x2E, 0x94, 0x9D, 0x1F, 0x1A, - 0x96, 0x02, 0xCF, 0x7B, 0xE6, 0x39, 0x6C, 0x0C, 0x34, 0xA4, 0xA1, 0x7E, - 0xB3, 0x38, 0x5F, 0x5D, 0x46, 0x40, 0x90, 0xAF, 0x8C, 0x56, 0x60, 0xEC, - 0xB9, 0x86, 0x78, 0xF6, 0x36, 0x38, 0x35, 0x28, 0x88, 0xC0, 0xFA, 0x57, - 0x9D, 0xFE, 0x94, 0x97, 0x2F, 0x0A, 0x31, 0x41, 0x02, 0xE6, 0xFA, 0x03, - 0x72, 0x98, 0x64, 0x71, 0x28, 0x6D, 0xFB, 0x12, 0x88, 0x7B, 0x41, 0xA7, - 0x8E, 0xBB, 0x6C, 0x16, 0x70, 0x86, 0x58, 0x55, 0x58, 0xF3, 0xE8, 0x60, - 0x24, 0xBF, 0x0D, 0x9C, 0x78, 0x8B, 0x0B, 0xCB, 0xD5, 0xA8, 0x8E, 0x3E, - 0x9F, 0x71, 0x46, 0x2A, 0x5A, 0x16, 0xE8, 0xE8, 0x63, 0xBC, 0x5E, 0x0A, - 0x5D, 0xE9, 0xF0, 0x99, 0xAB, 0x49, 0x8E, 0x44, 0xB7, 0x36, 0xEF, 0xC6, - 0x42, 0xC1, 0xC3, 0x71}; - -static const unsigned char TA0_RSA_E[] = { - 0x01, 0x00, 0x01}; - -static const br_x509_trust_anchor TAs[1] = { - {{(unsigned char *)TA0_DN, sizeof TA0_DN}, - BR_X509_TA_CA, - {BR_KEYTYPE_RSA, - {.rsa = { - (unsigned char *)TA0_RSA_N, - sizeof TA0_RSA_N, - (unsigned char *)TA0_RSA_E, - sizeof TA0_RSA_E, - }}}}}; -//only one certificate -#define TAs_NUM 1 /* * Connect to the specified host and port. The connected socket is