Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add NSS endpoints for ECH interop #25

Merged
merged 3 commits into from
Feb 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ testdata: util
mkdir -p ${TESTDATA_DIR}
${UTIL} -make-root -out ${TESTDATA_DIR}/root.crt -key-out ${TESTDATA_DIR}/root.key -host root.com
${UTIL} -make-intermediate -cert-in ${TESTDATA_DIR}/root.crt -key-in ${TESTDATA_DIR}/root.key -out ${TESTDATA_DIR}/example.crt -key-out ${TESTDATA_DIR}/example.key -host example.com
${UTIL} -make-intermediate -cert-in ${TESTDATA_DIR}/root.crt -key-in ${TESTDATA_DIR}/root.key -out ${TESTDATA_DIR}/client_facing.crt -key-out ${TESTDATA_DIR}/client_facing.key -host client-facing.com
${UTIL} -make-intermediate -cert-in ${TESTDATA_DIR}/root.crt -key-in ${TESTDATA_DIR}/root.key -out ${TESTDATA_DIR}/client-facing.crt -key-out ${TESTDATA_DIR}/client-facing.key -host client-facing.com
${UTIL} -make-dc -cert-in ${TESTDATA_DIR}/example.crt -key-in ${TESTDATA_DIR}/example.key -out ${TESTDATA_DIR}/dc.txt
${UTIL} -make-ech -out ${TESTDATA_DIR}/ech_configs -key-out ${TESTDATA_DIR}/ech_key -host client-facing.com

Expand Down
2 changes: 1 addition & 1 deletion cmd/util/make.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ func makeIntermediateCertificate(config *Config, inCertPath string, inKeyPath st
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"tls-interop-runner development certificate"},
OrganizationalUnit: []string{"tls-interop-runner"},
OrganizationalUnit: []string{outPath},
},

NotBefore: config.ValidFrom,
Expand Down
2 changes: 1 addition & 1 deletion cmd/util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const usage = `Usage:
$ util -make-root -out root.crt -key-out root.key -host root.com
$ util -make-intermediate -cert-in parent.crt -key-in parent.key -out child.crt -key-out child.key -host example.com
$ util -make-dc -cert-in leaf.crt -key-in leaf.key -out dc.txt
$ util -make-ech-key -cert-in client_facing.crt -out ech_configs -key-out ech_key
$ util -make-ech-key -cert-in client-facing.crt -out ech_configs -key-out ech_key

Note: This is a barebones CLI intended for basic usage/debugging.
`
Expand Down
4 changes: 2 additions & 2 deletions impl-endpoints/cloudflare-go/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ func init() {
}

