mirror of
https://github.com/soypat/lneto.git
synced 2026-08-16 04:43:29 +00:00
277 lines
14 KiB
Go
277 lines
14 KiB
Go
// Package tls implements a heapless, preallocated TLS 1.3 server (RFC 8446).
|
|
//
|
|
// All buffers are supplied by the caller and never grow. There is no
|
|
// allocation in the steady-state record datapath, no goroutines, and no
|
|
// dependency on an operating system. A [Conn] wraps any [io.ReadWriter], so it
|
|
// works over a lneto tcp.Conn, over net.Pipe, or over a bytes.Buffer in tests.
|
|
//
|
|
// # Scope
|
|
//
|
|
// TLS 1.3 only. There is deliberately no TLS 1.2 fallback, no renegotiation,
|
|
// no 0-RTT/early data, and no client certificate support. Each omission
|
|
// removes a whole class of attack surface.
|
|
//
|
|
// # Cryptographic primitives
|
|
//
|
|
// This package implements the record layer, the key schedule and the handshake
|
|
// state machine, but ships no cryptographic primitives of its own. Concrete
|
|
// hashes, AEADs, key agreements and signers cross the [Hasher], [AEADSuite],
|
|
// [KeyAgreement] and [Signer] interfaces. Standard library implementations
|
|
// live in the tlsstd subpackage; a hardware AEAD or a secure-element signer
|
|
// slots in at the same seam without protocol code changing.
|
|
//
|
|
// Note that on embedded targets the Signer is expected to be supplied by the
|
|
// integrator: Go's crypto/ecdsa carries an 88kB precomputed P-256 basepoint
|
|
// table that lands in RAM, which is a third of an RP2040's SRAM.
|
|
//
|
|
// # Randomness
|
|
//
|
|
// Config.Rand is required and has no default. Embedded targets frequently have
|
|
// a weak or stubbed entropy source, and a biased ECDSA nonce recovers the
|
|
// signing key from two signatures, so the choice is made explicit rather than
|
|
// silently defaulted. Never seed anything here from internal.Prand32/64; those
|
|
// are for TCP initial sequence numbers and DNS IDs, not for key material.
|
|
package tls
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"github.com/soypat/lneto"
|
|
)
|
|
|
|
// Sizes of fixed length headers and the record size limits of RFC 8446 5.2.
|
|
const (
|
|
// SizeHeaderRecord is the size of a TLSPlaintext/TLSCiphertext header:
|
|
// content type(1) + legacy record version(2) + length(2).
|
|
SizeHeaderRecord = 5
|
|
// SizeHeaderHandshake is the size of a Handshake header:
|
|
// message type(1) + 24-bit length(3).
|
|
SizeHeaderHandshake = 4
|
|
|
|
// MaxPlaintext is the largest legal TLSPlaintext.fragment.
|
|
MaxPlaintext = 1 << 14 // 16384
|
|
// MaxCiphertext is the largest legal TLSCiphertext.encrypted_record.
|
|
// The extra 256 bytes cover the content type byte, padding and the AEAD tag.
|
|
MaxCiphertext = 1<<14 + 256 // 16640
|
|
// MaxRecord is the largest legal record as it appears on the wire.
|
|
MaxRecord = SizeHeaderRecord + MaxCiphertext // 16645
|
|
|
|
// MinRecordSizeLimit is the smallest value a peer may advertise in the
|
|
// record_size_limit extension of RFC 8449 4.
|
|
MinRecordSizeLimit = 64
|
|
)
|
|
|
|
//go:generate stringer -type=ContentType,HandshakeType,ExtensionType,AlertDescription,AlertLevel,NamedGroup,SignatureScheme,CipherSuite -linecomment -output stringers.go .
|
|
|
|
// ContentType is the outermost record demultiplexing tag of RFC 8446 5.1.
|
|
type ContentType uint8
|
|
|
|
// Record content types. Values below are the only ones TLS 1.3 defines;
|
|
// everything else must be rejected with an unexpected_message alert.
|
|
const (
|
|
ContentTypeInvalid ContentType = 0 // invalid
|
|
ContentTypeChangeCipherSpec ContentType = 20 // change_cipher_spec
|
|
ContentTypeAlert ContentType = 21 // alert
|
|
ContentTypeHandshake ContentType = 22 // handshake
|
|
ContentTypeApplicationData ContentType = 23 // application_data
|
|
)
|
|
|
|
// HandshakeType identifies a handshake message, RFC 8446 4.
|
|
type HandshakeType uint8
|
|
|
|
// Handshake message types. Types this server never sends or accepts are still
|
|
// listed so that a peer sending one can be logged and rejected precisely.
|
|
const (
|
|
HandshakeTypeClientHello HandshakeType = 1 // client_hello
|
|
HandshakeTypeServerHello HandshakeType = 2 // server_hello
|
|
HandshakeTypeNewSessionTicket HandshakeType = 4 // new_session_ticket
|
|
HandshakeTypeEndOfEarlyData HandshakeType = 5 // end_of_early_data
|
|
HandshakeTypeEncryptedExtensions HandshakeType = 8 // encrypted_extensions
|
|
HandshakeTypeCertificate HandshakeType = 11 // certificate
|
|
HandshakeTypeCertificateRequest HandshakeType = 13 // certificate_request
|
|
HandshakeTypeCertificateVerify HandshakeType = 15 // certificate_verify
|
|
HandshakeTypeFinished HandshakeType = 20 // finished
|
|
HandshakeTypeKeyUpdate HandshakeType = 24 // key_update
|
|
HandshakeTypeMessageHash HandshakeType = 254 // message_hash
|
|
)
|
|
|
|
// ExtensionType identifies a hello extension, RFC 8446 4.2.
|
|
type ExtensionType uint16
|
|
|
|
// Extension types. Those marked "ignored" are ones a browser sends and this
|
|
// server parses past without erroring; being strict about unknown extensions
|
|
// breaks real clients.
|
|
const (
|
|
ExtServerName ExtensionType = 0 // server_name
|
|
ExtMaxFragmentLength ExtensionType = 1 // max_fragment_length
|
|
ExtStatusRequest ExtensionType = 5 // status_request
|
|
ExtSupportedGroups ExtensionType = 10 // supported_groups
|
|
ExtECPointFormats ExtensionType = 11 // ec_point_formats
|
|
ExtSignatureAlgorithms ExtensionType = 13 // signature_algorithms
|
|
ExtALPN ExtensionType = 16 // application_layer_protocol_negotiation
|
|
ExtSignedCertificateTimestamp ExtensionType = 18 // signed_certificate_timestamp
|
|
ExtPadding ExtensionType = 21 // padding
|
|
ExtExtendedMasterSecret ExtensionType = 23 // extended_master_secret
|
|
ExtCompressCertificate ExtensionType = 27 // compress_certificate
|
|
ExtRecordSizeLimit ExtensionType = 28 // record_size_limit
|
|
ExtSessionTicket ExtensionType = 35 // session_ticket
|
|
ExtPreSharedKey ExtensionType = 41 // pre_shared_key
|
|
ExtEarlyData ExtensionType = 42 // early_data
|
|
ExtSupportedVersions ExtensionType = 43 // supported_versions
|
|
ExtCookie ExtensionType = 44 // cookie
|
|
ExtPSKKeyExchangeModes ExtensionType = 45 // psk_key_exchange_modes
|
|
ExtCertificateAuthorities ExtensionType = 47 // certificate_authorities
|
|
ExtSignatureAlgorithmsCert ExtensionType = 50 // signature_algorithms_cert
|
|
ExtKeyShare ExtensionType = 51 // key_share
|
|
ExtApplicationSettings ExtensionType = 17513 // application_settings
|
|
ExtEncryptedClientHello ExtensionType = 65037 // encrypted_client_hello
|
|
ExtRenegotiationInfo ExtensionType = 65281 // renegotiation_info
|
|
)
|
|
|
|
// AlertLevel is the legacy severity byte of an alert. In TLS 1.3 every alert
|
|
// except close_notify and user_canceled is fatal regardless of this field
|
|
// (RFC 8446 6.1), so it is carried for wire compatibility only and must never
|
|
// be used to decide whether to continue.
|
|
type AlertLevel uint8
|
|
|
|
// Alert levels.
|
|
const (
|
|
AlertLevelWarning AlertLevel = 1 // warning
|
|
AlertLevelFatal AlertLevel = 2 // fatal
|
|
)
|
|
|
|
// AlertDescription is the alert code of RFC 8446 6.
|
|
type AlertDescription uint8
|
|
|
|
// Alert descriptions. Only the subset a TLS 1.3 server can legitimately send
|
|
// or receive is listed.
|
|
const (
|
|
AlertCloseNotify AlertDescription = 0 // close_notify
|
|
AlertUnexpectedMessage AlertDescription = 10 // unexpected_message
|
|
AlertBadRecordMAC AlertDescription = 20 // bad_record_mac
|
|
AlertRecordOverflow AlertDescription = 22 // record_overflow
|
|
AlertHandshakeFailure AlertDescription = 40 // handshake_failure
|
|
AlertBadCertificate AlertDescription = 42 // bad_certificate
|
|
AlertUnsupportedCertificate AlertDescription = 43 // unsupported_certificate
|
|
AlertCertificateRevoked AlertDescription = 44 // certificate_revoked
|
|
AlertCertificateExpired AlertDescription = 45 // certificate_expired
|
|
AlertCertificateUnknown AlertDescription = 46 // certificate_unknown
|
|
AlertIllegalParameter AlertDescription = 47 // illegal_parameter
|
|
AlertUnknownCA AlertDescription = 48 // unknown_ca
|
|
AlertAccessDenied AlertDescription = 49 // access_denied
|
|
AlertDecodeError AlertDescription = 50 // decode_error
|
|
AlertDecryptError AlertDescription = 51 // decrypt_error
|
|
AlertProtocolVersion AlertDescription = 70 // protocol_version
|
|
AlertInsufficientSecurity AlertDescription = 71 // insufficient_security
|
|
AlertInternalError AlertDescription = 80 // internal_error
|
|
AlertInappropriateFallback AlertDescription = 86 // inappropriate_fallback
|
|
AlertUserCanceled AlertDescription = 90 // user_canceled
|
|
AlertMissingExtension AlertDescription = 109 // missing_extension
|
|
AlertUnsupportedExtension AlertDescription = 110 // unsupported_extension
|
|
AlertUnrecognizedName AlertDescription = 112 // unrecognized_name
|
|
AlertBadCertificateStatusResponse AlertDescription = 113 // bad_certificate_status_response
|
|
AlertUnknownPSKIdentity AlertDescription = 115 // unknown_psk_identity
|
|
AlertCertificateRequired AlertDescription = 116 // certificate_required
|
|
AlertNoApplicationProtocol AlertDescription = 120 // no_application_protocol
|
|
)
|
|
|
|
// Protocol versions as they appear on the wire.
|
|
const (
|
|
// VersionTLS12 is the value TLS 1.3 requires in ClientHello.legacy_version
|
|
// and in ServerHello.legacy_version for middlebox compatibility.
|
|
VersionTLS12 uint16 = 0x0303
|
|
// VersionTLS13 is the real negotiated version, carried only in the
|
|
// supported_versions extension.
|
|
VersionTLS13 uint16 = 0x0304
|
|
// VersionTLS10 appears in the legacy_record_version of an initial
|
|
// ClientHello record. The field is ignored entirely on receipt.
|
|
VersionTLS10 uint16 = 0x0301
|
|
)
|
|
|
|
// NamedGroup identifies a key exchange group, RFC 8446 4.2.7.
|
|
type NamedGroup uint16
|
|
|
|
// Named groups. Only X25519 is implemented; the rest are recognized so that
|
|
// group selection and HelloRetryRequest can report precisely what was offered.
|
|
const (
|
|
GroupSECP256R1 NamedGroup = 0x0017 // secp256r1
|
|
GroupSECP384R1 NamedGroup = 0x0018 // secp384r1
|
|
GroupSECP521R1 NamedGroup = 0x0019 // secp521r1
|
|
GroupX25519 NamedGroup = 0x001d // x25519
|
|
GroupX448 NamedGroup = 0x001e // x448
|
|
GroupX25519MLKEM768 NamedGroup = 0x11ec // x25519mlkem768
|
|
)
|
|
|
|
// SignatureScheme identifies a signature algorithm, RFC 8446 4.2.3.
|
|
type SignatureScheme uint16
|
|
|
|
// Signature schemes. Browsers do not accept Ed25519 certificates, so
|
|
// [SigECDSAP256SHA256] is the practical minimum for a public-facing server.
|
|
const (
|
|
SigRSAPKCS1SHA256 SignatureScheme = 0x0401 // rsa_pkcs1_sha256
|
|
SigRSAPKCS1SHA384 SignatureScheme = 0x0501 // rsa_pkcs1_sha384
|
|
SigRSAPKCS1SHA512 SignatureScheme = 0x0601 // rsa_pkcs1_sha512
|
|
SigECDSAP256SHA256 SignatureScheme = 0x0403 // ecdsa_secp256r1_sha256
|
|
SigECDSAP384SHA384 SignatureScheme = 0x0503 // ecdsa_secp384r1_sha384
|
|
SigECDSAP521SHA512 SignatureScheme = 0x0603 // ecdsa_secp521r1_sha512
|
|
SigRSAPSSRSAESHA256 SignatureScheme = 0x0804 // rsa_pss_rsae_sha256
|
|
SigRSAPSSRSAESHA384 SignatureScheme = 0x0805 // rsa_pss_rsae_sha384
|
|
SigRSAPSSRSAESHA512 SignatureScheme = 0x0806 // rsa_pss_rsae_sha512
|
|
SigEd25519 SignatureScheme = 0x0807 // ed25519
|
|
SigRSAPSSPSSSHA256 SignatureScheme = 0x0809 // rsa_pss_pss_sha256
|
|
)
|
|
|
|
// CipherSuite identifies an AEAD plus hash pair, RFC 8446 B.4.
|
|
type CipherSuite uint16
|
|
|
|
// TLS 1.3 cipher suites. Only [SuiteAES128GCMSHA256] is implemented; it is the
|
|
// one suite RFC 8446 9.1 makes mandatory to implement.
|
|
const (
|
|
SuiteAES128GCMSHA256 CipherSuite = 0x1301 // TLS_AES_128_GCM_SHA256
|
|
SuiteAES256GCMSHA384 CipherSuite = 0x1302 // TLS_AES_256_GCM_SHA384
|
|
SuiteChaCha20Poly1305SHA256 CipherSuite = 0x1303 // TLS_CHACHA20_POLY1305_SHA256
|
|
SuiteAES128CCMSHA256 CipherSuite = 0x1304 // TLS_AES_128_CCM_SHA256
|
|
SuiteAES128CCM8SHA256 CipherSuite = 0x1305 // TLS_AES_128_CCM_8_SHA256
|
|
)
|
|
|
|
// IsGREASE reports whether v is one of the 16 reserved GREASE values of
|
|
// RFC 8701. Chrome injects GREASE values into its offered cipher suites,
|
|
// supported groups, extensions, ALPN protocol list and key shares. They carry
|
|
// no meaning and must be skipped wherever they appear; rejecting them breaks
|
|
// Chrome outright.
|
|
//
|
|
// GREASE values have both bytes equal and of the form 0x?a.
|
|
func IsGREASE(v uint16) bool {
|
|
return v&0x0f0f == 0x0a0a && v>>8 == v&0xff
|
|
}
|
|
|
|
// ErrNeedMore is returned by incremental parsers and by the record layer when
|
|
// the input available so far is a valid prefix but not yet a complete unit. It
|
|
// is not a failure: the caller should retry once more data has arrived.
|
|
//
|
|
// It is deliberately distinct from [lneto.ErrTruncatedFrame], which means the
|
|
// data is complete but malformed.
|
|
var ErrNeedMore = errors.New("tls: need more data")
|
|
|
|
// ErrUnexpectedClose is returned by Conn.Read when the underlying transport
|
|
// reached EOF before a close_notify alert was received. Returning io.EOF here
|
|
// instead would let an attacker who can inject a TCP FIN or RST silently
|
|
// truncate a response or a request body. Callers must treat this as a failure
|
|
// unless the application layer has independently confirmed the message was
|
|
// complete, for example by satisfying a Content-Length.
|
|
var ErrUnexpectedClose = errors.New("tls: connection closed without close_notify")
|
|
|
|
// Errors reported by frame constructors and walkers. These reuse the generic
|
|
// lneto error set so that callers can compare against a single vocabulary.
|
|
var (
|
|
errTruncated error = lneto.ErrTruncatedFrame
|
|
errShortBuffer error = lneto.ErrShortBuffer
|
|
errBadLength error = lneto.ErrInvalidLengthField
|
|
errBadField error = lneto.ErrInvalidField
|
|
errUnsupported error = lneto.ErrUnsupported
|
|
errTrailingBytes = errors.New("tls: trailing bytes after structure")
|
|
errBuilderNest = errors.New("tls: builder nesting depth exceeded")
|
|
errBuilderUnbal = errors.New("tls: builder length prefix unbalanced")
|
|
errAllZeroPlaintext = errors.New("tls: inner plaintext is all padding")
|
|
)
|