Skip to content

Echo TLS server example

The echo TLS server example implements a TLS server that echos every line it is receiving.

Go implementation

The server can be run with:

$ bazelisk run //examples/go/echo_tls_server:echo_tls_server -- -port 4444 -server_cert /path/to/cert.pem -server_key /path/to/key.pem

Its source code is the following (examples/go/echo_tls_server/main.go):

// Copyright (c) SandboxAQ. All rights reserved.
// SPDX-License-Identifier: AGPL-3.0-only

package main

import (
    "bufio"
    "flag"
    "fmt"
    "io"
    "log"
    "net"

    // --8<-- [start:go_imports]
    swapi "github.com/sandbox-quantum/sandwich/go/proto/sandwich/api/v1"
    sw "github.com/sandbox-quantum/sandwich/go"
    swtunnel "github.com/sandbox-quantum/sandwich/go/tunnel"
    // --8<-- [end:go_imports]
)

// --8<-- [start:go_server_cfg]
func createServerConfiguration(certfile *string, keyfile *string) *swapi.Configuration {
    return &swapi.Configuration{
        Impl: swapi.Implementation_IMPL_OPENSSL1_1_1_OQS,
        Opts: &swapi.Configuration_Server{
            Server: &swapi.ServerOptions{
                Opts: &swapi.ServerOptions_Tls{
                    Tls: &swapi.TLSServerOptions{
                        CommonOptions: &swapi.TLSOptions{
                            Tls13: &swapi.TLSv13Config{
                                Compliance: &swapi.Compliance{
                                    ClassicalChoice: swapi.ClassicalAlgoChoice_CLASSICAL_ALGORITHMS_ALLOW,
                                },
                                Ke: []string{
                                    "kyber768",
                                    "p256_kyber512",
                                    "prime256v1",
                                },
                            },
                            Tls12: &swapi.TLSv12Config{
                                Ciphersuite: []string{
                                    "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
                                    "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
                                    "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
                                    "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
                                    "TLS_RSA_WITH_AES_256_GCM_SHA384",
                                    "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
                                    "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
                                    "TLS_RSA_WITH_AES_128_GCM_SHA256",
                                },
                            },
                            PeerVerifier: &swapi.TLSOptions_EmptyVerifier{
                                EmptyVerifier: &swapi.EmptyVerifier{},
                            },
                            Identity: &swapi.X509Identity{
                                Certificate: &swapi.Certificate{
                                    Source: &swapi.Certificate_Static{
                                        Static: &swapi.ASN1DataSource{
                                            Data: &swapi.DataSource{
                                                Specifier: &swapi.DataSource_Filename{
                                                    Filename: *certfile,
                                                },
                                            },
                                            Format: swapi.ASN1EncodingFormat_ENCODING_FORMAT_PEM,
                                        },
                                    },
                                },
                                PrivateKey: &swapi.PrivateKey{
                                    Source: &swapi.PrivateKey_Static{
                                        Static: &swapi.ASN1DataSource{
                                            Data: &swapi.DataSource{
                                                Specifier: &swapi.DataSource_Filename{
                                                    Filename: *keyfile,
                                                },
                                            },
                                            Format: swapi.ASN1EncodingFormat_ENCODING_FORMAT_PEM,
                                        },
                                    },
                                },
                            },
                        },
                    },
                },
            },
        },
    }
}

// --8<-- [end:go_server_cfg]

func SWAccept(ctx *swtunnel.TunnelContext, listener net.Listener) (*swtunnel.Tunnel, error) {
    conn, err := listener.Accept()
    if err != nil {
        return nil, err
    }

    // --8<-- [start:go_new_tunnel]
    tunnel, err := swtunnel.NewTunnelWithReadWriter(ctx, conn, &swapi.TunnelConfiguration{
        Verifier: &swapi.TunnelVerifier{
            Verifier: &swapi.TunnelVerifier_EmptyVerifier{
                EmptyVerifier: &swapi.EmptyVerifier{},
            },
        },
    })
    // --8<-- [end:go_new_tunnel]
    if err != nil {
        return nil, err
    }

    err = tunnel.Handshake()
    if err != nil {
        return nil, err
    }

    return tunnel, nil
}

func handleTunnel(tunnel *swtunnel.Tunnel) {
    reader := bufio.NewReader(tunnel)
    for {
        bytes, err := reader.ReadBytes(byte('\n'))
        if err != nil {
            if err != io.EOF {
                log.Println("failed to read data, err:", err)
            }
            return
        }
        fmt.Printf("%s", bytes)
        tunnel.Write(bytes)
    }
}

func main() {
    serverCert := flag.String("server_cert", "", "Server public certificate")
    serverKey := flag.String("server_key", "", "Server private key")
    port := flag.String("port", "", "TCP listening port")
    host := flag.String("host", "127.0.0.1", "TCP listening host")

    flag.Parse()

    if *serverCert == "" || *serverKey == "" {
        log.Fatalln("Please provide a server certificate and key")
    }

    if *port == "" {
        log.Fatalln("Please provide a server port")
    }

    sw_lib_ctx := sw.NewSandwich()

    conf := createServerConfiguration(serverCert, serverKey)
    tunnel_ctx, err := swtunnel.NewTunnelContext(sw_lib_ctx, conf)
    if err != nil {
        log.Fatalln(err)
    }

    listen, err := net.Listen("tcp", *host+":"+*port)
    if err != nil {
        log.Fatalln(err)
    }
    defer listen.Close()
    for {
        conn, err := SWAccept(tunnel_ctx, listen)
        if err != nil {
            log.Println(err)
            continue
        }
        go handleTunnel(conn)
    }
}

