Skip to content

Commit

Permalink
Implement certificate compression (refraction-networking#95)
Browse files Browse the repository at this point in the history
Certificate compression is defined in RFC 8879:
https://datatracker.ietf.org/doc/html/rfc8879

This implementation is client-side only, for server certificates.

- Fixes refraction-networking#104.
  • Loading branch information
hwh33 authored Jul 20, 2022
1 parent 423efbb commit 3b426f4
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 49 deletions.
20 changes: 13 additions & 7 deletions u_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,16 @@ const (
utlsExtensionPadding uint16 = 21
utlsExtensionExtendedMasterSecret uint16 = 23 // https://tools.ietf.org/html/rfc7627

// https://datatracker.ietf.org/doc/html/rfc8879#section-7.1
utlsExtensionCompressCertificate uint16 = 27

// extensions with 'fake' prefix break connection, if server echoes them back
fakeExtensionChannelID uint16 = 30032 // not IANA assigned

fakeCertCompressionAlgs uint16 = 0x001b
fakeRecordSizeLimit uint16 = 0x001c
fakeRecordSizeLimit uint16 = 0x001c

// https://datatracker.ietf.org/doc/html/rfc8879#section-7.2
typeCompressedCertificate uint8 = 25
)

const (
Expand All @@ -37,11 +42,11 @@ const (
FAKE_OLD_TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = uint16(0xcc15) // we can try to craft these ciphersuites
FAKE_TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 = uint16(0x009e) // from existing pieces, if needed

FAKE_TLS_DHE_RSA_WITH_AES_128_CBC_SHA = uint16(0x0033)
FAKE_TLS_DHE_RSA_WITH_AES_256_CBC_SHA = uint16(0x0039)
FAKE_TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 = uint16(0x009f)
FAKE_TLS_RSA_WITH_RC4_128_MD5 = uint16(0x0004)
FAKE_TLS_EMPTY_RENEGOTIATION_INFO_SCSV = uint16(0x00ff)
FAKE_TLS_DHE_RSA_WITH_AES_128_CBC_SHA = uint16(0x0033)
FAKE_TLS_DHE_RSA_WITH_AES_256_CBC_SHA = uint16(0x0039)
FAKE_TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 = uint16(0x009f)
FAKE_TLS_RSA_WITH_RC4_128_MD5 = uint16(0x0004)
FAKE_TLS_EMPTY_RENEGOTIATION_INFO_SCSV = uint16(0x00ff)
)

// newest signatures
Expand All @@ -65,6 +70,7 @@ type CertCompressionAlgo uint16
const (
CertCompressionZlib CertCompressionAlgo = 0x0001
CertCompressionBrotli CertCompressionAlgo = 0x0002
CertCompressionZstd CertCompressionAlgo = 0x0003
)

const (
Expand Down
5 changes: 5 additions & 0 deletions u_conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ type UConn struct {
greaseSeed [ssl_grease_last_index]uint16

omitSNIExtension bool

// certCompressionAlgs represents the set of advertised certificate compression
// algorithms, as specified in the ClientHello. This is only relevant client-side, for the
// server certificate. All other forms of certificate compression are unsupported.
certCompressionAlgs []CertCompressionAlgo
}

// UClient returns a new uTLS client, with behavior depending on clientHelloID.
Expand Down
17 changes: 16 additions & 1 deletion u_fingerprinter.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,22 @@ func (f *Fingerprinter) FingerprintClientHello(data []byte) (*ClientHelloSpec, e
case utlsExtensionPadding:
clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &UtlsPaddingExtension{GetPaddingLen: BoringPaddingStyle})

case fakeExtensionChannelID, fakeCertCompressionAlgs, fakeRecordSizeLimit:
case utlsExtensionCompressCertificate:
methods := []CertCompressionAlgo{}
methodsRaw := new(cryptobyte.String)
if !extData.ReadUint8LengthPrefixed(methodsRaw) {
return nil, errors.New("unable to read cert compression algorithms extension data")
}
for !methodsRaw.Empty() {
var method uint16
if !methodsRaw.ReadUint16(&method) {
return nil, errors.New("unable to read cert compression algorithms extension data")
}
methods = append(methods, CertCompressionAlgo(method))
}
clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &UtlsCompressCertExtension{methods})

case fakeExtensionChannelID, fakeRecordSizeLimit:
clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &GenericExtension{extension, extData})

case extensionPreSharedKey:
Expand Down
49 changes: 49 additions & 0 deletions u_handshake_messages.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package tls

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

// Only implemented client-side, for server certificates.
// Alternate certificate message formats (https://datatracker.ietf.org/doc/html/rfc7250) are not
// supported.
// https://datatracker.ietf.org/doc/html/rfc8879
type compressedCertificateMsg struct {
raw []byte

algorithm uint16
uncompressedLength uint32 // uint24
compressedCertificateMessage []byte
}

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

var b cryptobyte.Builder
b.AddUint8(typeCompressedCertificate)
b.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) {
b.AddUint16(m.algorithm)
b.AddUint24(m.uncompressedLength)
b.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) {
b.AddBytes(m.compressedCertificateMessage)
})
})

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

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

