Skip to content

Commit

Permalink
feat: implement encrypted client extensions (alps)
Browse files Browse the repository at this point in the history
  • Loading branch information
blakebyrnes committed Oct 2, 2022
1 parent e8f6488 commit 8d0f1c4
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 15 deletions.
5 changes: 5 additions & 0 deletions common.go
Expand Up @@ -204,6 +204,7 @@ type ConnectionState struct {
CipherSuite uint16 // cipher suite in use (TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, ...)
NegotiatedProtocol string // negotiated next protocol (not guaranteed to be from Config.NextProtos)
NegotiatedProtocolIsMutual bool // negotiated protocol was advertised by server (client side only)
PeerApplicationSettings []byte // application settings provided by peer
ServerName string // server name requested by client, if any (server side only)
PeerCertificates []*x509.Certificate // certificate chain presented by remote peer
VerifiedChains [][]*x509.Certificate // verified chains built from PeerCertificates
Expand Down Expand Up @@ -500,6 +501,10 @@ type Config struct {
// order of preference.
NextProtos []string

// ApplicationSettings is a set of application settings to use which each
// application protocol.
ApplicationSettings map[string][]byte

// ServerName is used to verify the hostname on the returned
// certificates unless InsecureSkipVerify is given. It is also included
// in the client's handshake to support virtual hosting unless it is
Expand Down
11 changes: 10 additions & 1 deletion conn.go
Expand Up @@ -83,6 +83,10 @@ type Conn struct {
clientProtocol string
clientProtocolFallback bool

hasApplicationSettings bool
peerApplicationSettings []byte
localApplicationSettings []byte

// input/output
in, out halfConn
rawInput bytes.Buffer // raw input, starting with a record header
Expand Down Expand Up @@ -1052,7 +1056,11 @@ func (c *Conn) readHandshake() (interface{}, error) {
case typeFinished:
m = new(finishedMsg)
case typeEncryptedExtensions:
m = new(encryptedExtensionsMsg)
if c.isClient {
m = new(encryptedExtensionsMsg)
} else {
m = new(clientEncryptedExtensionsMsg)
}
case typeEndOfEarlyData:
m = new(endOfEarlyDataMsg)
case typeKeyUpdate:
Expand Down Expand Up @@ -1392,6 +1400,7 @@ func (c *Conn) ConnectionState() ConnectionState {
if state.HandshakeComplete {
state.Version = c.vers
state.NegotiatedProtocol = c.clientProtocol
state.PeerApplicationSettings = c.peerApplicationSettings
state.DidResume = c.didResume
state.NegotiatedProtocolIsMutual = !c.clientProtocolFallback
state.CipherSuite = c.cipherSuite
Expand Down
29 changes: 29 additions & 0 deletions handshake_client_tls13.go
Expand Up @@ -95,6 +95,9 @@ func (hs *clientHandshakeStateTLS13) handshake() error {
if err := hs.readServerFinished(); err != nil {
return err
}
if err := hs.sendClientEncryptedExtensions(); err != nil {
return err
}
if err := hs.sendClientCertificate(); err != nil {
return err
}
Expand Down Expand Up @@ -459,7 +462,18 @@ func (hs *clientHandshakeStateTLS13) readServerParameters() error {
return errors.New("tls: server advertised unrequested ALPN extension")
}
c.clientProtocol = encryptedExtensions.alpnProtocol
c.hasApplicationSettings = encryptedExtensions.hasApplicationSettings
c.peerApplicationSettings = encryptedExtensions.applicationSettings

if c.hasApplicationSettings {
if c.vers < VersionTLS13 {
return errors.New("tls: server sent application settings at invalid version")
}
if len(c.clientProtocol) == 0 {
return errors.New("tls: server sent application settings without ALPN")
}
c.localApplicationSettings = c.config.ApplicationSettings[hs.serverHello.alpnProtocol]
}
return nil
}

Expand Down Expand Up @@ -721,6 +735,21 @@ func (hs *clientHandshakeStateTLS13) sendClientFinished() error {
return nil
}

func (hs *clientHandshakeStateTLS13) sendClientEncryptedExtensions() error {
c := hs.c
clientEncryptedExtensions := new(clientEncryptedExtensionsMsg)
if c.hasApplicationSettings {
clientEncryptedExtensions.hasApplicationSettings = true
clientEncryptedExtensions.applicationSettings = c.localApplicationSettings
hs.transcript.Write(clientEncryptedExtensions.marshal())
if _, err := c.writeRecord(recordTypeHandshake, clientEncryptedExtensions.marshal()); err != nil {
return err
}
}

return nil
}

func (c *Conn) handleNewSessionTicket(msg *newSessionTicketMsgTLS13) error {
if !c.isClient {
c.sendAlert(alertUnexpectedMessage)
Expand Down
15 changes: 12 additions & 3 deletions handshake_messages.go
Expand Up @@ -6,8 +6,9 @@ package tls

import (
"fmt"
"golang.org/x/crypto/cryptobyte"
"strings"

"golang.org/x/crypto/cryptobyte"
)

// The marshalingFunction type is an adapter to allow the use of ordinary
Expand Down Expand Up @@ -863,8 +864,11 @@ func (m *serverHelloMsg) unmarshal(data []byte) bool {
}

type encryptedExtensionsMsg struct {
raw []byte
alpnProtocol string
raw []byte
alpnProtocol string
hasApplicationSettings bool
applicationSettings []byte
customExtension []byte
}

func (m *encryptedExtensionsMsg) marshal() []byte {
Expand Down Expand Up @@ -922,7 +926,12 @@ func (m *encryptedExtensionsMsg) unmarshal(data []byte) bool {
proto.Empty() || !protoList.Empty() {
return false
}

m.alpnProtocol = string(proto)
case extensionApplicationSettings:
m.hasApplicationSettings = true
m.applicationSettings = []byte(extData)
continue
default:
// Ignore unknown extensions.
continue
Expand Down
7 changes: 4 additions & 3 deletions u_common.go
Expand Up @@ -20,9 +20,10 @@ const (
utlsExtensionExtendedMasterSecret uint16 = 23 // https://tools.ietf.org/html/rfc7627

// extensions with 'fake' prefix break connection, if server echoes them back
fakeExtensionChannelIDOld uint16 = 30031 // not IANA assigned
fakeExtensionChannelID uint16 = 30032 // not IANA assigned
fakeExtensionALPS uint16 = 17513 // not IANA assigned
fakeExtensionChannelIDOld uint16 = 30031 // not IANA assigned
fakeExtensionChannelID uint16 = 30032 // not IANA assigned
extensionApplicationSettings uint16 = 17513 // not IANA assigned
extensionCustom uint16 = 1234 // not IANA assigned

fakeRecordSizeLimit uint16 = 0x001c
)
Expand Down
4 changes: 2 additions & 2 deletions u_parrots.go
Expand Up @@ -354,7 +354,7 @@ func UtlsIdToSpec(id ClientHelloID) (ClientHelloSpec, error) {
&CompressCertificateExtension{
Algorithms: []CertCompressionAlgo{CertCompressionBrotli},
},
&FakeALPSExtension{SupportedProtocols: []string{"h2"}},
&ALPSExtension{SupportedProtocols: []string{"h2"}},
&UtlsGREASEExtension{},
&UtlsPaddingExtension{GetPaddingLen: BoringPaddingStyle},
},
Expand Down Expand Up @@ -425,7 +425,7 @@ func UtlsIdToSpec(id ClientHelloID) (ClientHelloSpec, error) {
&CompressCertificateExtension{
Algorithms: []CertCompressionAlgo{CertCompressionBrotli},
},
&FakeALPSExtension{SupportedProtocols: []string{"h2"}},
&ALPSExtension{SupportedProtocols: []string{"h2"}},
&UtlsGREASEExtension{},
&UtlsPaddingExtension{GetPaddingLen: BoringPaddingStyle},
},
Expand Down
79 changes: 73 additions & 6 deletions u_tls_extensions.go
Expand Up @@ -7,6 +7,8 @@ package tls
import (
"errors"
"io"

"golang.org/x/crypto/cryptobyte"
)

type TLSExtension interface {
Expand Down Expand Up @@ -737,29 +739,29 @@ func (e *FakeRecordSizeLimitExtension) Read(b []byte) (int, error) {
return e.Len(), io.EOF
}

type FakeALPSExtension struct {
type ALPSExtension struct {
SupportedProtocols []string
}

func (e *FakeALPSExtension) writeToUConn(uc *UConn) error {
func (e *ALPSExtension) writeToUConn(uc *UConn) error {
return nil
}

func (e *FakeALPSExtension) Len() int {
func (e *ALPSExtension) Len() int {
bLen := 2 + 2 + 2
for _, s := range e.SupportedProtocols {
bLen += 1 + len(s)
}
return bLen
}

func (e *FakeALPSExtension) Read(b []byte) (int, error) {
func (e *ALPSExtension) Read(b []byte) (int, error) {
if len(b) < e.Len() {
return 0, io.ErrShortBuffer
}

b[0] = byte(fakeExtensionALPS >> 8)
b[1] = byte(fakeExtensionALPS & 0xff)
b[0] = byte(extensionApplicationSettings >> 8)
b[1] = byte(extensionApplicationSettings & 0xff)
lengths := b[2:]
b = b[6:]

Expand All @@ -780,3 +782,68 @@ func (e *FakeALPSExtension) Read(b []byte) (int, error) {

return e.Len(), io.EOF
}

type clientEncryptedExtensionsMsg struct {
raw []byte
applicationSettings []byte
hasApplicationSettings bool
customExtension []byte
}

func (m *clientEncryptedExtensionsMsg) marshal() (x []byte) {
if m.raw != nil {
return m.raw
}

var builder cryptobyte.Builder
builder.AddUint8(typeEncryptedExtensions)
builder.AddUint24LengthPrefixed(func(body *cryptobyte.Builder) {
body.AddUint16LengthPrefixed(func(extensions *cryptobyte.Builder) {
if m.hasApplicationSettings {
extensions.AddUint16(extensionApplicationSettings)
extensions.AddUint16LengthPrefixed(func(msg *cryptobyte.Builder) {
msg.AddBytes(m.applicationSettings)
})
}
if len(m.customExtension) > 0 {
extensions.AddUint16(extensionCustom)
extensions.AddUint16LengthPrefixed(func(msg *cryptobyte.Builder) {
msg.AddBytes(m.customExtension)
})
}
})
})

m.raw = builder.BytesOrPanic()
return m.raw
}

func (m *clientEncryptedExtensionsMsg) unmarshal(data []byte) bool {
*m = clientEncryptedExtensionsMsg{raw: data}
s := cryptobyte.String(data)

var extensions cryptobyte.String
if !s.Skip(4) || // message type and uint24 length field
!s.ReadUint16LengthPrefixed(&extensions) || !s.Empty() {
return false
}

for !extensions.Empty() {
var extension uint16
var extData cryptobyte.String
if !extensions.ReadUint16(&extension) ||
!extensions.ReadUint16LengthPrefixed(&extData) {
return false
}

switch extension {
case extensionApplicationSettings:
m.hasApplicationSettings = true
m.applicationSettings = []byte(extData)
default:
// Unknown extensions are illegal in EncryptedExtensions.
return false
}
}
return true
}

0 comments on commit 8d0f1c4

Please sign in to comment.