From 4092d3f4797e54b4f7b32ea0dac4191ca81cc033 Mon Sep 17 00:00:00 2001 From: Jonathan Hoyland Date: Mon, 7 Aug 2023 16:52:50 +0100 Subject: [PATCH] Implement TLS Flags and the request mTLS flag. --- src/crypto/tls/common.go | 19 +++++++++ src/crypto/tls/conn.go | 7 ++++ src/crypto/tls/handshake_client.go | 22 ++++++++++ src/crypto/tls/handshake_messages.go | 21 ++++++++++ src/crypto/tls/handshake_server_tls13.go | 51 +++++++++++++++++++++++- 5 files changed, 119 insertions(+), 1 deletion(-) diff --git a/src/crypto/tls/common.go b/src/crypto/tls/common.go index 60c3b2d0883..7399f29b65b 100644 --- a/src/crypto/tls/common.go +++ b/src/crypto/tls/common.go @@ -126,6 +126,7 @@ const ( extensionRenegotiationInfo uint16 = 0xff01 extensionECH uint16 = 0xfe0d // draft-ietf-tls-esni-13 extensionECHOuterExtensions uint16 = 0xfd00 // draft-ietf-tls-esni-13 + extensionTLSFlags uint16 = 0xfe01 // draft-ietf-tls-tlsflags-12 ) // TLS signaling cipher suite values @@ -364,6 +365,15 @@ type ConnectionState struct { // This means the client has offered ECH or sent GREASE ECH. ECHOffered bool + // PeerTLSFlags is the set of TLS Flags sent by the Peer. + PeerTLSFlags []TLSFlag + + // AgreedTLSFlags is the set of TLS Flags mutually supported by the Client and Server + AgreedTLSFlags []TLSFlag + + // RequestClientCert is true if the server decided to request a client certificate + RequestClientCert bool + // ekm is a closure exposed via ExportKeyingMaterial. ekm func(label string, context []byte, length int) ([]byte, error) } @@ -469,6 +479,12 @@ const ( ECDSAWithSHA1 SignatureScheme = 0x0203 ) +type TLSFlag uint16 + +const ( + FlagSupportMTLS TLSFlag = 0x50 +) + // ClientHelloInfo contains information from a ClientHello message in order to // guide application logic in the GetCertificate and GetConfigForClient callbacks. type ClientHelloInfo struct { @@ -905,6 +921,8 @@ type Config struct { // See https://tools.ietf.org/html/draft-ietf-tls-subcerts. SupportDelegatedCredential bool + TLSFlagsSupported []TLSFlag + // mutex protects sessionTicketKeys and autoSessionTicketKeys. mutex sync.RWMutex // sessionTicketKeys contains zero or more ticket keys. If set, it means @@ -995,6 +1013,7 @@ func (c *Config) Clone() *Config { Renegotiation: c.Renegotiation, KeyLogWriter: c.KeyLogWriter, SupportDelegatedCredential: c.SupportDelegatedCredential, + TLSFlagsSupported: c.TLSFlagsSupported, ECHEnabled: c.ECHEnabled, ClientECHConfigs: c.ClientECHConfigs, ServerECHProvider: c.ServerECHProvider, diff --git a/src/crypto/tls/conn.go b/src/crypto/tls/conn.go index 5b04b0e2e2e..9349c3789bc 100644 --- a/src/crypto/tls/conn.go +++ b/src/crypto/tls/conn.go @@ -143,6 +143,10 @@ type Conn struct { configId uint8 // The ECH config id maxNameLen int // maximum_name_len indicated by the ECH config } + + agreedTLSFlags []TLSFlag + peerTLSFlags []TLSFlag + requestClientCert bool } // Access to net.Conn methods. @@ -1696,6 +1700,9 @@ func (c *Conn) connectionStateLocked() ConnectionState { } else { state.ekm = c.ekm } + state.PeerTLSFlags = c.peerTLSFlags + state.AgreedTLSFlags = c.agreedTLSFlags + state.RequestClientCert = c.requestClientCert return state } diff --git a/src/crypto/tls/handshake_client.go b/src/crypto/tls/handshake_client.go index 6755ce04b4a..ea07b6d9197 100644 --- a/src/crypto/tls/handshake_client.go +++ b/src/crypto/tls/handshake_client.go @@ -218,6 +218,11 @@ func (c *Conn) makeClientHello(minVersion uint16) (*clientHelloMsg, clientKeySha hello.delegatedCredentialSupported = config.SupportDelegatedCredential hello.supportedSignatureAlgorithmsDC = supportedSignatureAlgorithmsDC + flagBytes, err := encodeFlags(config.TLSFlagsSupported) + if err != nil { + return nil, nil, err + } + hello.tlsFlags = flagBytes } if c.quic != nil { @@ -234,6 +239,23 @@ func (c *Conn) makeClientHello(minVersion uint16) (*clientHelloMsg, clientKeySha return hello, secret, nil } +const maxTLSFlag = TLSFlag(2040) + +func encodeFlags(flags []TLSFlag) ([]byte, error) { + flagBytes := make([]byte, 0, 255) + for _, flag := range flags { + if flag >= maxTLSFlag { + return nil, fmt.Errorf("cannot encode TLS flags greater than %d", maxTLSFlag) + } + whichByte := int(flag) >> 3 + if whichByte >= len(flagBytes) { + flagBytes = flagBytes[:whichByte+1] + } + flagBytes[whichByte] |= 1 << (flag % 8) + } + return flagBytes, nil +} + func (c *Conn) clientHandshake(ctx context.Context) (err error) { if c.config == nil { c.config = defaultConfig() diff --git a/src/crypto/tls/handshake_messages.go b/src/crypto/tls/handshake_messages.go index b3840f6c10f..f6d0b81a9c5 100644 --- a/src/crypto/tls/handshake_messages.go +++ b/src/crypto/tls/handshake_messages.go @@ -90,6 +90,7 @@ type clientHelloMsg struct { alpnProtocols []string scts bool supportedVersions []uint16 + tlsFlags []byte cookie []byte keyShares []keyShare earlyData bool @@ -239,6 +240,14 @@ func (m *clientHelloMsg) marshal() ([]byte, error) { }) }) } + if len(m.tlsFlags) > 0 { + exts.AddUint16(extensionTLSFlags) + exts.AddUint16LengthPrefixed(func(exts *cryptobyte.Builder) { + exts.AddUint8LengthPrefixed(func(exts *cryptobyte.Builder) { + exts.AddBytes(m.tlsFlags) + }) + }) + } if len(m.cookie) > 0 { // RFC 8446, Section 4.2.2 exts.AddUint16(extensionCookie) @@ -577,6 +586,18 @@ func (m *clientHelloMsg) unmarshal(data []byte) bool { } m.supportedVersions = append(m.supportedVersions, vers) } + case extensionTLSFlags: + var flagsList cryptobyte.String + if !extData.ReadUint8LengthPrefixed(&flagsList) || flagsList.Empty() { + return false + } + for !flagsList.Empty() { + var flagByte uint8 + if !flagsList.ReadUint8(&flagByte) { + return false + } + m.tlsFlags = append(m.tlsFlags, flagByte) + } case extensionCookie: // RFC 8446, Section 4.2.2 if !readUint16LengthPrefixed(&extData, &m.cookie) || diff --git a/src/crypto/tls/handshake_server_tls13.go b/src/crypto/tls/handshake_server_tls13.go index 298d271d5c6..a286a0aa669 100644 --- a/src/crypto/tls/handshake_server_tls13.go +++ b/src/crypto/tls/handshake_server_tls13.go @@ -43,6 +43,8 @@ type serverHandshakeStateTLS13 struct { transcript hash.Hash clientFinished []byte certReq *certificateRequestMsgTLS13 + peerTLSFlags []TLSFlag + tlsFlags []TLSFlag hsTimings CFEventTLS13ServerHandshakeTimingInfo } @@ -132,7 +134,9 @@ func (hs *serverHandshakeStateTLS13) handshake() error { c.handleCFEvent(hs.hsTimings) c.isHandshakeComplete.Store(true) - + c.agreedTLSFlags = hs.tlsFlags + c.peerTLSFlags = hs.peerTLSFlags + c.requestClientCert = hs.requestClientCert() return nil } @@ -317,6 +321,29 @@ GroupSelection: c.sendAlert(alertIllegalParameter) return errors.New("tls: invalid client key share") } + if len(hs.clientHello.tlsFlags) != 0 { + supportedFlags, err := encodeFlags(hs.c.config.TLSFlagsSupported) + if err != nil { + return errors.New("tls: invalid server flags") + } + var mutuallySupportedFlags []byte + for i, sFB := range supportedFlags { + if i >= len(hs.clientHello.tlsFlags) { + break + } + mutuallySupportedFlags = append(mutuallySupportedFlags, hs.clientHello.tlsFlags[i]&sFB) + } + + peerTLSFlags, err := decodeFlags(hs.clientHello.tlsFlags) + if err == nil { + hs.peerTLSFlags = peerTLSFlags + } + + tlsFlags, err := decodeFlags(mutuallySupportedFlags) + if err == nil { + hs.tlsFlags = tlsFlags + } + } selectedProto, err := negotiateALPN(c.config.NextProtos, hs.clientHello.alpnProtocols, c.quic != nil) if err != nil { @@ -356,6 +383,23 @@ GroupSelection: return nil } +func decodeFlags(flagBytes []byte) ([]TLSFlag, error) { + var flags []TLSFlag + for byteIndex, b := range flagBytes { + for i := 0; !(b == 0); i++ { + if (b & 1) == 1 { + flagNo := byteIndex*8 + i + if flagNo >= int(maxTLSFlag) { + return nil, fmt.Errorf("TLS flag is out of range: %d", flagNo) + } + flags = append(flags, TLSFlag(flagNo)) + } + b >>= 1 + } + } + return flags, nil +} + func (hs *serverHandshakeStateTLS13) checkForResumption() error { c := hs.c @@ -892,6 +936,11 @@ func (hs *serverHandshakeStateTLS13) sendServerParameters() error { } func (hs *serverHandshakeStateTLS13) requestClientCert() bool { + for _, flag := range hs.tlsFlags { + if flag == FlagSupportMTLS { + return true + } + } return hs.c.config.ClientAuth >= RequestClientCert && !hs.usingPSK }