feat(x/nts): add NTS support (#88)

Implement NTS (RFC 8915) with KERecord zero-copy frame, PerformKE
(TLS 1.3 + ExportKeyingMaterial), DeriveKeys, and Client state
machine implementing lneto.StackNode. Client handles cookie pool
management, auth body codec with nonce/ciphertext, and two-exchange
NTP flow with UniqueID verification and AEAD authentication.

Add NTS Server wrapping ntp.Server with AEAD verification/sealing,
and HandleKE for server-side NTS Key Exchange over TLS 1.3.

Use internal.LogAttrs for non-allocating structured logging
throughout the NTS client, matching existing conventions.

Generated with LLM assistance.

Signed-off-by: Marvin Drees <marvin.drees@9elements.com>
This commit is contained in:
Marvin Drees
2026-06-15 16:42:10 +02:00
committed by GitHub
parent 856ac373d5
commit 5f8ca45859
8 changed files with 1778 additions and 0 deletions
+73
View File
@@ -0,0 +1,73 @@
// Package nts implements the Network Time Security (NTS) key exchange and
// authenticated NTP packet construction as specified in RFC 8915.
//
// # Protocol overview
//
// NTS adds an authentication layer on top of NTPv4. It has two phases:
//
// 1. Key Exchange (NTS-KE, TCP port 4460): a TLS 1.3 handshake followed by
// a small application-layer record exchange that negotiates the AEAD
// algorithm and distributes opaque cookies. Use [PerformKE] to run this
// phase over a caller-owned *tls.Conn.
//
// 2. Authenticated NTP: each request carries a Unique-ID extension field, a
// Cookie extension field (from the pool obtained in phase 1), and an
// NTS-Authenticator-and-EEF field sealed with the C2S AEAD key. Use
// [Client] as the lneto [StackNode] for this phase.
//
// # Cipher note
//
// RFC 8915 §5.1 mandates AEAD_AES_SIV_CMAC_256 ([AlgAESSIVCMAC256]); it is the
// only algorithm a compliant implementation must support and the sole one
// registered at the time of writing. This algorithm is not part of the Go
// standard library, and lneto deliberately does not ship cryptographic
// primitives. Callers must therefore supply their own [cipher.AEAD]
// implementation, keyed with the C2S and S2C material in [KESecrets], via
// [ClientConfig] and [ServerConfig]. Any external or standard-library
// [cipher.AEAD] may be plugged in, but only AEAD_AES_SIV_CMAC_256 is
// guaranteed to interoperate with other RFC 8915 peers.
//
//go:generate stringer -type=KERecordType,AEADAlgorithmID -linecomment -output stringers.go
package nts
// KEPort is the IANA-assigned TCP port for the NTS Key Exchange protocol.
const KEPort = 4460
// MaxCookies is the maximum number of cookies the client stores at one time.
// RFC 8915 §5.7 says servers SHOULD send eight cookies.
const MaxCookies = 8
// MaxCookieLen is the maximum byte length of a single NTS cookie.
// Real-world servers typically use 100200 bytes; 256 provides headroom.
const MaxCookieLen = 256
// maxNonceLen is the largest nonce we pre-allocate space for.
// AES-SIV uses 16 bytes; GCM uses 12 bytes.
const maxNonceLen = 16
// KERecordType identifies NTS-KE record types (RFC 8915 §4.1.2).
type KERecordType uint16
const (
RecordEndOfMessage KERecordType = 0 // end of message
RecordNextProtoNeg KERecordType = 1 // next protocol negotiation
RecordError KERecordType = 2 // error
RecordWarning KERecordType = 3 // warning
RecordAEADAlgNeg KERecordType = 4 // AEAD algorithm negotiation
RecordNewCookie KERecordType = 5 // new cookie for NTPv4
RecordNTPv4Server KERecordType = 6 // NTPv4 server negotiation
RecordNTPv4Port KERecordType = 7 // NTPv4 port negotiation
)
// AEADAlgorithmID identifies AEAD algorithms used in NTS (RFC 8915 §5.1).
type AEADAlgorithmID uint16
const (
// AlgAESSIVCMAC256 is AEAD_AES_SIV_CMAC_256 (algorithm number 15).
// This is the only algorithm mandated by RFC 8915 §5.1 and the sole
// registered algorithm at time of writing.
AlgAESSIVCMAC256 AEADAlgorithmID = 15 // AEAD_AES_SIV_CMAC_256
)
// ntpv4ProtocolID is the NTS-KE protocol identifier for NTPv4 (RFC 8915 §4).
const ntpv4ProtocolID uint16 = 0