Skip to content

Commit 458ba85

Browse files
hwh33adotkhan
authored andcommitted
Implement certificate compression (refraction-networking#95)
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.
1 parent 2fc88e9 commit 458ba85

File tree

6 files changed

+131
-49
lines changed

6 files changed

+131
-49
lines changed

u_common.go

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,16 @@ const (
1919
utlsExtensionPadding uint16 = 21
2020
utlsExtensionExtendedMasterSecret uint16 = 23 // https://tools.ietf.org/html/rfc7627
2121

22+
// https://datatracker.ietf.org/doc/html/rfc8879#section-7.1
23+
utlsExtensionCompressCertificate uint16 = 27
24+
2225
// extensions with 'fake' prefix break connection, if server echoes them back
2326
fakeExtensionChannelID uint16 = 30032 // not IANA assigned
2427

25-
fakeCertCompressionAlgs uint16 = 0x001b
26-
fakeRecordSizeLimit uint16 = 0x001c
28+
fakeRecordSizeLimit uint16 = 0x001c
29+
30+
// https://datatracker.ietf.org/doc/html/rfc8879#section-7.2
31+
typeCompressedCertificate uint8 = 25
2732
)
2833

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

40-
FAKE_TLS_DHE_RSA_WITH_AES_128_CBC_SHA = uint16(0x0033)
41-
FAKE_TLS_DHE_RSA_WITH_AES_256_CBC_SHA = uint16(0x0039)
42-
FAKE_TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 = uint16(0x009f)
43-
FAKE_TLS_RSA_WITH_RC4_128_MD5 = uint16(0x0004)
44-
FAKE_TLS_EMPTY_RENEGOTIATION_INFO_SCSV = uint16(0x00ff)
45+
FAKE_TLS_DHE_RSA_WITH_AES_128_CBC_SHA = uint16(0x0033)
46+
FAKE_TLS_DHE_RSA_WITH_AES_256_CBC_SHA = uint16(0x0039)
47+
FAKE_TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 = uint16(0x009f)
48+
FAKE_TLS_RSA_WITH_RC4_128_MD5 = uint16(0x0004)
49+
FAKE_TLS_EMPTY_RENEGOTIATION_INFO_SCSV = uint16(0x00ff)
4550
)
4651

4752
// newest signatures
@@ -65,6 +70,7 @@ type CertCompressionAlgo uint16
6570
const (
6671
CertCompressionZlib CertCompressionAlgo = 0x0001
6772
CertCompressionBrotli CertCompressionAlgo = 0x0002
73+
CertCompressionZstd CertCompressionAlgo = 0x0003
6874
)
6975

7076
const (

u_conn.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ type UConn struct {
3232
greaseSeed [ssl_grease_last_index]uint16
3333

3434
omitSNIExtension bool
35+
36+
// certCompressionAlgs represents the set of advertised certificate compression
37+
// algorithms, as specified in the ClientHello. This is only relevant client-side, for the
38+
// server certificate. All other forms of certificate compression are unsupported.
39+
certCompressionAlgs []CertCompressionAlgo
3540
}
3641

3742
// UClient returns a new uTLS client, with behavior depending on clientHelloID.

u_fingerprinter.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,22 @@ func (f *Fingerprinter) FingerprintClientHello(data []byte) (*ClientHelloSpec, e
303303
case utlsExtensionPadding:
304304
clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &UtlsPaddingExtension{GetPaddingLen: BoringPaddingStyle})
305305

306-
case fakeExtensionChannelID, fakeCertCompressionAlgs, fakeRecordSizeLimit:
306+
case utlsExtensionCompressCertificate:
307+
methods := []CertCompressionAlgo{}
308+
methodsRaw := new(cryptobyte.String)
309+
if !extData.ReadUint8LengthPrefixed(methodsRaw) {
310+
return nil, errors.New("unable to read cert compression algorithms extension data")
311+
}
312+
for !methodsRaw.Empty() {
313+
var method uint16
314+
if !methodsRaw.ReadUint16(&method) {
315+
return nil, errors.New("unable to read cert compression algorithms extension data")
316+
}
317+
methods = append(methods, CertCompressionAlgo(method))
318+
}
319+
clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &UtlsCompressCertExtension{methods})
320+
321+
case fakeExtensionChannelID, fakeRecordSizeLimit:
307322
clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &GenericExtension{extension, extData})
308323