Python implementation

The server can be run with:

$ bazelisk run //examples/python/echo_tls_server:echo_tls_server -- -p 4444 -c /path/to/cert.pem -k /path/to/key.pem

Its source code is the following (examples/python/echo_tls_server/main.py):

# Copyright (c) SandboxAQ. All rights reserved.
# SPDX-License-Identifier: AGPL-3.0-only


# --8<-- [start:py_imports_proto]
import pysandwich.proto.api.v1.compliance_pb2 as Compliance
import pysandwich.proto.api.v1.configuration_pb2 as SandwichTunnelProto
import pysandwich.proto.api.v1.encoding_format_pb2 as EncodingFormat
import pysandwich.proto.api.v1.listener_configuration_pb2 as ListenerAPI
import pysandwich.proto.api.v1.verifiers_pb2 as SandwichVerifiers
import pysandwich.io_helpers as SandwichIOHelpers
import pysandwich.tunnel as SandwichTunnel
from pysandwich.proto.api.v1.tunnel_pb2 import TunnelConfiguration
from pysandwich import listener as SandwichListener
from pysandwich.sandwich import Sandwich

# --8<-- [end:py_imports_proto]


# --8<-- [start:py_server_cfg]
def create_server_conf(cert_path: str, key_path: str) -> SandwichTunnelProto:
    conf = SandwichTunnelProto.Configuration()
    conf.impl = SandwichTunnelProto.IMPL_OPENSSL1_1_1_OQS

    # Sets TLS 1.3 Compliance, Key Establishment (KE) and Ciphersuites.
    tls13 = conf.server.tls.common_options.tls13
    tls13.ke.append("X25519")
    tls13.compliance.classical_choice = Compliance.CLASSICAL_ALGORITHMS_ALLOW

    # Sets TLS 1.2 Ciphersuite.
    tls12 = conf.server.tls.common_options.tls12
    ciphers = [
        "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
        "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
        "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
        "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
        "TLS_RSA_WITH_AES_256_GCM_SHA384",
        "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
        "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
        "TLS_RSA_WITH_AES_128_GCM_SHA256",
    ]
    tls12.ciphersuite.extend(ciphers)

    conf.server.tls.common_options.empty_verifier.CopyFrom(
        SandwichVerifiers.EmptyVerifier()
    )
    conf.server.tls.common_options.identity.certificate.static.data.filename = cert_path
    conf.server.tls.common_options.identity.certificate.static.format = (
        EncodingFormat.ENCODING_FORMAT_PEM
    )

    conf.server.tls.common_options.identity.private_key.static.data.filename = key_path
    conf.server.tls.common_options.identity.private_key.static.format = (
        EncodingFormat.ENCODING_FORMAT_PEM
    )

    return conf


def create_server_tun_conf() -> TunnelConfiguration:
    tun_conf = TunnelConfiguration()
    tun_conf.verifier.empty_verifier.CopyFrom(SandwichVerifiers.EmptyVerifier())
    return tun_conf


def create_tcp_listener(hostname: str, port: int) -> SandwichListener.Listener:
    """Creates the configuration for a TCP listener.
    Returns:
        A TCP listener which is listening on hostname:port.
    """
    conf = ListenerAPI.ListenerConfiguration()
    conf.tcp.addr.hostname = hostname
    conf.tcp.addr.port = port
    conf.tcp.blocking_mode = ListenerAPI.BLOCKINGMODE_BLOCKING

    return SandwichListener.Listener(conf)


# --8<-- [end:py_server_cfg]
sw = Sandwich()


def server_to_client(server_ctx_conf, swio: SandwichIOHelpers.SwTunnelIOWrapper):
    # --8<-- [start:py_ctx]
    server_tun_conf = create_server_tun_conf()
    server = SandwichTunnel.Tunnel(server_ctx_conf, swio, server_tun_conf)

    server.handshake()
    state = server.state()
    assert (
        state == server.State.STATE_HANDSHAKE_DONE
    ), f"Expected state HANDSHAKE_DONE, got {state}"

    while True:
        data = b""
        while True:
            c = server.read(1)
            data += c
            if c == b"\n":
                break
        server.write(data)

    server.close()
    # --8<-- [end:py_ctx]


def main(hostname: str, port: int, cert: str, key: str):
    server_ctx_conf = SandwichTunnel.Context.from_config(
        sw, create_server_conf(cert, key)
    )
    listener = create_tcp_listener(hostname, port)
    listener.listen()

    while True:
        server_io = listener.accept()
        server_to_client(server_ctx_conf, server_io)


if __name__ == "__main__":
    import argparse

    parser = argparse.ArgumentParser(prog="Echo TLS server using Sandwich")
    parser.add_argument("-p", "--port", type=int, help="Listening port", required=True)
    parser.add_argument("--host", type=str, help="Listening host", default="127.0.0.1")
    parser.add_argument(
        "-k", "--key", type=str, help="Path to the server private key", required=True
    )
    parser.add_argument(
        "-c",
        "--cert",
        type=str,
        help="Path to the server public certificate",
        required=True,
    )
    args = parser.parse_args()

    main(args.host, args.port, args.cert, args.key)

Rust implementation

The server can be run with:

$ bazelisk run //examples/rust/echo_tls_server:echo_tls_server -- -p 4444 --certificate /path/to/cert.pem --private-key /path/to/key.pem

Its source code is the following (examples/rust/echo_tls_server/main.rs):