diff --git a/u_common.go b/u_common.go index 9c6e238c..668a677b 100644 --- a/u_common.go +++ b/u_common.go @@ -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 ( @@ -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 @@ -65,6 +70,7 @@ type CertCompressionAlgo uint16 const ( CertCompressionZlib CertCompressionAlgo = 0x0001 CertCompressionBrotli CertCompressionAlgo = 0x0002 + CertCompressionZstd CertCompressionAlgo = 0x0003 ) const ( diff --git a/u_conn.go b/u_conn.go index c3dfffe1..aab964e5 100644 --- a/u_conn.go +++ b/u_conn.go @@ -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. diff --git a/u_fingerprinter.go b/u_fingerprinter.go index ec1df89b..88b87cf2 100644 --- a/u_fingerprinter.go +++ b/u_fingerprinter.go @@ -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: diff --git a/u_handshake_messages.go b/u_handshake_messages.go new file mode 100644 index 00000000..07203c21 --- /dev/null +++ b/u_handshake_messages.go @@ -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 +} diff --git a/u_parrots.go b/u_parrots.go index faa5479e..eeda357b 100644 --- a/u_parrots.go +++ b/u_parrots.go @@ -133,7 +133,7 @@ func utlsIdToSpec(id ClientHelloID) (ClientHelloSpec, error) { CurveP256, CurveP384, }}, - &FakeCertCompressionAlgsExtension{[]CertCompressionAlgo{CertCompressionBrotli}}, + &UtlsCompressCertExtension{[]CertCompressionAlgo{CertCompressionBrotli}}, &UtlsGREASEExtension{}, &UtlsPaddingExtension{GetPaddingLen: BoringPaddingStyle}, }, @@ -205,7 +205,7 @@ func utlsIdToSpec(id ClientHelloID) (ClientHelloSpec, error) { VersionTLS11, VersionTLS10, }}, - &FakeCertCompressionAlgsExtension{[]CertCompressionAlgo{ + &UtlsCompressCertExtension{[]CertCompressionAlgo{ CertCompressionBrotli, }}, &UtlsGREASEExtension{}, @@ -277,7 +277,7 @@ func utlsIdToSpec(id ClientHelloID) (ClientHelloSpec, error) { VersionTLS11, VersionTLS10, }}, - &FakeCertCompressionAlgsExtension{[]CertCompressionAlgo{ + &UtlsCompressCertExtension{[]CertCompressionAlgo{ CertCompressionBrotli, }}, &UtlsGREASEExtension{}, diff --git a/u_tls_extensions.go b/u_tls_extensions.go index 4330f36b..8bf4f4b0 100644 --- a/u_tls_extensions.go +++ b/u_tls_extensions.go @@ -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 { @@ -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 }