309324
case extensionPreSharedKey:

u_handshake_messages.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package tls
2+
3+
import (
4+
"golang.org/x/crypto/cryptobyte"
5+
)
6+
7+
// Only implemented client-side, for server certificates.
8+
// Alternate certificate message formats (https://datatracker.ietf.org/doc/html/rfc7250) are not
9+
// supported.
10+
// https://datatracker.ietf.org/doc/html/rfc8879
11+
type compressedCertificateMsg struct {
12+
raw []byte
13+
14+
algorithm uint16
15+
uncompressedLength uint32 // uint24
16+
compressedCertificateMessage []byte
17+
}
18+
19+
func (m *compressedCertificateMsg) marshal() []byte {
20+
if m.raw != nil {
21+
return m.raw
22+
}
23+
24+
var b cryptobyte.Builder
25+
b.AddUint8(typeCompressedCertificate)
26+
b.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) {
27+
b.AddUint16(m.algorithm)
28+
b.AddUint24(m.uncompressedLength)
29+
b.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) {
30+
b.AddBytes(m.compressedCertificateMessage)
31+
})
32+
})
33+
34+
m.raw = b.BytesOrPanic()
35+
return m.raw
36+
}
37+
38+
func (m *compressedCertificateMsg) unmarshal(data []byte) bool {
39+
*m = compressedCertificateMsg{raw: data}
40+
s := cryptobyte.String(data)
41+
42+
if !s.Skip(4) || // message type and uint24 length field
43+
!s.ReadUint16(&m.algorithm) ||
44+
!s.ReadUint24(&m.uncompressedLength) ||
45+
!readUint24LengthPrefixed(&s, &m.compressedCertificateMessage) {
46+
return false
47+
}
48+
return true
49+
}

