diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..91fb166 --- /dev/null +++ b/build.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +if [ "$#" -ne 2 ]; then + echo "usage: $0 " + echo + echo "where and are one of the following:" + echo "boringssl, cloudflare-go, nss, rustls" + exit 1 +fi + +env SERVER_SRC=./impl-endpoints SERVER=$2 \ + CLIENT_SRC=./impl-endpoints CLIENT=$1 \ + docker-compose build diff --git a/cmd/util/util.go b/cmd/util/util.go index 14d6e58..bca9361 100644 --- a/cmd/util/util.go +++ b/cmd/util/util.go @@ -113,8 +113,9 @@ func main() { } else if *makeECH { err = utils.MakeECHKey( utils.ECHConfigTemplate{ + Id: 123, // This is chosen at random by the client-facing server. PublicName: *hostName, - Version: utils.ECHVersionDraft09, + Version: utils.ECHVersionDraft10, KemId: uint16(hpke.KEM_X25519_HKDF_SHA256), KdfIds: []uint16{ uint16(hpke.KDF_HKDF_SHA256), diff --git a/impl-endpoints/cloudflare-go/Dockerfile b/impl-endpoints/cloudflare-go/Dockerfile index ad0fde0..16c3ced 100644 --- a/impl-endpoints/cloudflare-go/Dockerfile +++ b/impl-endpoints/cloudflare-go/Dockerfile @@ -5,10 +5,10 @@ FROM golang:latest AS builder RUN apt-get update && \ apt-get install git -RUN git clone https://github.com/cloudflare/go /cf +RUN git clone --branch cf https://github.com/cloudflare/go /cf WORKDIR /cf/src -RUN git checkout 2a17ea31d28264fccfec4e59dc2ac733b6c73dec +RUN git checkout 7c96cb688f153fcbd51bb2c2e2e38f85820ee5c7 RUN ./make.bash FROM ubuntu:20.04 diff --git a/impl-endpoints/nss/Dockerfile b/impl-endpoints/nss/Dockerfile index 5660c55..4f2b82b 100644 --- a/impl-endpoints/nss/Dockerfile +++ b/impl-endpoints/nss/Dockerfile @@ -22,8 +22,8 @@ RUN cd /build \ && hg clone https://hg.mozilla.org/projects/nspr \ --rev b09175587dad2bfb923ec87250ac80461f620577 \ && hg clone https://hg.mozilla.org/projects/nss \ - --rev 38a91427d65fffd0d7f7d2b6d0bcee7dc8b77a37 \ && cd nss \ + && hg pull -u -r 98542d9c204f8e91336f0a36239d776d82dc8989 https://hg.mozilla.org/projects/nss-try \ && ./build.sh -Denable_draft_hpke=1 \ && cd / diff --git a/impl-endpoints/nss/ech_key_converter.py b/impl-endpoints/nss/ech_key_converter.py index c168be7..a20172e 100644 --- a/impl-endpoints/nss/ech_key_converter.py +++ b/impl-endpoints/nss/ech_key_converter.py @@ -9,7 +9,7 @@ * struct { * opaque pkcs8_ech_keypair<0..2^16-1>; - * ECHConfigs configs<0..2^16>; // draft-ietf-tls-esni-09 + * ECHConfigList configs<0..2^16>; // draft-ietf-tls-esni-09 * } ECHKey; """ @@ -17,7 +17,7 @@ import struct import base64 -ECH_VERSION = 0xFE09 +ECH_VERSION = 0xFE0A DHKEM_X25519_SHA256 = 0x0020 # Hardcoded ASN.1 for ECPrivateKey, curve25519. See section 2 of rfc5958. @@ -30,35 +30,42 @@ def convert_ech_key(in_file, out_file): ech_keypair = base64.b64decode(f.read(), None, True) offset = 0 + + # Parse the private key. length = struct.unpack("!H", ech_keypair[:2])[0] offset += 2 private_key = ech_keypair[offset : offset + length] offset += length - ech_configs = ech_keypair[offset:] + # Encode the ECHConfigList that will be output. + ech_config_list = ech_keypair[offset:] - # Parse the public key out of the ECHConfig. + # Parse the length of the ECHConfigList. length = struct.unpack("!H", ech_keypair[offset : offset + 2])[0] offset += 2 + + # Parse ECHConfig.version, where ECHConfig is the first configuration in + # ECHConfigList. version = struct.unpack("!H", ech_keypair[offset : offset + 2])[0] offset += 2 + # Verify that the version number is as expected. if version != ECH_VERSION: - print("ECHConfig.version is not 0xFE09: %x", hex(version)) + print("ECHConfig.version is not 0xfe0a: got", hex(version)) exit(1) + # Parse ECHConfig.Length, which indicates the length of + # ECHConfig.contents. 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 + # Parse ECHConfig.contents.key_config.config_id. + config_id = struct.unpack("!B", ech_keypair[offset : offset + 1])[0] + offset += 1 - # Public key - length = struct.unpack("!H", ech_keypair[offset : offset + 2])[0] + # Parse ECHConfig.contents.key_config.kem_id. + kem_id = 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] @@ -66,6 +73,12 @@ def convert_ech_key(in_file, out_file): print("Unsupported KEM ID: %x", hex(kem_id)) exit(1) + # Parse ECHConfig.contents.key_config.public_key. + length = struct.unpack("!H", ech_keypair[offset : offset + 2])[0] + offset += 2 + public_key = ech_keypair[offset : offset + length] + offset += length + pkcs8 = bytearray() pkcs8 += ( bytearray(pkcs8_start) @@ -75,7 +88,7 @@ def convert_ech_key(in_file, out_file): ) out_bytes = bytearray() - out_bytes += struct.pack("!H", len(pkcs8)) + pkcs8 + ech_configs + out_bytes += struct.pack("!H", len(pkcs8)) + pkcs8 + ech_config_list out = open(out_file, "wb") out.write(base64.b64encode(out_bytes)) diff --git a/internal/utils/ech.go b/internal/utils/ech.go index ad60c61..07f2f62 100644 --- a/internal/utils/ech.go +++ b/internal/utils/ech.go @@ -13,12 +13,15 @@ import ( ) const ( - ECHVersionDraft09 uint16 = 0xfe09 // draft-ietf-tls-esni-09 + ECHVersionDraft10 uint16 = 0xfe0a // draft-ietf-tls-esni-10 ) // ECHConfigTemplate defines the parameters for generating an ECH config and // corresponding key. type ECHConfigTemplate struct { + // The 1-byte configuration identifier. + Id uint8 + // The version of ECH to use for this configuration. Version uint16 @@ -65,7 +68,7 @@ type ECHKey struct { // GenerateECHKey generates an ECH config and corresponding key using the // parameters specified by template. func GenerateECHKey(template ECHConfigTemplate) (*ECHKey, error) { - if template.Version != ECHVersionDraft09 { + if template.Version != ECHVersionDraft10 { return nil, errors.New("template version not supported") } @@ -92,13 +95,11 @@ func GenerateECHKey(template ECHConfigTemplate) (*ECHKey, error) { var c cryptobyte.Builder c.AddUint16(template.Version) c.AddUint16LengthPrefixed(func(c *cryptobyte.Builder) { // contents - c.AddUint16LengthPrefixed(func(c *cryptobyte.Builder) { - c.AddBytes([]byte(template.PublicName)) - }) + c.AddUint8(template.Id) + c.AddUint16(template.KemId) c.AddUint16LengthPrefixed(func(c *cryptobyte.Builder) { c.AddBytes(publicKey) }) - c.AddUint16(template.KemId) c.AddUint16LengthPrefixed(func(c *cryptobyte.Builder) { for _, kdfId := range template.KdfIds { for _, aeadId := range template.AeadIds { @@ -108,6 +109,9 @@ func GenerateECHKey(template ECHConfigTemplate) (*ECHKey, error) { } }) c.AddUint16(template.MaximumNameLength) + c.AddUint16LengthPrefixed(func(c *cryptobyte.Builder) { + c.AddBytes([]byte(template.PublicName)) + }) c.AddUint16LengthPrefixed(func(c *cryptobyte.Builder) { c.AddBytes(template.Extensions) }) diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..72ba01d --- /dev/null +++ b/run.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +# After typing Ctrl-C, Docker waits this number of seconds to interrupt the +# containers. +TIMEOUT=0 + +if [ "$#" -ne 3 ]; then + echo "usage: $0 " + echo + echo "where and are one of the following:" + echo "boringssl, cloudflare-go, nss, rustls" + echo + echo "and is one of the following:" + echo "dc, ech-accept, ech-reject" + exit 1 +fi + +env SERVER_SRC=./impl-endpoints SERVER=$2 \ + CLIENT_SRC=./impl-endpoints CLIENT=$1 \ + TESTCASE=$3 \ + docker-compose up --timeout $TIMEOUT + +docker-compose stop