Plan 9 from Bell Labs’s /usr/web/sources/contrib/mospak/tls-1.2/README

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


tls-1.2: client and server support for 9legacy

TLS 1.2 for 9legacy in both directions: ECDHE key exchange, AEAD
ciphers, SNI, and X.509 path validation.  Twenty-three patches
against libsec, the kernel record layer, hget, webfs and ip/httpd.

With the series applied:

    - hget(1), webfs(4) and abaco(1) reach RSA- and ECDSA-fronted
      servers over ECDHE (X25519, secp256r1, secp384r1) with
      AES-128-GCM or ChaCha20-Poly1305, and send SNI.
    - ip/httpd serves HTTPS and picks a certificate per vhost from
      the presented name (RFC 6066 Section 3).
    - Certificates are verified by default against
      /sys/lib/tls/ca.pem: chain, hostname, validity window, and
      the RFC 5280 Section 4.2 constraints (basicConstraints,
      keyUsage, extKeyUsage, nameConstraints).
    - The server echoes renegotiation_info on the initial handshake
      and refuses renegotiation afterwards (RFC 5746).
    - Both sides negotiate extended_master_secret where the peer
      offers it (RFC 7627).
    - TLS 1.0 and TLS 1.1 are refused on both sides (RFC 8996), and
      a version mismatch answers with the protocol_version alert.
    - Scalar multiplication is constant time: X25519 selects each
      ladder step with a conditional swap rather than a branch on
      the scalar, and the NIST curves use a Montgomery ladder.  The
      field arithmetic beneath them is not claimed to be.
    - The record layer zeroes cipher state, MAC keys and the Secret
      on session close, and its sequence number is 64 bits wide, so
      the AEAD nonce cannot repeat under one key.
    - The handshake bounds every message it writes and every length
      it reads, the ASN.1 decoder limits its own recursion, and the
      send buffer belongs to the connection rather than to the
      process.


Apply

The patches are unified diffs against the iso-extracted 9legacy
tree, which is Plan 9 with the Stable and 9k queues already
applied.  Order matters:

    cd /
    for p in \
        libsec-ecdhe-aead-primitives \
        tls-aead-record-layer \
        tls-record-zeroize-on-free \
        tls-ecdhe-sni \
        libsec-ecc-ecdsa-primitives \
        libsec-x509-chain-hostname \
        tls-ecdhe-ecdsa-and-chain \
        tls-nist-ecdhe-curves \
        libsec-x509-validity-dates \
        libsec-x509-extension-framework \
        libsec-x509-rfc5280-constraints \
        tls-ca-bundle-default \
        tls-aead-cipher-preference \
        tls-extension-framework \
        tls-server-reneg-info \
        tls-extended-master-secret \
        ip-httpd-vhosts-sni \
        libsec-ecmul-ct-p256 \
        libsec-ecmul-ct-p384 \
        tls-min-version-tls12 \
        libsec-x509-decoder-limits \
        libsec-tlshand-message-bounds \
        tls-sendbuf-per-connection \
    ; do
        echo === $p ===
        ape/patch -p0 < /n/contrib/tls-1.2/$p.diff
    done

They apply with no fuzz and leave no .rej.  Anything else means
the baseline is not the tree described above.


Reverse

Back out in the opposite order, with -R:

    cd /
    for p in \
        tls-sendbuf-per-connection \
        libsec-tlshand-message-bounds \
        libsec-x509-decoder-limits \
        tls-min-version-tls12 \
        libsec-ecmul-ct-p384 \
        libsec-ecmul-ct-p256 \
        ip-httpd-vhosts-sni \
        tls-extended-master-secret \
        tls-server-reneg-info \
        tls-extension-framework \
        tls-aead-cipher-preference \
        tls-ca-bundle-default \
        libsec-x509-rfc5280-constraints \
        libsec-x509-extension-framework \
        libsec-x509-validity-dates \
        tls-nist-ecdhe-curves \
        tls-ecdhe-ecdsa-and-chain \
        libsec-x509-chain-hostname \
        libsec-ecc-ecdsa-primitives \
        tls-ecdhe-sni \
        tls-record-zeroize-on-free \
        tls-aead-record-layer \
        libsec-ecdhe-aead-primitives \
    ; do
        echo === $p -R ===
        ape/patch -p0 -R < /n/contrib/tls-1.2/$p.diff
    done


Build

    rc /n/contrib/tls-1.2/tools/build.rc

This rebuilds libsec, then every program that links its TLS --
hget, webfs, tlsclient, tlssrv, ip/httpd, vncs, vncv, the upas
mail programs, wpa, httpfile and ftpfs -- then the kernel.  Plan 9
links statically, so a program left unbuilt keeps a whole copy of
the old handshake; that is why the list is long.

    rc /n/contrib/tls-1.2/tools/build.rc userspace

skips the kernel.  Three patches modify sys/src/9/port/devtls.c
(tls-aead-record-layer, tls-record-zeroize-on-free and
tls-ecdhe-ecdsa-and-chain), so a kernel build and a reboot are
needed before HTTPS works.

