diff --git a/alert.go b/alert.go index 577cddc..a196135 100644 --- a/alert.go +++ b/alert.go @@ -12,6 +12,8 @@ import "strconv" // which wraps AlertError rather than sending a TLS alert. type AlertError uint8 +var _ error = AlertError(0) + func (e AlertError) Error() string { return alert(e).String() } diff --git a/cipher_suites.go b/cipher_suites.go index bc508fe..9cffca0 100644 --- a/cipher_suites.go +++ b/cipher_suites.go @@ -343,25 +343,16 @@ var disabledCipherSuites = map[uint16]bool{ TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: true, TLS_ECDHE_RSA_WITH_RC4_128_SHA: true, TLS_RSA_WITH_RC4_128_SHA: true, -} -// rsaKexCiphers contains the ciphers which use RSA based key exchange, -// which we also disable by default unless a GODEBUG is set. -var rsaKexCiphers = map[uint16]bool{ - TLS_RSA_WITH_RC4_128_SHA: true, + // RSA key exchange TLS_RSA_WITH_3DES_EDE_CBC_SHA: true, TLS_RSA_WITH_AES_128_CBC_SHA: true, TLS_RSA_WITH_AES_256_CBC_SHA: true, - TLS_RSA_WITH_AES_128_CBC_SHA256: true, TLS_RSA_WITH_AES_128_GCM_SHA256: true, TLS_RSA_WITH_AES_256_GCM_SHA384: true, -} -// tdesCiphers contains 3DES ciphers, -// which we also disable by default unless a GODEBUG is set. -var tdesCiphers = map[uint16]bool{ + // 3DES TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: true, - TLS_RSA_WITH_3DES_EDE_CBC_SHA: true, } var ( diff --git a/common.go b/common.go index 67c6994..cdda38a 100644 --- a/common.go +++ b/common.go @@ -155,11 +155,12 @@ const ( X25519MLKEM768 CurveID = 4588 SecP256r1MLKEM768 CurveID = 4587 SecP384r1MLKEM1024 CurveID = 4589 + MLKEM1024 CurveID = 514 ) func isTLS13OnlyKeyExchange(curve CurveID) bool { switch curve { - case X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024: + case X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024, MLKEM1024: return true default: return false @@ -168,7 +169,7 @@ func isTLS13OnlyKeyExchange(curve CurveID) bool { func isPQKeyExchange(curve CurveID) bool { switch curve { - case X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024: + case X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024, MLKEM1024: return true default: return false @@ -324,6 +325,11 @@ type ConnectionState struct { // are a server, or if we received a HelloRetryRequest if we are a client. HelloRetryRequest bool + // LocalCertificate is the certificate chain presented to the peer, if any, + // during the handshake. This field is only populated for connections which + // are not resumed (DidResume is false). + LocalCertificate [][]byte + // ekm is a closure exposed via ExportKeyingMaterial. ekm func(label string, context []byte, length int) ([]byte, error) @@ -337,11 +343,6 @@ type ConnectionState struct { // the seed. If the connection was set to allow renegotiation via // Config.Renegotiation, or if the connections supports neither TLS 1.3 nor // Extended Master Secret, this function will return an error. -// -// Exporting key material without Extended Master Secret or TLS 1.3 was disabled -// in Go 1.22 due to security issues (see the Security Considerations sections -// of RFC 5705 and RFC 7627), but can be re-enabled with the GODEBUG setting -// tlsunsafeekm=1. func (cs *ConnectionState) ExportKeyingMaterial(label string, context []byte, length int) ([]byte, error) { return cs.ekm(label, context, length) } @@ -499,6 +500,9 @@ type ClientHelloInfo struct { // for use with SupportsCertificate. config *Config + // isQUIC indicates whether the connection is a QUIC connection. + isQUIC bool + // ctx is the context of the handshake that is in progress. ctx context.Context } @@ -597,10 +601,13 @@ type Config struct { LimitFallbackUpload LimitFallback LimitFallbackDownload LimitFallback - // Rand provides the source of entropy for nonces and RSA blinding. + // Rand provides the source of entropy for the connection. // If Rand is nil, TLS uses the cryptographic random reader in package - // crypto/rand. - // The Reader must be safe for use by multiple goroutines. + // crypto/rand. The Reader must be safe for use by multiple goroutines. + // + // Deprecated: this should be left nil in production. Not all TLS + // configurations are guaranteed to use Rand. Test code can use + // [testing/cryptotest.SetGlobalRandom] instead. Rand io.Reader // Time returns the current time as the number of seconds since the epoch. @@ -749,11 +756,7 @@ type Config struct { // the list is ignored. Note that TLS 1.3 ciphersuites are not configurable. // // If CipherSuites is nil, a safe default list is used. The default cipher - // suites might change over time. In Go 1.22 RSA key exchange based cipher - // suites were removed from the default list, but can be re-added with the - // GODEBUG setting tlsrsakex=1. In Go 1.23 3DES cipher suites were removed - // from the default list, but can be re-added with the GODEBUG setting - // tls3des=1. + // suites might change over time. CipherSuites []uint16 // PreferServerCipherSuites is a legacy field and has no effect. @@ -818,9 +821,7 @@ type Config struct { // // By default, TLS 1.2 is currently used as the minimum. TLS 1.0 is the // minimum supported by this package. - // - // The server-side default can be reverted to TLS 1.0 by including the value - // "tls10server=1" in the GODEBUG environment variable. + MinVersion uint16 // MaxVersion contains the maximum TLS version that is acceptable. @@ -957,6 +958,11 @@ type EncryptedClientHelloKey struct { // - DHKEM(P-384, HKDF-SHA384) (0x0011) // - DHKEM(P-521, HKDF-SHA512) (0x0012) // - DHKEM(X25519, HKDF-SHA256) (0x0020) + // - ML-KEM-768 (0x0041) + // - ML-KEM-1024 (0x0042) + // - MLKEM768-P256 (0x0050) + // - MLKEM1024-P384 (0x0051) + // - MLKEM768-X25519 (0x647a) // // and as KDF one of // @@ -1264,7 +1270,7 @@ const roleServer = false // supportedVersions returns the list of supported TLS versions, sorted from // highest to lowest (and hence also in preference order). -func (c *Config) supportedVersions(isClient bool) []uint16 { +func (c *Config) supportedVersions(isClient, isQUIC bool) []uint16 { versions := make([]uint16, 0, len(supportedVersions)) for _, v := range supportedVersions { if fips140tls.Required() && !slices.Contains(allowedSupportedVersionsFIPS, v) { @@ -1282,13 +1288,16 @@ func (c *Config) supportedVersions(isClient bool) []uint16 { if c != nil && c.MaxVersion != 0 && v > c.MaxVersion { continue } + if isQUIC && v < VersionTLS13 { + continue + } versions = append(versions, v) } return versions } -func (c *Config) maxSupportedVersion(isClient bool) uint16 { - supportedVersions := c.supportedVersions(isClient) +func (c *Config) maxSupportedVersion(isClient, isQUIC bool) uint16 { + supportedVersions := c.supportedVersions(isClient, isQUIC) if len(supportedVersions) == 0 { return 0 } @@ -1310,31 +1319,38 @@ func supportedVersionsFromMax(maxVersion uint16) []uint16 { } func (c *Config) curvePreferences(version uint16) []CurveID { - curvePreferences := defaultCurvePreferences() - if fips140tls.Required() { - curvePreferences = slices.DeleteFunc(curvePreferences, func(x CurveID) bool { - return !slices.Contains(allowedCurvePreferencesFIPS, x) - }) - } + return slices.DeleteFunc(curvePreferenceOrder(), func(x CurveID) bool { + return !c.supportsCurve(version, x) + }) +} + +func (c *Config) supportsCurve(version uint16, x CurveID) bool { if c != nil && len(c.CurvePreferences) != 0 { - curvePreferences = slices.DeleteFunc(curvePreferences, func(x CurveID) bool { - return !slices.Contains(c.CurvePreferences, x) - }) + if !slices.Contains(c.CurvePreferences, x) { + return false + } + // Ignore unimplemented entries in c.CurvePreferences. + if !slices.Contains(curvePreferenceOrder(), x) { + return false + } + } else { + if !defaultCurveEnabled(x) { + return false + } } - if version < VersionTLS13 { - curvePreferences = slices.DeleteFunc(curvePreferences, isTLS13OnlyKeyExchange) + if fips140tls.Required() && !slices.Contains(allowedCurvePreferencesFIPS, x) { + return false } - return curvePreferences -} - -func (c *Config) supportsCurve(version uint16, curve CurveID) bool { - return slices.Contains(c.curvePreferences(version), curve) + if version < VersionTLS13 && isTLS13OnlyKeyExchange(x) { + return false + } + return true } // mutualVersion returns the protocol version to use given the advertised // versions of the peer. The highest supported version is preferred. -func (c *Config) mutualVersion(isClient bool, peerVersions []uint16) (uint16, bool) { - supportedVersions := c.supportedVersions(isClient) +func (c *Config) mutualVersion(isClient, isQUIC bool, peerVersions []uint16) (uint16, bool) { + supportedVersions := c.supportedVersions(isClient, isQUIC) for _, v := range supportedVersions { if slices.Contains(peerVersions, v) { return v, true @@ -1420,7 +1436,7 @@ func (chi *ClientHelloInfo) SupportsCertificate(c *Certificate) error { if config == nil { config = &Config{} } - vers, ok := config.mutualVersion(roleServer, chi.SupportedVersions) + vers, ok := config.mutualVersion(roleServer, chi.isQUIC, chi.SupportedVersions) if !ok { return errors.New("no mutually supported protocol versions") } @@ -1644,7 +1660,10 @@ func (c *Config) writeKeyLog(label string, clientRandom, secret []byte) error { _, err := c.KeyLogWriter.Write(logLine) writerMutex.Unlock() - return err + if err != nil { + return fmt.Errorf("tls: KeyLogWriter: %w", err) + } + return nil } // writerMutex protects all KeyLogWriters globally. It is rarely enabled, @@ -1751,6 +1770,10 @@ func (c *lruSessionCache) Put(sessionKey string, cs *ClientSessionState) { return } + if cs == nil { + return + } + if c.q.Len() < c.capacity { entry := &lruSessionCacheEntry{sessionKey, cs} c.m[sessionKey] = c.q.PushFront(entry) diff --git a/common_string.go b/common_string.go index bab3b84..09ce16c 100644 --- a/common_string.go +++ b/common_string.go @@ -82,17 +82,19 @@ func _() { _ = x[X25519MLKEM768-4588] _ = x[SecP256r1MLKEM768-4587] _ = x[SecP384r1MLKEM1024-4589] + _ = x[MLKEM1024-514] } const ( _CurveID_name_0 = "CurveP256CurveP384CurveP521" _CurveID_name_1 = "X25519" - _CurveID_name_2 = "SecP256r1MLKEM768X25519MLKEM768SecP384r1MLKEM1024" + _CurveID_name_2 = "MLKEM1024" + _CurveID_name_3 = "SecP256r1MLKEM768X25519MLKEM768SecP384r1MLKEM1024" ) var ( _CurveID_index_0 = [...]uint8{0, 9, 18, 27} - _CurveID_index_2 = [...]uint8{0, 17, 31, 49} + _CurveID_index_3 = [...]uint8{0, 17, 31, 49} ) func (i CurveID) String() string { @@ -102,9 +104,11 @@ func (i CurveID) String() string { return _CurveID_name_0[_CurveID_index_0[i]:_CurveID_index_0[i+1]] case i == 29: return _CurveID_name_1 + case i == 514: + return _CurveID_name_2 case 4587 <= i && i <= 4589: i -= 4587 - return _CurveID_name_2[_CurveID_index_2[i]:_CurveID_index_2[i+1]] + return _CurveID_name_3[_CurveID_index_3[i]:_CurveID_index_3[i+1]] default: return "CurveID(" + strconv.FormatInt(int64(i), 10) + ")" } diff --git a/conn.go b/conn.go index 3bcca8b..1493012 100644 --- a/conn.go +++ b/conn.go @@ -60,6 +60,7 @@ type Conn struct { ocspResponse []byte // stapled OCSP response scts [][]byte // signed certificate timestamps from server peerCertificates []*x509.Certificate + localCertificate [][]byte // verifiedChains contains the certificate chains that we built, as // opposed to the ones presented by the server. verifiedChains [][]*x509.Certificate @@ -104,12 +105,27 @@ type Conn struct { clientProtocol string // input/output - in, out halfConn - rawInput bytes.Buffer // raw input, starting with a record header - input bytes.Reader // application data waiting to be read, from rawInput.Next - hand bytes.Buffer // handshake data waiting to be read - buffering bool // whether records are buffered in sendBuf - sendBuf []byte // a buffer of records waiting to be sent + in, out halfConn + // rawInput holds raw input, starting with a record header. + // It is nil when no input is buffered, in which case the buffer has + // been returned to rawInputPool so that connections idle in Read do + // not pin a record-sized buffer. It is lazily repopulated from the + // pool by readFromUntil. + rawInput *bytes.Buffer + // smallInput is a small buffer that serves as rawInput while + // waiting for a record header after rawInput has been returned to + // rawInputPool. It is lazily allocated by readFromUntil and then + // kept for the life of the connection. + smallInput *bytes.Buffer + // input holds application data waiting to be read, from rawInput.Next. + input bytes.Reader + // hand holds handshake data waiting to be read. + // It is nil when no handshake data is buffered, in which case the + // buffer has been returned to handPool. Use handBuf and handLen to + // access it. + hand *bytes.Buffer + buffering bool // whether records are buffered in sendBuf + sendBuf []byte // a buffer of records waiting to be sent // bytesSent counts the bytes of application data sent. // packetsSent counts packets. @@ -615,7 +631,9 @@ func (e RecordHeaderError) Error() string { return "tls: " + e.Msg } func (c *Conn) newRecordHeaderError(conn net.Conn, msg string) (err RecordHeaderError) { err.Msg = msg err.Conn = conn - copy(err.RecordHeader[:], c.rawInput.Bytes()) + if c.rawInput != nil { + copy(err.RecordHeader[:], c.rawInput.Bytes()) + } return err } @@ -657,6 +675,19 @@ func (c *Conn) readRecordOrCCS(expectChangeCipherSpec bool) error { return c.in.setErrorLocked(errors.New("tls: internal error: attempted to read record with QUIC transport")) } + // If rawInput is empty, we are about to block in a Read on the + // underlying connection waiting for the next record, possibly for a + // long time. A previous record may have grown rawInput to the maximum + // record size; don't pin that memory while idle. Return the buffer to + // the pool, and let readFromUntil read the header into a small buffer + // and switch back to a pooled record-sized buffer only once the + // payload length is known. + if c.rawInput != nil && c.rawInput.Len() == 0 && c.rawInput != c.smallInput && c.rawInput.Cap() > maxIdleInputCap { + c.rawInput.Reset() + rawInputPool.Put(c.rawInput) + c.rawInput = nil + } + // Read header, payload. if err := c.readFromUntil(c.conn, recordHeaderLen); err != nil { // RFC 8446, Section 6.1 suggests that EOF without an alertCloseNotify @@ -731,13 +762,13 @@ func (c *Conn) readRecordOrCCS(expectChangeCipherSpec bool) error { return c.in.setErrorLocked(c.sendAlert(alertUnexpectedMessage)) } - if typ != recordTypeAlert && typ != recordTypeChangeCipherSpec && len(data) > 0 { + if (typ == recordTypeApplicationData || (typ == recordTypeHandshake && !handshakeComplete)) && len(data) > 0 { // This is a state-advancing message: reset the retry count. c.retryCount = 0 } // Handshake messages MUST NOT be interleaved with other record types in TLS 1.3. - if c.vers == VersionTLS13 && typ != recordTypeHandshake && c.hand.Len() > 0 { + if c.vers == VersionTLS13 && typ != recordTypeHandshake && c.handLen() > 0 { return c.in.setErrorLocked(c.sendAlert(alertUnexpectedMessage)) } @@ -782,7 +813,7 @@ func (c *Conn) readRecordOrCCS(expectChangeCipherSpec bool) error { return c.in.setErrorLocked(c.sendAlert(alertDecodeError)) } // Handshake messages are not allowed to fragment across the CCS. - if c.hand.Len() > 0 { + if c.handLen() > 0 { return c.in.setErrorLocked(c.sendAlert(alertUnexpectedMessage)) } // In TLS 1.3, change_cipher_spec records are ignored until the @@ -818,7 +849,7 @@ func (c *Conn) readRecordOrCCS(expectChangeCipherSpec bool) error { if len(data) == 0 || expectChangeCipherSpec { return c.in.setErrorLocked(c.sendAlert(alertUnexpectedMessage)) } - c.hand.Write(data) + c.handBuf().Write(data) } return nil @@ -838,13 +869,85 @@ func (c *Conn) retryReadRecord(expectChangeCipherSpec bool) error { return c.readRecordOrCCS(expectChangeCipherSpec) } +// rawInputPool pools the record-sized buffers that back Conn.rawInput +// while records are being received. A connection returns its buffer to +// the pool before blocking to wait for a new record, often for a long +// time, so that idle connections do not each pin a record-sized buffer. +// Only buffers with capacity above maxIdleInputCap are pooled; smaller +// buffers stay attached to their connection. +var rawInputPool = sync.Pool{New: func() any { return new(bytes.Buffer) }} + +// maxIdleInputCap is the largest rawInput capacity that a connection +// keeps while waiting for a new record to arrive. It is large enough to +// hold a record header and small records, so that only connections +// receiving larger records pay for the pooled buffer switch below. +const maxIdleInputCap = 1024 + +// handPool pools the buffers that back Conn.hand, which typically grow +// to hold the peer's largest flight of handshake messages. A connection +// returns its buffer to the pool once the handshake completes and after +// buffered post-handshake messages have been consumed, so that +// established connections do not pin it. +var handPool = sync.Pool{New: func() any { return new(bytes.Buffer) }} + +// handBuf returns c.hand for writing, getting a buffer from handPool if +// c.hand is nil. +func (c *Conn) handBuf() *bytes.Buffer { + if c.hand == nil { + c.hand = handPool.Get().(*bytes.Buffer) + } + return c.hand +} + +// handLen returns the number of buffered handshake bytes. +func (c *Conn) handLen() int { + if c.hand == nil { + return 0 + } + return c.hand.Len() +} + +// releaseHand returns c.hand to handPool if it is empty. +func (c *Conn) releaseHand() { + if c.hand != nil && c.hand.Len() == 0 { + c.hand.Reset() + handPool.Put(c.hand) + c.hand = nil + } +} + // readFromUntil reads from r into c.rawInput until c.rawInput contains // at least n bytes or else returns an error. func (c *Conn) readFromUntil(r io.Reader, n int) error { + if c.rawInput == nil { + // The record buffer was released while waiting for a new + // record. Block for the header using the connection's small + // buffer; the switch to a pooled record-sized buffer below + // happens only once the payload length is known and data is + // flowing. + if c.smallInput == nil { + c.smallInput = new(bytes.Buffer) + } + c.rawInput = c.smallInput + } if c.rawInput.Len() >= n { return nil } needs := n - c.rawInput.Len() + if want := c.rawInput.Len() + needs + bytes.MinRead; want > maxIdleInputCap && want > c.rawInput.Cap() { + // Growing past maxIdleInputCap: switch to a pooled buffer so + // that record-sized buffers are recycled across connections + // rather than allocated for every record. + b := rawInputPool.Get().(*bytes.Buffer) + b.Write(c.rawInput.Bytes()) + if c.rawInput == c.smallInput { + c.smallInput.Reset() + } else if c.rawInput.Cap() > maxIdleInputCap { + c.rawInput.Reset() + rawInputPool.Put(c.rawInput) + } + c.rawInput = b + } // There might be extra input waiting on the wire. Make a best effort // attempt to fetch it so that it can be used in (*Conn).Read to // "predict" closeNotify alerts. @@ -1145,7 +1248,7 @@ func (c *Conn) readHandshakeBytes(n int) error { if c.quic != nil { return c.quicReadHandshakeBytes(n) } - for c.hand.Len() < n { + for c.handLen() < n { if err := c.readRecord(); err != nil { return err } @@ -1459,11 +1562,12 @@ func (c *Conn) Read(b []byte) (int, error) { if err := c.readRecord(); err != nil { return 0, err } - for c.hand.Len() > 0 { + for c.handLen() > 0 { if err := c.handlePostHandshakeMessage(); err != nil { return 0, err } } + c.releaseHand() } n, _ := c.input.Read(b) @@ -1641,6 +1745,14 @@ func (c *Conn) handshakeContext(ctx context.Context) (ret error) { panic("tls: internal error: handshake returned an error but is marked successful") } + // The handshake buffer typically grew to hold the peer's largest + // flight of handshake messages and is now empty. Post-handshake + // messages are rare and small, so release the buffer rather than + // pinning it for the life of the connection. + if c.handshakeErr == nil { + c.releaseHand() + } + if c.quic != nil { if c.handshakeErr == nil { c.quicHandshakeComplete() @@ -1671,6 +1783,12 @@ func (c *Conn) handshakeContext(ctx context.Context) (ret error) { } // ConnectionState returns basic TLS details about the connection. +// +// The returned [ConnectionState] is only meaningful after the handshake has +// completed, as reported by [ConnectionState.HandshakeComplete]; before then +// its fields are not populated. The handshake is run automatically by the +// first [Conn.Read] or [Conn.Write], or it can be triggered explicitly with +// [Conn.Handshake]. func (c *Conn) ConnectionState() ConnectionState { c.handshakeMutex.Lock() defer c.handshakeMutex.Unlock() @@ -1690,6 +1808,7 @@ func (c *Conn) connectionStateLocked() ConnectionState { state.ServerName = c.serverName state.CipherSuite = c.cipherSuite state.PeerCertificates = c.peerCertificates + state.LocalCertificate = c.localCertificate state.VerifiedChains = c.verifiedChains state.SignedCertificateTimestamps = c.scts state.OCSPResponse = c.ocspResponse @@ -1703,13 +1822,7 @@ func (c *Conn) connectionStateLocked() ConnectionState { if c.config.Renegotiation != RenegotiateNever { state.ekm = noEKMBecauseRenegotiation } else if c.vers != VersionTLS13 && !c.extMasterSecret { - state.ekm = func(label string, context []byte, length int) ([]byte, error) { - // if ekmgodebug.Value() == "1" { - // ekmgodebug.IncNonDefault() - // return c.ekm(label, context, length) - // } - return noEKMBecauseNoEMS(label, context, length) - } + state.ekm = noEKMBecauseNoEMS } else { state.ekm = c.ekm } @@ -1751,7 +1864,7 @@ func (c *Conn) setReadTrafficSecret(suite *cipherSuiteTLS13, level QUICEncryptio // Ensure that there are no buffered handshake messages before changing the // read keys, since that can cause messages to be parsed that were encrypted // using old keys which are no longer appropriate. - if c.hand.Len() != 0 { + if c.handLen() != 0 { if locked { c.sendAlertLocked(alertUnexpectedMessage) } else { diff --git a/defaults.go b/defaults.go index 5c03801..0829e75 100644 --- a/defaults.go +++ b/defaults.go @@ -12,24 +12,33 @@ import ( // Defaults are collected in this file to allow distributions to more easily patch // them to apply local policies. +// tlsmlkem=0 restores the pre-Go 1.24 default key exchanges. //var tlsmlkem = godebug.New("tlsmlkem") + +// tlssecpmlkem=0 restores the pre-Go 1.26 default key exchanges. //var tlssecpmlkem = godebug.New("tlssecpmlkem") -// defaultCurvePreferences is the default set of supported key exchanges, as -// well as the preference order. -func defaultCurvePreferences() []CurveID { - switch { - // // tlsmlkem=0 restores the pre-Go 1.24 default. - // case tlsmlkem.Value() == "0": - // return []CurveID{X25519, CurveP256, CurveP384, CurveP521} - // // tlssecpmlkem=0 restores the pre-Go 1.26 default. - // case tlssecpmlkem.Value() == "0": - // return []CurveID{X25519MLKEM768, X25519, CurveP256, CurveP384, CurveP521} +// defaultCurveEnabled returns whether the key exchange c is enabled by default. +func defaultCurveEnabled(c CurveID) bool { + switch c { + case X25519, CurveP256, CurveP384, CurveP521: + return true + case X25519MLKEM768: + return true//tlsmlkem.Value() != "0" + case SecP256r1MLKEM768, SecP384r1MLKEM1024: + return true//tlsmlkem.Value() != "0" && tlssecpmlkem.Value() != "0" + default: - return []CurveID{ - X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024, - X25519, CurveP256, CurveP384, CurveP521, - } + return false + } +} + +// curvePreferenceOrder is the fixed preference order of key exchanges. It must +// include every supported key exchange. +func curvePreferenceOrder() []CurveID { + return []CurveID{ + X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024, MLKEM1024, + X25519, CurveP256, CurveP384, CurveP521, } } @@ -57,9 +66,6 @@ func defaultSupportedSignatureAlgorithms() []SignatureScheme { } } -//var tlsrsakex = godebug.New("tlsrsakex") -//var tls3des = godebug.New("tls3des") - func supportedCipherSuites(aesGCMPreferred bool) []uint16 { if aesGCMPreferred { return slices.Clone(cipherSuitesPreferenceOrder) @@ -71,9 +77,7 @@ func supportedCipherSuites(aesGCMPreferred bool) []uint16 { func defaultCipherSuites(aesGCMPreferred bool) []uint16 { cipherSuites := supportedCipherSuites(aesGCMPreferred) return slices.DeleteFunc(cipherSuites, func(c uint16) bool { - return disabledCipherSuites[c] || - rsaKexCiphers[c] || - tdesCiphers[c] + return disabledCipherSuites[c] }) } diff --git a/defaults_fips140.go b/defaults_fips140.go index 63bebd0..901e81d 100644 --- a/defaults_fips140.go +++ b/defaults_fips140.go @@ -35,6 +35,7 @@ var ( X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024, + MLKEM1024, CurveP256, CurveP384, CurveP521, diff --git a/ech.go b/ech.go index f86752e..277eaaa 100644 --- a/ech.go +++ b/ech.go @@ -55,7 +55,7 @@ func (e *echConfigErr) Error() string { func parseECHConfig(enc []byte) (skip bool, ec EchConfig, err error) { s := cryptobyte.String(enc) - ec.raw = []byte(enc) + ec.raw = enc if !s.ReadUint16(&ec.Version) { return false, EchConfig{}, &echConfigErr{"version"} } @@ -65,7 +65,7 @@ func parseECHConfig(enc []byte) (skip bool, ec EchConfig, err error) { if len(ec.raw) < int(ec.Length)+4 { return false, EchConfig{}, &echConfigErr{"length"} } - ec.raw = ec.raw[:ec.Length+4] + ec.raw = ec.raw[:int(ec.Length)+4] if ec.Version != extensionEncryptedClientHello { s.Skip(int(ec.Length)) return true, EchConfig{}, nil @@ -119,7 +119,7 @@ func parseECHConfig(enc []byte) (skip bool, ec EchConfig, err error) { return false, ec, nil } -// parseECHConfigList parses a draft-ietf-tls-esni-18 ECHConfigList, returning a +// parseECHConfigList parses a RFC 9849 ECHConfigList, returning a // slice of parsed ECHConfigs, in the same order they were parsed, or an error // if the list is malformed. func parseECHConfigList(data []byte) ([]EchConfig, error) { @@ -128,7 +128,7 @@ func parseECHConfigList(data []byte) ([]EchConfig, error) { if !s.ReadUint16(&length) { return nil, errMalformedECHConfigList } - if length != uint16(len(data)-2) { + if int(length) != len(data)-2 { return nil, errMalformedECHConfigList } var configs []EchConfig @@ -136,7 +136,7 @@ func parseECHConfigList(data []byte) ([]EchConfig, error) { if len(s) < 4 { return nil, errors.New("tls: malformed ECHConfig") } - configLen := uint16(s[2])<<8 | uint16(s[3]) + configLen := int(s[2])<<8 | int(s[3]) skip, ec, err := parseECHConfig(s) if err != nil { return nil, err diff --git a/handshake_client.go b/handshake_client.go index 22aed23..9f766ee 100644 --- a/handshake_client.go +++ b/handshake_client.go @@ -58,7 +58,7 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCli return nil, nil, nil, errors.New("tls: NextProtos values too large") } - supportedVersions := config.supportedVersions(roleClient) + supportedVersions := config.supportedVersions(roleClient, c.quic != nil) if len(supportedVersions) == 0 { return nil, nil, nil, errors.New("tls: no supported versions satisfy MinVersion and MaxVersion") } @@ -140,7 +140,7 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCli } if len(hello.supportedCurves) == 0 { - return nil, nil, nil, errors.New("tls: no supported elliptic curves for ECDHE") + return nil, nil, nil, errors.New("tls: no supported key exchange methods (CurveIDs)") } // Since the order is fixed, the first one is always the one to send a // key share for. All the PQ hybrids sort first, and produce a fallback @@ -148,7 +148,7 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCli curveID := hello.supportedCurves[0] ke, err := keyExchangeForCurveID(curveID) if err != nil { - return nil, nil, nil, errors.New("tls: CurvePreferences includes unsupported curve") + return nil, nil, nil, errors.New("tls: internal error: supportsCurve accepted unimplemented curve") } keyShareKeys, hello.keyShares, err = ke.keyShares(config.rand()) if err != nil { @@ -317,7 +317,7 @@ func (c *Conn) clientHandshake(ctx context.Context) (err error) { // If we are negotiating a protocol version that's lower than what we // support, check for the server downgrade canaries. // See RFC 8446, Section 4.1.3. - maxVers := c.config.maxSupportedVersion(roleClient) + maxVers := c.config.maxSupportedVersion(roleClient, c.quic != nil) tls12Downgrade := string(serverHello.random[24:]) == downgradeCanaryTLS12 tls11Downgrade := string(serverHello.random[24:]) == downgradeCanaryTLS11 if maxVers == VersionTLS13 && c.vers <= VersionTLS12 && (tls12Downgrade || tls11Downgrade) || @@ -510,7 +510,7 @@ func (c *Conn) pickTLSVersion(serverHello *serverHelloMsg) error { peerVersion = serverHello.supportedVersion } - vers, ok := c.config.mutualVersion(roleClient, []uint16{peerVersion}) + vers, ok := c.config.mutualVersion(roleClient, c.quic != nil, []uint16{peerVersion}) if !ok { c.sendAlert(alertProtocolVersion) return fmt.Errorf("tls: server selected unsupported protocol version %x", peerVersion) @@ -730,6 +730,10 @@ func (hs *clientHandshakeState) doFullHandshake() error { } } + if chainToSend != nil { + hs.c.localCertificate = chainToSend.Certificate + } + shd, ok := msg.(*serverHelloDoneMsg) if !ok { c.sendAlert(alertUnexpectedMessage) @@ -1158,9 +1162,14 @@ func (c *Conn) verifyServerCertificate(certificates [][]byte) error { } } + if fips140tls.Required() && !isCertificateAllowedFIPS(certs[0]) { + c.sendAlert(alertBadCertificate) + err := errors.New("server's certificate is not allowed in FIPS 140-3 mode") + return &CertificateVerificationError{UnverifiedCertificates: certs, Err: err} + } + switch certs[0].PublicKey.(type) { case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey: - break case *mldsa.PublicKey: if c.vers < VersionTLS13 { c.sendAlert(alertIllegalParameter) diff --git a/handshake_client_tls13.go b/handshake_client_tls13.go index 0084dc7..d5e9d36 100644 --- a/handshake_client_tls13.go +++ b/handshake_client_tls13.go @@ -54,7 +54,8 @@ func (hs *clientHandshakeStateTLS13) handshake() error { } // Consistency check on the presence of a keyShare and its parameters. - if hs.keyShareKeys == nil || hs.keyShareKeys.ecdhe == nil || len(hs.hello.keyShares) == 0 { + if hs.keyShareKeys == nil || (hs.keyShareKeys.ecdhe == nil && hs.keyShareKeys.mlkem == nil) || + len(hs.hello.keyShares) == 0 { return c.sendAlert(alertInternalError) } @@ -322,7 +323,7 @@ func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error { ke, err := keyExchangeForCurveID(curveID) if err != nil { c.sendAlert(alertInternalError) - return errors.New("tls: CurvePreferences includes unsupported curve") + return errors.New("tls: internal error: supportsCurve accepted unimplemented curve") } hs.keyShareKeys, hello.keyShares, err = ke.keyShares(c.config.rand()) if err != nil { @@ -755,6 +756,10 @@ func (hs *clientHandshakeStateTLS13) sendClientCertificate() error { return err } + if cert != nil { + hs.c.localCertificate = cert.Certificate + } + certMsg := new(certificateMsgTLS13) certMsg.certificate = *cert diff --git a/handshake_messages.go b/handshake_messages.go index 3653035..cf3c149 100644 --- a/handshake_messages.go +++ b/handshake_messages.go @@ -5,6 +5,7 @@ package reality import ( + "bytes" "errors" "fmt" "slices" @@ -317,7 +318,8 @@ func (m *clientHelloMsg) marshalMsg(echInner bool) ([]byte, error) { }) }) } - if len(m.pskIdentities) > 0 { // pre_shared_key must be the last extension + // pre_shared_key must be the last extension + if len(m.pskIdentities) > 0 && (echInner || len(m.encryptedClientHello) == 0 || bytes.Equal(m.encryptedClientHello, []byte{byte(innerECHExt)})) { // RFC 8446, Section 4.2.11 exts.AddUint16(extensionPreSharedKey) exts.AddUint16LengthPrefixed(func(exts *cryptobyte.Builder) { @@ -1105,6 +1107,16 @@ func (m *encryptedExtensionsMsg) unmarshal(data []byte) bool { return false } m.serverNameAck = true + case extensionStatusRequest, extensionSupportedPoints, + extensionSignatureAlgorithms, extensionSCT, + extensionExtendedMasterSecret, extensionSessionTicket, + extensionPreSharedKey, extensionSupportedVersions, + extensionCookie, extensionPSKModes, + extensionCertificateAuthorities, extensionSignatureAlgorithmsCert, + extensionKeyShare, extensionRenegotiationInfo, + extensionECHOuterExtensions: + // Not allowed in EncryptedExtensions. + return false default: // Ignore unknown extensions. continue @@ -1229,6 +1241,18 @@ func (m *newSessionTicketMsgTLS13) unmarshal(data []byte) bool { if !extData.ReadUint32(&m.maxEarlyData) { return false } + case extensionServerName, extensionStatusRequest, + extensionSupportedCurves, extensionSupportedPoints, + extensionSignatureAlgorithms, extensionALPN, extensionSCT, + extensionExtendedMasterSecret, extensionSessionTicket, + extensionPreSharedKey, extensionSupportedVersions, + extensionCookie, extensionPSKModes, + extensionCertificateAuthorities, extensionSignatureAlgorithmsCert, + extensionKeyShare, extensionQUICTransportParameters, + extensionRenegotiationInfo, extensionECHOuterExtensions, + extensionEncryptedClientHello: + // Not allowed in TLS 1.3 NewSessionTicket. + return false default: // Ignore unknown extensions. continue @@ -1373,6 +1397,15 @@ func (m *certificateRequestMsgTLS13) unmarshal(data []byte) bool { } m.certificateAuthorities = append(m.certificateAuthorities, ca) } + case extensionSupportedCurves, extensionSupportedPoints, + extensionALPN, extensionExtendedMasterSecret, + extensionSessionTicket, extensionPreSharedKey, + extensionEarlyData, extensionSupportedVersions, + extensionCookie, extensionPSKModes, extensionKeyShare, + extensionQUICTransportParameters, extensionRenegotiationInfo, + extensionECHOuterExtensions, extensionEncryptedClientHello: + // Not allowed in TLS 1.3 CertificateRequest. + return false default: // Ignore unknown extensions. continue @@ -1583,6 +1616,18 @@ func unmarshalCertificate(s *cryptobyte.String, certificate *Certificate) bool { certificate.SignedCertificateTimestamps = append( certificate.SignedCertificateTimestamps, sct) } + case extensionServerName, extensionSupportedCurves, + extensionSupportedPoints, extensionSignatureAlgorithms, + extensionALPN, extensionExtendedMasterSecret, + extensionSessionTicket, extensionPreSharedKey, + extensionEarlyData, extensionSupportedVersions, + extensionCookie, extensionPSKModes, + extensionCertificateAuthorities, extensionSignatureAlgorithmsCert, + extensionKeyShare, extensionQUICTransportParameters, + extensionRenegotiationInfo, extensionECHOuterExtensions, + extensionEncryptedClientHello: + // Not allowed in Certificate. + return false default: // Ignore unknown extensions. continue diff --git a/handshake_server.go b/handshake_server.go index 422cdb1..a749cc0 100644 --- a/handshake_server.go +++ b/handshake_server.go @@ -189,7 +189,7 @@ func (c *Conn) readClientHello(ctx context.Context) (*clientHelloMsg, *echServer } else if len(clientVersions) == 0 { clientVersions = supportedVersionsFromMax(clientHello.vers) } - c.vers, ok = c.config.mutualVersion(roleServer, clientVersions) + c.vers, ok = c.config.mutualVersion(roleServer, c.quic != nil, clientVersions) if !ok { c.sendAlert(alertProtocolVersion) return nil, nil, fmt.Errorf("tls: client offered only unsupported versions: %x", clientVersions) @@ -236,7 +236,7 @@ func (hs *serverHandshakeState) processClientHello() error { hs.hello.random = make([]byte, 32) serverRandom := hs.hello.random // Downgrade protection canaries. See RFC 8446, Section 4.1.3. - maxVers := c.config.maxSupportedVersion(roleServer) + maxVers := c.config.maxSupportedVersion(roleServer, c.quic != nil) if maxVers >= VersionTLS12 && c.vers < maxVers || testingOnlyForceDowngradeCanary { if c.vers == VersionTLS12 { copy(serverRandom[24:], downgradeCanaryTLS12) @@ -280,6 +280,7 @@ func (hs *serverHandshakeState) processClientHello() error { } return err } + if hs.clientHello.scts { hs.hello.scts = hs.cert.SignedCertificateTimestamps } @@ -411,7 +412,7 @@ func (hs *serverHandshakeState) pickCipherSuite() error { for _, id := range hs.clientHello.cipherSuites { if id == TLS_FALLBACK_SCSV { // The client is doing a fallback connection. See RFC 7507. - if hs.clientHello.vers < c.config.maxSupportedVersion(roleServer) { + if hs.clientHello.vers < c.config.maxSupportedVersion(roleServer, c.quic != nil) { c.sendAlert(alertInappropriateFallback) return errors.New("tls: client using inappropriate protocol fallback") } @@ -614,6 +615,10 @@ func (hs *serverHandshakeState) doFullHandshake() error { certMsg := new(certificateMsg) certMsg.certificates = hs.cert.Certificate + // Set localCertificate here, rather than at certificate selection time, so + // that it is only populated when a certificate is actually presented to the + // peer, and not on resumed connections. + c.localCertificate = hs.cert.Certificate if _, err := hs.c.writeHandshakeRecord(certMsg, &hs.finishedHash); err != nil { return err } @@ -989,6 +994,12 @@ func (c *Conn) processCertsFromClient(certificate Certificate) error { c.scts = certificate.SignedCertificateTimestamps if len(certs) > 0 { + if fips140tls.Required() && !isCertificateAllowedFIPS(certs[0]) { + c.sendAlert(alertBadCertificate) + err := errors.New("client's certificate is not allowed in FIPS 140-3 mode") + return &CertificateVerificationError{UnverifiedCertificates: certs, Err: err} + } + switch certs[0].PublicKey.(type) { case *ecdsa.PublicKey, *rsa.PublicKey, ed25519.PublicKey: case *mldsa.PublicKey: @@ -1034,6 +1045,7 @@ func clientHelloInfo(ctx context.Context, c *Conn, clientHello *clientHelloMsg) Conn: conn, HelloRetryRequest: c.didHRR, config: c.config, + isQUIC: c.quic != nil, ctx: ctx, } } diff --git a/handshake_server_tls13.go b/handshake_server_tls13.go index 673843f..54ba8d5 100644 --- a/handshake_server_tls13.go +++ b/handshake_server_tls13.go @@ -226,7 +226,7 @@ func (hs *serverHandshakeStateTLS13) processClientHello() error { if id == TLS_FALLBACK_SCSV { // Use c.vers instead of max(supported_versions) because an attacker // could defeat this by adding an arbitrary high version otherwise. - if c.vers < c.config.maxSupportedVersion(roleServer) { + if c.vers < c.config.maxSupportedVersion(roleServer, c.quic != nil) { c.sendAlert(alertInappropriateFallback) return errors.New("tls: client using inappropriate protocol fallback") } @@ -343,7 +343,7 @@ func (hs *serverHandshakeStateTLS13) processClientHello() error { ke, err := keyExchangeForCurveID(selectedGroup) if err != nil { c.sendAlert(alertInternalError) - return errors.New("tls: CurvePreferences includes unsupported curve") + return errors.New("tls: internal error: supportsCurve accepted unimplemented curve") } hs.sharedKey, hs.hello.serverShare, err = ke.serverSharedSecret(c.config.rand(), clientKeyShare.data) if err != nil { @@ -587,6 +587,9 @@ func (hs *serverHandshakeStateTLS13) pickCertificate() error { } return err } + if certificate != nil { + hs.c.localCertificate = certificate.Certificate + } hs.sigAlg, err = selectSignatureScheme(c.vers, certificate, hs.clientHello.supportedSignatureAlgorithms) if err != nil { // getCertificate returned a certificate that is unsupported or @@ -619,7 +622,7 @@ func (hs *serverHandshakeStateTLS13) doHelloRetryRequest(selectedGroup CurveID) // Make sure the client didn't send extra handshake messages alongside // their initial client_hello. If they sent two client_hello messages, // we will consume the second before they respond to the server_hello. - if c.hand.Len() != 0 { + if c.handLen() != 0 { c.sendAlert(alertUnexpectedMessage) return nil, errors.New("tls: handshake buffer not empty before HelloRetryRequest") } diff --git a/key_agreement.go b/key_agreement.go index 220df9a..099cf88 100644 --- a/key_agreement.go +++ b/key_agreement.go @@ -167,7 +167,7 @@ func (ka *ecdheKeyAgreement) generateServerKeyExchange(config *Config, cert *Cer return nil, errors.New("tls: no supported elliptic curves offered") } if _, ok := curveForCurveID(ka.curveID); !ok { - return nil, errors.New("tls: CurvePreferences includes unsupported curve") + return nil, errors.New("tls: internal error: supportsCurve accepted unimplemented curve") } key, err := generateECDHEKey(config.rand(), ka.curveID) diff --git a/key_schedule.go b/key_schedule.go index b07a86f..c2d09ca 100644 --- a/key_schedule.go +++ b/key_schedule.go @@ -75,16 +75,16 @@ type keyExchange interface { } func keyExchangeForCurveID(id CurveID) (keyExchange, error) { - newMLKEMPrivateKey768 := func(b []byte) (crypto.Decapsulator, error) { - return mlkem.NewDecapsulationKey768(b) + mlkemGenerateKey768 := func() (crypto.Decapsulator, error) { + return mlkem.GenerateKey768() } - newMLKEMPrivateKey1024 := func(b []byte) (crypto.Decapsulator, error) { - return mlkem.NewDecapsulationKey1024(b) + mlkemGenerateKey1024 := func() (crypto.Decapsulator, error) { + return mlkem.GenerateKey1024() } - newMLKEMPublicKey768 := func(b []byte) (crypto.Encapsulator, error) { + mlkemNewPublicKey768 := func(b []byte) (crypto.Encapsulator, error) { return mlkem.NewEncapsulationKey768(b) } - newMLKEMPublicKey1024 := func(b []byte) (crypto.Encapsulator, error) { + mlkemNewPublicKey1024 := func(b []byte) (crypto.Encapsulator, error) { return mlkem.NewEncapsulationKey1024(b) } switch id { @@ -99,20 +99,49 @@ func keyExchangeForCurveID(id CurveID) (keyExchange, error) { case X25519MLKEM768: return &hybridKeyExchange{id, ecdhKeyExchange{X25519, ecdh.X25519()}, 32, mlkem.EncapsulationKeySize768, mlkem.CiphertextSize768, - newMLKEMPrivateKey768, newMLKEMPublicKey768}, nil + mlkemGenerateKey768, mlkemNewPublicKey768}, nil case SecP256r1MLKEM768: return &hybridKeyExchange{id, ecdhKeyExchange{CurveP256, ecdh.P256()}, 65, mlkem.EncapsulationKeySize768, mlkem.CiphertextSize768, - newMLKEMPrivateKey768, newMLKEMPublicKey768}, nil + mlkemGenerateKey768, mlkemNewPublicKey768}, nil case SecP384r1MLKEM1024: return &hybridKeyExchange{id, ecdhKeyExchange{CurveP384, ecdh.P384()}, 97, mlkem.EncapsulationKeySize1024, mlkem.CiphertextSize1024, - newMLKEMPrivateKey1024, newMLKEMPublicKey1024}, nil + mlkemGenerateKey1024, mlkemNewPublicKey1024}, nil + case MLKEM1024: + return &mlkem1024KeyExchange{}, nil default: return nil, errors.New("tls: unsupported key exchange") } } +type mlkem1024KeyExchange struct{} + +func (ke *mlkem1024KeyExchange) keyShares(_ io.Reader) (*keySharePrivateKeys, []keyShare, error) { + priv, err := mlkem.GenerateKey1024() + if err != nil { + return nil, nil, err + } + return &keySharePrivateKeys{mlkem: priv}, []keyShare{{MLKEM1024, priv.EncapsulationKey().Bytes()}}, nil +} + +func (ke *mlkem1024KeyExchange) serverSharedSecret(_ io.Reader, clientKeyShare []byte) ([]byte, keyShare, error) { + peerKey, err := mlkem.NewEncapsulationKey1024(clientKeyShare) + if err != nil { + return nil, keyShare{}, err + } + sharedKey, keyShareData := peerKey.Encapsulate() + return sharedKey, keyShare{MLKEM1024, keyShareData}, nil +} + +func (ke *mlkem1024KeyExchange) clientSharedSecret(priv *keySharePrivateKeys, serverKeyShare []byte) ([]byte, error) { + sharedKey, err := priv.mlkem.Decapsulate(serverKeyShare) + if err != nil { + return nil, err + } + return sharedKey, nil +} + type ecdhKeyExchange struct { id CurveID curve ecdh.Curve @@ -162,8 +191,8 @@ type hybridKeyExchange struct { mlkemPublicKeySize int mlkemCiphertextSize int - newMLKEMPrivateKey func([]byte) (crypto.Decapsulator, error) - newMLKEMPublicKey func([]byte) (crypto.Encapsulator, error) + mlkemGenerateKey func() (crypto.Decapsulator, error) + mlkemNewPublicKey func([]byte) (crypto.Encapsulator, error) } func (ke *hybridKeyExchange) keyShares(rand io.Reader) (*keySharePrivateKeys, []keyShare, error) { @@ -178,11 +207,7 @@ func (ke *hybridKeyExchange) keyShares(rand io.Reader) (*keySharePrivateKeys, [] if err != nil { return nil, nil, err } - seed := make([]byte, mlkem.SeedSize) - if _, err := io.ReadFull(rand, seed); err != nil { - return nil, nil, err - } - priv.mlkem, err = ke.newMLKEMPrivateKey(seed) + priv.mlkem, err = ke.mlkemGenerateKey() if err != nil { return nil, nil, err } @@ -221,7 +246,7 @@ func (ke *hybridKeyExchange) serverSharedSecret(rand io.Reader, clientKeyShare [ if err != nil { return nil, keyShare{}, err } - mlkemPeerKey, err := ke.newMLKEMPublicKey(mlkemShareData) + mlkemPeerKey, err := ke.mlkemNewPublicKey(mlkemShareData) if err != nil { return nil, keyShare{}, err } diff --git a/prf.go b/prf.go index f288642..a0e51d2 100644 --- a/prf.go +++ b/prf.go @@ -250,7 +250,7 @@ func noEKMBecauseRenegotiation(label string, context []byte, length int) ([]byte // Master Secret is not negotiated and thus we wish to fail all key-material // export requests. func noEKMBecauseNoEMS(label string, context []byte, length int) ([]byte, error) { - return nil, errors.New("crypto/tls: ExportKeyingMaterial is unavailable when neither TLS 1.3 nor Extended Master Secret are negotiated; override with GODEBUG=tlsunsafeekm=1") + return nil, errors.New("crypto/tls: ExportKeyingMaterial is unavailable when neither TLS 1.3 nor Extended Master Secret are negotiated") } // ekmFromMasterSecret generates exported keying material as defined in RFC 5705. diff --git a/quic.go b/quic.go index 95a1995..4a14eda 100644 --- a/quic.go +++ b/quic.go @@ -185,16 +185,12 @@ type quicState struct { // QUICClient returns a new TLS client side connection using QUICTransport as the // underlying transport. The config cannot be nil. -// -// The config's MinVersion must be at least TLS 1.3. func QUICClient(config *QUICConfig) *QUICConn { return newQUICConn(Client(nil, config.TLSConfig), config) } // QUICServer returns a new TLS server side connection using QUICTransport as the // underlying transport. The config cannot be nil. -// -// The config's MinVersion must be at least TLS 1.3. func QUICServer(config *QUICConfig) *QUICConn { c, _ := Server(context.Background(), nil, config.TLSConfig) return newQUICConn(c, config) @@ -222,9 +218,6 @@ func (q *QUICConn) Start(ctx context.Context) error { return quicError(errors.New("tls: Start called more than once")) } q.conn.quic.started = true - if q.conn.config.MinVersion < VersionTLS13 { - return quicError(errors.New("tls: Config MinVersion must be at least TLS 1.3")) - } go q.conn.HandshakeContext(ctx) if _, ok := <-q.conn.quic.blockedc; !ok { return q.conn.handshakeErr @@ -296,9 +289,9 @@ func (q *QUICConn) HandleData(level QUICEncryptionLevel, data []byte) error { // The handshake goroutine has exited. c.handshakeMutex.Lock() defer c.handshakeMutex.Unlock() - c.hand.Write(c.quic.readbuf) + c.handBuf().Write(c.quic.readbuf) c.quic.readbuf = nil - for q.conn.hand.Len() >= 4 && q.conn.handshakeErr == nil { + for q.conn.handLen() >= 4 && q.conn.handshakeErr == nil { b := q.conn.hand.Bytes() n := int(b[1])<<16 | int(b[2])<<8 | int(b[3]) if n > maxHandshake { @@ -312,6 +305,7 @@ func (q *QUICConn) HandleData(level QUICEncryptionLevel, data []byte) error { q.conn.handshakeErr = err } } + q.conn.releaseHand() if q.conn.handshakeErr != nil { return quicError(q.conn.handshakeErr) } @@ -402,7 +396,7 @@ func quicError(err error) error { } func (c *Conn) quicReadHandshakeBytes(n int) error { - for c.hand.Len() < n { + for c.handLen() < n { if err := c.quicWaitForSignal(); err != nil { return err } @@ -415,7 +409,7 @@ func (c *Conn) quicSetReadSecret(level QUICEncryptionLevel, suite uint16, secret // read keys, since that can cause messages to be parsed that were encrypted // using old keys which are no longer appropriate. // TODO(roland): we should merge this check with the similar one in setReadTrafficSecret. - if c.hand.Len() != 0 { + if c.handLen() != 0 { c.sendAlert(alertUnexpectedMessage) return errors.New("tls: handshake buffer not empty before setting read traffic secret") } @@ -528,7 +522,7 @@ func (c *Conn) quicWaitForSignal() error { // The connection has been canceled. return c.sendAlertLocked(alertCloseNotify) } - c.hand.Write(c.quic.readbuf) + c.handBuf().Write(c.quic.readbuf) c.quic.readbuf = nil return nil } \ No newline at end of file diff --git a/tls.go b/tls.go index 0d82c72..bdb0ed2 100644 --- a/tls.go +++ b/tls.go @@ -583,6 +583,8 @@ func Listen(network, laddr string, config *Config) (net.Listener, error) { type timeoutError struct{} +var _ error = timeoutError{} + func (timeoutError) Error() string { return "tls: DialWithDialer timed out" } func (timeoutError) Timeout() bool { return true } func (timeoutError) Temporary() bool { return true } @@ -710,10 +712,6 @@ func (d *Dialer) DialContext(ctx context.Context, network, addr string) (net.Con // files. The files must contain PEM encoded data. The certificate file may // contain intermediate certificates following the leaf certificate to form a // certificate chain. On successful return, Certificate.Leaf will be populated. -// -// Before Go 1.23 Certificate.Leaf was left nil, and the parsed certificate was -// discarded. This behavior can be re-enabled by setting "x509keypairleaf=0" -// in the GODEBUG environment variable. func LoadX509KeyPair(certFile, keyFile string) (Certificate, error) { certPEMBlock, err := os.ReadFile(certFile) if err != nil { @@ -728,10 +726,6 @@ func LoadX509KeyPair(certFile, keyFile string) (Certificate, error) { // X509KeyPair parses a public/private key pair from a pair of // PEM encoded data. On successful return, Certificate.Leaf will be populated. -// -// Before Go 1.23 Certificate.Leaf was left nil, and the parsed certificate was -// discarded. This behavior can be re-enabled by setting "x509keypairleaf=0" -// in the GODEBUG environment variable. func X509KeyPair(certPEMBlock, keyPEMBlock []byte) (Certificate, error) { fail := func(err error) (Certificate, error) { return Certificate{}, err } @@ -837,20 +831,21 @@ func X509KeyPair(certPEMBlock, keyPEMBlock []byte) (Certificate, error) { // PKCS #1 private keys by default, while OpenSSL 1.0.0 generates PKCS #8 keys. // OpenSSL ecparam generates SEC1 EC private keys for ECDSA. We try all three. func parsePrivateKey(der []byte) (crypto.PrivateKey, error) { - if key, err := x509.ParsePKCS1PrivateKey(der); err == nil { - return key, nil + key, err := x509.ParsePKCS8PrivateKey(der) + pkcs8Err := err // Return the PKCS#8 error if all parsing attempts fail. + if err != nil { + key, err = x509.ParsePKCS1PrivateKey(der) } - if key, err := x509.ParsePKCS8PrivateKey(der); err == nil { - switch key := key.(type) { - case *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey, *mldsa.PrivateKey: - return key, nil - default: - return nil, errors.New("tls: found unknown private key type in PKCS#8 wrapping") - } + if err != nil { + key, err = x509.ParseECPrivateKey(der) + } + if err != nil { + return nil, fmt.Errorf("tls: failed to parse private key: %w", pkcs8Err) } - if key, err := x509.ParseECPrivateKey(der); err == nil { + switch key := key.(type) { + case *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey, *mldsa.PrivateKey: return key, nil + default: + return nil, errors.New("tls: found unknown private key type in PKCS#8 wrapping") } - - return nil, errors.New("tls: failed to parse private key") }