if !s.Skip(4) || // message type and uint24 length field
!s.ReadUint16(&m.algorithm) ||
!s.ReadUint24(&m.uncompressedLength) ||
!readUint24LengthPrefixed(&s, &m.compressedCertificateMessage) {
return false
}
return true
}
6 changes: 3 additions & 3 deletions u_parrots.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ func utlsIdToSpec(id ClientHelloID) (ClientHelloSpec, error) {
CurveP256,
CurveP384,
}},
&FakeCertCompressionAlgsExtension{[]CertCompressionAlgo{CertCompressionBrotli}},
&UtlsCompressCertExtension{[]CertCompressionAlgo{CertCompressionBrotli}},
&UtlsGREASEExtension{},
&UtlsPaddingExtension{GetPaddingLen: BoringPaddingStyle},
},
Expand Down Expand Up @@ -205,7 +205,7 @@ func utlsIdToSpec(id ClientHelloID) (ClientHelloSpec, error) {
VersionTLS11,
VersionTLS10,
}},
&FakeCertCompressionAlgsExtension{[]CertCompressionAlgo{
&UtlsCompressCertExtension{[]CertCompressionAlgo{
CertCompressionBrotli,
}},
&UtlsGREASEExtension{},
Expand Down Expand Up @@ -277,7 +277,7 @@ func utlsIdToSpec(id ClientHelloID) (ClientHelloSpec, error) {
VersionTLS11,
VersionTLS10,
}},
&FakeCertCompressionAlgsExtension{[]CertCompressionAlgo{
&UtlsCompressCertExtension{[]CertCompressionAlgo{
CertCompressionBrotli,
}},
&UtlsGREASEExtension{},
Expand Down
83 changes: 45 additions & 38 deletions u_tls_extensions.go
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,51 @@ func (e *UtlsPaddingExtension) Read(b []byte) (int, error) {
return e.Len(), io.EOF
}

// UtlsCompressCertExtension is only implemented client-side, for server certificates. Alternate
// certificate message formats (https://datatracker.ietf.org/doc/html/rfc7250) are not supported.
//
// See https://datatracker.ietf.org/doc/html/rfc8879#section-3
type UtlsCompressCertExtension struct {
Algorithms []CertCompressionAlgo
}

func (e *UtlsCompressCertExtension) writeToUConn(uc *UConn) error {
uc.certCompressionAlgs = e.Algorithms
return nil
}

func (e *UtlsCompressCertExtension) Len() int {
return 4 + 1 + (2 * len(e.Algorithms))
}

func (e *UtlsCompressCertExtension) Read(b []byte) (int, error) {
if len(b) < e.Len() {
return 0, io.ErrShortBuffer
}
b[0] = byte(utlsExtensionCompressCertificate >> 8)
b[1] = byte(utlsExtensionCompressCertificate & 0xff)

extLen := 2 * len(e.Algorithms)
if extLen > 255 {
return 0, errors.New("too many certificate compression methods")
}

// Extension data length.
b[2] = byte((extLen + 1) >> 8)
b[3] = byte((extLen + 1) & 0xff)

// Methods length.
b[4] = byte(extLen)

i := 5
for _, compMethod := range e.Algorithms {
b[i] = byte(compMethod >> 8)
b[i+1] = byte(compMethod)
i += 2
}
return e.Len(), io.EOF
}

// https://github.com/google/boringssl/blob/7d7554b6b3c79e707e25521e61e066ce2b996e4c/ssl/t1_lib.c#L2803
func BoringPaddingStyle(unpaddedLen int) (int, bool) {
if unpaddedLen > 0xff && unpaddedLen < 0x200 {
Expand Down Expand Up @@ -703,44 +748,6 @@ func (e *FakeChannelIDExtension) Read(b []byte) (int, error) {
return e.Len(), io.EOF
}

type FakeCertCompressionAlgsExtension struct {
Methods []CertCompressionAlgo
}

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

func (e *FakeCertCompressionAlgsExtension) Len() int {
return 4 + 1 + (2 * len(e.Methods))
}

func (e *FakeCertCompressionAlgsExtension) Read(b []byte) (int, error) {
if len(b) < e.Len() {
return 0, io.ErrShortBuffer
}
// https://tools.ietf.org/html/draft-balfanz-tls-channelid-00
b[0] = byte(fakeCertCompressionAlgs >> 8)
b[1] = byte(fakeCertCompressionAlgs & 0xff)

extLen := 2 * len(e.Methods)
if extLen > 255 {
return 0, errors.New("too many certificate compression methods")
}

b[2] = byte((extLen + 1) >> 8)
b[3] = byte((extLen + 1) & 0xff)
b[4] = byte(extLen)

i := 5
for _, compMethod := range e.Methods {
b[i] = byte(compMethod >> 8)
b[i+1] = byte(compMethod)
i += 2
}
return e.Len(), io.EOF
}

type FakeRecordSizeLimitExtension struct {
Limit uint16
}
Expand Down

0 comments on commit 3b426f4

Please sign in to comment.