mk does not know that a program depends on libsec.a, so build.rc
deletes the intermediates first to force the relink.  It also
copies the new kernel into the 9fat partition, which 9load reads;
without that the next boot loads the old one.  Then:

    fshalt -r


Test

    rc /n/contrib/tls-1.2/tools/test.rc

Fourteen probes against real servers: eleven expect success, and
three expect a named rejection -- a self-signed certificate, a
wrong hostname, and an expired certificate.  The summary reads
ok: 14 / 14.


Scope limits

Not here, and each declined rather than unfinished:

    - Serving an ECDSA certificate.  It needs factotum to speak
      proto=ecdsa, which 9legacy factotum does not.  An RSA
      certificate serves every client the self-test reaches.
    - Server-side P-256 and P-384 ECDHE.  The server offers X25519
      alone; a client with no X25519 will not handshake with it.
      The client offers all three, which is the direction that
      reaches the web.
    - RSA-PSS signatures.  The verify path takes
      RSA-PKCS1-SHA{256,384,512} and ECDSA-SECP{256R1,384R1}-SHA{256,384}.
    - Session resumption, mTLS, ALPN, encrypt-then-MAC, HSTS, and
      HTTP-to-HTTPS redirection.
    - Fallback SCSV.  RFC 8996 obsoletes it: with TLS 1.0 and 1.1
      refused there is no older version to fall back to.
    - TLS 1.3.  A different protocol, not an extension of this one.


Files

    README                                this file
    libsec-ecdhe-aead-primitives.diff     X25519, AES-GCM and
                                          ChaCha20-Poly1305 in libsec
    tls-aead-record-layer.diff            AEAD record layer in devtls
    tls-record-zeroize-on-free.diff       zero cipher state, MAC keys
                                          and Secret on close
    tls-ecdhe-sni.diff                    ECDHE-RSA and SNI, both
                                          directions
    libsec-ecc-ecdsa-primitives.diff      curve parameters and ECDSA
                                          verify
    libsec-x509-chain-hostname.diff       chain walk and hostname match
                                          (RFC 5280, RFC 6125)
    tls-ecdhe-ecdsa-and-chain.diff        client-side ECDHE-ECDSA
    tls-nist-ecdhe-curves.diff            P-256 and P-384 ECDHE, client
                                          side (RFC 8422)
    libsec-x509-validity-dates.diff       notBefore and notAfter
                                          (RFC 5280 Section 4.1.2.5)
    libsec-x509-extension-framework.diff  X.509 v3 extension parser
    libsec-x509-rfc5280-constraints.diff  basicConstraints, keyUsage,
                                          extKeyUsage, nameConstraints
                                          (RFC 5280 Section 4.2)
    tls-ca-bundle-default.diff            /sys/lib/tls/ca.pem as the
                                          default trust anchor
    tls-aead-cipher-preference.diff       server-side suite choice and
                                          the certificate-key gate
                                          (RFC 4492 Section 2.2)
    tls-extension-framework.diff          table-driven extension
                                          dispatch
    tls-server-reneg-info.diff            RFC 5746 echo and refusal
    tls-extended-master-secret.diff       RFC 7627
    ip-httpd-vhosts-sni.diff              per-vhost certificates in
                                          ip/httpd (RFC 6066 Section 3)
    libsec-ecmul-ct-p256.diff             constant-time ladder on P-256
    libsec-ecmul-ct-p384.diff             the same on P-384
    tls-min-version-tls12.diff            refuse TLS 1.0 and 1.1
                                          (RFC 8996)
    libsec-x509-decoder-limits.diff       bound the ASN.1 decoder's
                                          recursion; free what it drops
    libsec-tlshand-message-bounds.diff    bound every handshake message
                                          written and every length read
    tls-sendbuf-per-connection.diff       the handshake send buffer
                                          belongs to the connection
    tools/build.rc                        rebuild libsec, its consumers
                                          and the kernel
    tools/test.rc                         the self-test


Prerequisites

The iso-extracted 9legacy tree, from <https://9legacy.org/download/>.

Applying needs ape/patch.  Building needs write access to /sys and
/$cputype, which in practice means glenda or an account in the
same group.


Trust anchors

/sys/lib/tls/ca.pem ships as a 2021 Mozilla NSS snapshot.  Refresh
it when CAs rotate:

    hget https://curl.se/ca/cacert.pem > /sys/lib/tls/ca.pem

Append a PEM certificate to trust an internal CA as well:

    cat /tmp/internal-ca.pem >> /sys/lib/tls/ca.pem

Hide the bundle for one session to fall back to thumbprint trust:

    bind /dev/null /sys/lib/tls/ca.pem

Rename or remove it to opt out entirely.  hget and webfs decode
the bundle once per process, so a machine that fetches often pays
for it once.


Bugs

The X25519 field arithmetic reduces its coefficients and contracts
its result with loops whose trip counts follow the values, so the
constant-time claim covers the choice of ladder step and not the
arithmetic under it.

Distinguished names are compared as encoded bytes.  RFC 5280
Section 7.1 asks for the RFC 4518 string preparation, which needs
Unicode tables Plan 9 does not carry.  The comparison fails
closed: a name that ought to match but does not yields a rejected
chain, never an accepted one.

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.