u_parrots.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ func utlsIdToSpec(id ClientHelloID) (ClientHelloSpec, error) {
133133
CurveP256,
134134
CurveP384,
135135
}},
136-
&FakeCertCompressionAlgsExtension{[]CertCompressionAlgo{CertCompressionBrotli}},
136+
&UtlsCompressCertExtension{[]CertCompressionAlgo{CertCompressionBrotli}},
137137
&UtlsGREASEExtension{},
138138
&UtlsPaddingExtension{GetPaddingLen: BoringPaddingStyle},
139139
},
@@ -205,7 +205,7 @@ func utlsIdToSpec(id ClientHelloID) (ClientHelloSpec, error) {
205205
VersionTLS11,
206206
VersionTLS10,
207207
}},
208-
&FakeCertCompressionAlgsExtension{[]CertCompressionAlgo{
208+
&UtlsCompressCertExtension{[]CertCompressionAlgo{
209209
CertCompressionBrotli,
210210
}},
211211
&UtlsGREASEExtension{},
@@ -277,7 +277,7 @@ func utlsIdToSpec(id ClientHelloID) (ClientHelloSpec, error) {
277277
VersionTLS11,
278278
VersionTLS10,
279279
}},
280-
&FakeCertCompressionAlgsExtension{[]CertCompressionAlgo{
280+
&UtlsCompressCertExtension{[]CertCompressionAlgo{
281281
CertCompressionBrotli,
282282
}},
283283
&UtlsGREASEExtension{},

u_tls_extensions.go

Lines changed: 45 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,51 @@ func (e *UtlsPaddingExtension) Read(b []byte) (int, error) {
513513
return e.Len(), io.EOF
514514
}
515515

516+
// UtlsCompressCertExtension is only implemented client-side, for server certificates. Alternate
517+
// certificate message formats (https://datatracker.ietf.org/doc/html/rfc7250) are not supported.
518+
//
519+
// See https://datatracker.ietf.org/doc/html/rfc8879#section-3
520+
type UtlsCompressCertExtension struct {
521+
Algorithms []CertCompressionAlgo
522+
}
523+
524+
func (e *UtlsCompressCertExtension) writeToUConn(uc *UConn) error {
525+
uc.certCompressionAlgs = e.Algorithms
526+
return nil
527+
}
528+
529+
func (e *UtlsCompressCertExtension) Len() int {
530+
return 4 + 1 + (2 * len(e.Algorithms))
531+
}
532+
533+
func (e *UtlsCompressCertExtension) Read(b []byte) (int, error) {
534+
if len(b) < e.Len() {
535+
return 0, io.ErrShortBuffer
536+
}
537+
b[0] = byte(utlsExtensionCompressCertificate >> 8)
538+
b[1] = byte(utlsExtensionCompressCertificate & 0xff)
539+
540+
extLen := 2 * len(e.Algorithms)
541+
if extLen > 255 {
542+
return 0, errors.New("too many certificate compression methods")
543+
}
544+
545+
// Extension data length.
546+
b[2] = byte((extLen + 1) >> 8)
547+
b[3] = byte((extLen + 1) & 0xff)
548+
549+
// Methods length.
550+
b[4] = byte(extLen)
551+
552+
i := 5
553+
for _, compMethod := range e.Algorithms {
554+
b[i] = byte(compMethod >> 8)
555+
b[i+1] = byte(compMethod)
556+
i += 2
557+
}
558+
return e.Len(), io.EOF
559+
}
560+
516561
// https://github.com/google/boringssl/blob/7d7554b6b3c79e707e25521e61e066ce2b996e4c/ssl/t1_lib.c#L2803
517562
func BoringPaddingStyle(unpaddedLen int) (int, bool) {
518563
if unpaddedLen > 0xff && unpaddedLen < 0x200 {
@@ -703,44 +748,6 @@ func (e *FakeChannelIDExtension) Read(b []byte) (int, error) {
703748
return e.Len(), io.EOF
704749
}
705750

706-
type FakeCertCompressionAlgsExtension struct {
707-
Methods []CertCompressionAlgo
708-
}
709-
710-
func (e *FakeCertCompressionAlgsExtension) writeToUConn(uc *UConn) error {
711-
return nil
712-
}
713-
714-
func (e *FakeCertCompressionAlgsExtension) Len() int {
715-
return 4 + 1 + (2 * len(e.Methods))
716-
}
717-
718-
func (e *FakeCertCompressionAlgsExtension) Read(b []byte) (int, error) {
719-
if len(b) < e.Len() {
720-
return 0, io.ErrShortBuffer
721-
}
722-
// https://tools.ietf.org/html/draft-balfanz-tls-channelid-00
723-
b[0] = byte(fakeCertCompressionAlgs >> 8)
724-
b[1] = byte(fakeCertCompressionAlgs & 0xff)
725-
726-
extLen := 2 * len(e.Methods)
727-
if extLen > 255 {
728-
return 0, errors.New("too many certificate compression methods")
729-
}
730-
731-
b[2] = byte((extLen + 1) >> 8)
732-
b[3] = byte((extLen + 1) & 0xff)
733-
b[4] = byte(extLen)
734-
735-
i := 5
736-
for _, compMethod := range e.Methods {
737-
b[i] = byte(compMethod >> 8)
738-
b[i+1] = byte(compMethod)
739-
i += 2
740-
}
741-
return e.Len(), io.EOF
742-
}
743-
744751
type FakeRecordSizeLimitExtension struct {
745752
Limit uint16
746753
}

0 commit comments

Comments
 (0)