clientFacingCert, err := tls.LoadX509KeyPair(
"/testdata/client_facing.crt",
"/testdata/client_facing.key",
"/testdata/client-facing.crt",
"/testdata/client-facing.key",
)
if err != nil {
log.Fatal(err)
Expand Down
50 changes: 50 additions & 0 deletions impl-endpoints/nss/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
FROM golang:latest AS builder

RUN apt-get update \
&& apt-get install -y --no-install-recommends \
build-essential \
ca-certificates \
gyp \
mercurial \
ninja-build \
zlib1g-dev \
&& rm -rf /var/lib/apt/lists/* \
&& apt-get autoremove -y && apt-get clean -y

RUN mkdir /build
WORKDIR /build

# Clone and build. Output in /nss/dist/Debug/.
RUN cd /build \
&& hg clone https://hg.mozilla.org/projects/nspr \
&& hg clone https://hg.mozilla.org/projects/nss \
&& cd nss \
&& ./build.sh -Denable_draft_hpke=1 \
&& cd /

FROM ubuntu:20.04
COPY --from=builder /build/dist/Debug/bin/* /usr/bin/
COPY --from=builder /build/dist/Debug/lib/* /usr/lib/

# Setup the nss database
RUN mkdir /db && cd /db \
&& certutil -N -d . --empty-password

RUN apt-get update && \
apt-get install -y \
net-tools \
tcpdump \
ethtool \
iproute2 \
python3 \
openssl

COPY ech_key_converter.py /

COPY setup.sh /setup.sh
RUN chmod +x /setup.sh

COPY run_endpoint.sh /run_endpoint.sh
RUN chmod +x /run_endpoint.sh

ENTRYPOINT [ "/run_endpoint.sh" ]
87 changes: 87 additions & 0 deletions impl-endpoints/nss/ech_key_converter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#!/usr/bin/python3

"""
This script rewrites an ECHKey to contain a PKCS8-formatted keypair, which is the format
required by the selfserv utility in NSS. Specifically, the output is formed as follows:

* struct {
* opaque pkcs8_ech_keypair<0..2^16-1>;
* ECHConfigs configs<0..2^16>; // draft-ietf-tls-esni-09
* } ECHKey;
"""

import sys
import struct
import base64

ECH_VERSION = 0xFE09
DHKEM_X25519_SHA256 = 0x0020

# Hardcoded ASN.1 for ECPrivateKey, curve25519. See section 2 of rfc5958.
pkcs8_start = b"\x30\x67\x02\x01\x00\x30\x14\x06\x07\x2A\x86\x48\xCE\x3D\x02\x01\x06\x09\x2B\x06\x01\x04\x01\xDA\x47\x0F\x01\x04\x4C\x30\x4A\x02\x01\x01\x04\x20"
pkcs8_pub_header = b"\xa1\x23\x03\x21\x00"


def convert_ech_key(in_file, out_file):
with open(in_file, "rb") as f:
ech_keypair = base64.b64decode(f.read(), None, True)

offset = 0
length = struct.unpack("!H", ech_keypair[:2])[0]
offset += 2
private_key = ech_keypair[offset : offset + length]
offset += length

ech_configs = ech_keypair[offset:]

# Parse the public key out of the ECHConfig.
length = struct.unpack("!H", ech_keypair[offset : offset + 2])[0]
offset += 2
version = struct.unpack("!H", ech_keypair[offset : offset + 2])[0]
offset += 2

if version != ECH_VERSION:
print("ECHConfig.version is not 0xFE09: %x", hex(version))
exit(1)

length = struct.unpack("!H", ech_keypair[offset : offset + 2])[0]
offset += 2

# Public name
length = struct.unpack("!H", ech_keypair[offset : offset + 2])[0]
offset += 2 + length

# Public key
length = struct.unpack("!H", ech_keypair[offset : offset + 2])[0]
offset += 2
public_key = ech_keypair[offset : offset + length]
offset += length

# Verify that the KEM is X25519. We don't support anything else.
kem_id = struct.unpack("!H", ech_keypair[offset : offset + 2])[0]
if kem_id != DHKEM_X25519_SHA256:
print("Unsupported KEM ID: %x", hex(kem_id))
exit(1)

pkcs8 = bytearray()
pkcs8 += (
bytearray(pkcs8_start)
+ bytearray(private_key)
+ bytearray(pkcs8_pub_header)
+ bytearray(public_key)
)

out_bytes = bytearray()
out_bytes += struct.pack("!H", len(pkcs8)) + pkcs8 + ech_configs

out = open(out_file, "wb")
out.write(base64.b64encode(out_bytes))
out.close()


if __name__ == "__main__":
if len(sys.argv) < 3:
print("Usage: ech_key_converter.py <in_ech_keypair> <out_pkcs8>")
exit(1)

convert_ech_key(sys.argv[1], sys.argv[2])
51 changes: 51 additions & 0 deletions impl-endpoints/nss/run_endpoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/bin/bash
set -e

/setup.sh

DB_DIR="db"
P12_PASS="runner"
PORT=4433

rm -rf nss_testdata
mkdir nss_testdata

if [ "$TESTCASE" = "ech-accept" ]; then
# Create a PKCS8 file for the ECH keypair
python3 /ech_key_converter.py testdata/ech_key nss_testdata/ech_key_converted

# Create pfx files for pk12util
openssl pkcs12 -export -out nss_testdata/root.pfx -name root.com -inkey testdata/root.key -in testdata/root.crt -passout pass:"$P12_PASS"
openssl pkcs12 -export -out nss_testdata/example.pfx -name example.com -inkey testdata/example.key -in testdata/example.crt -passout pass:"$P12_PASS"
openssl pkcs12 -export -out nss_testdata/client-facing.pfx -name client-facing.com -inkey testdata/client-facing.key -in testdata/client-facing.crt -passout pass:"$P12_PASS"
else
echo "Test case not supported."
return true
fi

# Import certs and keys
certs=("example" "client-facing" "root")
for i in "${certs[@]}"
do
pk12util -i nss_testdata/"$i".pfx -d "$DB_DIR" -W "$P12_PASS"

# Trust the root
if [ "$i" = "root" ]; then
certutil -A -n "$i".com -t "C,C,C" -i testdata/"$i".crt -d "$DB_DIR"
fi
done

if [ "$ROLE" = "client" ]; then
echo "Running NSS client."
echo "Client params: $SERVER_PARAMS"
echo "Test case: $TESTCASE"
ECH_CONFIGS=$(<testdata/ech_configs)
echo "GET / HTTP/1.0" > req.txt
tstclnt -d "$DB_DIR" -h example.com -p "$PORT" -N "$ECH_CONFIGS" -A req.txt
else
echo "Running NSS server."
echo "Server params: $SERVER_PARAMS"
echo "Test case: $TESTCASE"
ECH_KEY=$(<nss_testdata/ech_key_converted)
selfserv -a example.com -n example.com -a client-facing.com -n client-facing.com -p "$PORT" -d "$DB_DIR" -X "$ECH_KEY"
fi
27 changes: 27 additions & 0 deletions impl-endpoints/nss/setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/bin/bash

echo "Setting up routes..."
# By default, docker containers don't compute UDP / TCP checksums.
# When packets run through ns3 however, the receiving endpoint requires valid checksums.
# This command makes sure that the endpoints set the checksum on outgoing packets.
ethtool -K eth0 tx off

# this relies on the IPv4 address being first in the "hostname -I" output
IP=$(hostname -I | cut -f1 -d" ")
GATEWAY="${IP%.*}.2"
UNNEEDED_ROUTE="${IP%.*}.0"
echo "Endpoint's IPv4 address is $IP"

route add -net 193.167.0.0 netmask 255.255.0.0 gw $GATEWAY
# delete unused route
route del -net $UNNEEDED_ROUTE netmask 255.255.255.0

# this relies on the IPv6 address being second in the "hostname -I" output
IP=$(hostname -I | cut -f2 -d" ")
GATEWAY="${IP%:*}:2"
UNNEEDED_ROUTE="${IP%:*}:"
echo "Endpoint's IPv6 address is $IP"

ip -d route add fd00:cafe:cafe::/48 via $GATEWAY
# delete unused route
ip -d route del $UNNEEDED_ROUTE/64