mirror of
https://github.com/ScuroNeko/mtg.git
synced 2026-08-31 08:54:02 +03:00
Do not use default TLS cipher
As per RFC, if TLS server cannot pickup a suitable cipher from a client list, it has to send handshake_failure alert. For us it means that we have to route a request to a fronting domain, because we want to have it exactly like a real webserver does. So, if it misbehaves, so do we.
This commit is contained in:
@@ -5,8 +5,8 @@ import (
|
|||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
"crypto/tls"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
@@ -25,7 +25,6 @@ const (
|
|||||||
// https://medium.com/asecuritysite-when-bob-met-alice/in-cybersecurity-what-is-grease-9f8850558dea
|
// https://medium.com/asecuritysite-when-bob-met-alice/in-cybersecurity-what-is-grease-9f8850558dea
|
||||||
GreaseMask = 0x0f0f
|
GreaseMask = 0x0f0f
|
||||||
GreaseValueType = 0x0a0a
|
GreaseValueType = 0x0a0a
|
||||||
DefaultCipher = tls.TLS_AES_128_GCM_SHA256
|
|
||||||
|
|
||||||
sniDNSNamesListType = 0
|
sniDNSNamesListType = 0
|
||||||
)
|
)
|
||||||
@@ -33,6 +32,8 @@ const (
|
|||||||
var (
|
var (
|
||||||
emptyRandom = [RandomLen]byte{}
|
emptyRandom = [RandomLen]byte{}
|
||||||
extTypeSNI = [2]byte{}
|
extTypeSNI = [2]byte{}
|
||||||
|
|
||||||
|
ErrCannotFindCipher = errors.New("cannot find a cipher")
|
||||||
)
|
)
|
||||||
|
|
||||||
type ClientHello struct {
|
type ClientHello struct {
|
||||||
@@ -110,9 +111,7 @@ func parseHandshake(r io.Reader) (*ClientHello, error) {
|
|||||||
return nil, fmt.Errorf("cannot read client version: %w", err)
|
return nil, fmt.Errorf("cannot read client version: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
hello := &ClientHello{
|
hello := &ClientHello{}
|
||||||
CipherSuite: DefaultCipher,
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := io.ReadFull(r, hello.Random[:]); err != nil {
|
if _, err := io.ReadFull(r, hello.Random[:]); err != nil {
|
||||||
return nil, fmt.Errorf("cannot read client random: %w", err)
|
return nil, fmt.Errorf("cannot read client random: %w", err)
|
||||||
@@ -133,7 +132,6 @@ func parseHandshake(r io.Reader) (*ClientHello, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cipherSuiteLen := int64(binary.BigEndian.Uint16(header[:]))
|
cipherSuiteLen := int64(binary.BigEndian.Uint16(header[:]))
|
||||||
foundCipher := false
|
|
||||||
|
|
||||||
// Pick the first non-GREASE cipher suite from the list.
|
// Pick the first non-GREASE cipher suite from the list.
|
||||||
// Real TLS servers never select GREASE values (RFC 8701, pattern 0x?a?a),
|
// Real TLS servers never select GREASE values (RFC 8701, pattern 0x?a?a),
|
||||||
@@ -144,17 +142,20 @@ func parseHandshake(r io.Reader) (*ClientHello, error) {
|
|||||||
return nil, fmt.Errorf("cannot read cipher suite: %w", err)
|
return nil, fmt.Errorf("cannot read cipher suite: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if foundCipher {
|
if hello.CipherSuite != 0 {
|
||||||
|
// do not forget we have to scan until the end
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if cs := binary.BigEndian.Uint16(header[:]); cs&GreaseMask != GreaseValueType {
|
if cs := binary.BigEndian.Uint16(header[:]); cs&GreaseMask != GreaseValueType {
|
||||||
hello.CipherSuite = cs
|
hello.CipherSuite = cs
|
||||||
// do not forget we have to scan until the end
|
|
||||||
foundCipher = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if hello.CipherSuite == 0 {
|
||||||
|
return nil, ErrCannotFindCipher
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := io.ReadFull(r, header[:1]); err != nil {
|
if _, err := io.ReadFull(r, header[:1]); err != nil {
|
||||||
return nil, fmt.Errorf("cannot read compression methods length: %w", err)
|
return nil, fmt.Errorf("cannot read compression methods length: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package fake_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
cryptotls "crypto/tls"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
@@ -234,9 +235,24 @@ func (suite *ParseClientHelloHandshakeBodyTestSuite) TestCannotSkipRemainingCiph
|
|||||||
suite.ErrorContains(err, "cannot read cipher suite")
|
suite.ErrorContains(err, "cannot read cipher suite")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (suite *ParseClientHelloHandshakeBodyTestSuite) TestCannotFindCipher() {
|
||||||
|
// All cipher suites are GREASE values — must return ErrCannotFindCipher.
|
||||||
|
body := make([]byte, 2+fake.RandomLen+1+2+4+1)
|
||||||
|
binary.BigEndian.PutUint16(body[2+fake.RandomLen+1:], 4)
|
||||||
|
binary.BigEndian.PutUint16(body[2+fake.RandomLen+1+2:], 0x0a0a)
|
||||||
|
binary.BigEndian.PutUint16(body[2+fake.RandomLen+1+2+2:], 0x1a1a)
|
||||||
|
body[2+fake.RandomLen+1+2+4] = 1
|
||||||
|
|
||||||
|
suite.writeBody(body)
|
||||||
|
|
||||||
|
_, err := fake.ReadClientHello(suite.connMock, suite.secret.Key[:], suite.secret.Host, TolerateTime)
|
||||||
|
suite.ErrorIs(err, fake.ErrCannotFindCipher)
|
||||||
|
}
|
||||||
|
|
||||||
func (suite *ParseClientHelloHandshakeBodyTestSuite) TestCannotReadCompressionMethodsLength() {
|
func (suite *ParseClientHelloHandshakeBodyTestSuite) TestCannotReadCompressionMethodsLength() {
|
||||||
body := make([]byte, 2+fake.RandomLen+1+2+2)
|
body := make([]byte, 2+fake.RandomLen+1+2+2)
|
||||||
binary.BigEndian.PutUint16(body[2+fake.RandomLen+1:], 2)
|
binary.BigEndian.PutUint16(body[2+fake.RandomLen+1:], 2)
|
||||||
|
binary.BigEndian.PutUint16(body[2+fake.RandomLen+1+2:], cryptotls.TLS_AES_128_GCM_SHA256)
|
||||||
|
|
||||||
suite.writeBody(body)
|
suite.writeBody(body)
|
||||||
|
|
||||||
@@ -247,6 +263,7 @@ func (suite *ParseClientHelloHandshakeBodyTestSuite) TestCannotReadCompressionMe
|
|||||||
func (suite *ParseClientHelloHandshakeBodyTestSuite) TestCannotSkipCompressionMethods() {
|
func (suite *ParseClientHelloHandshakeBodyTestSuite) TestCannotSkipCompressionMethods() {
|
||||||
body := make([]byte, 2+fake.RandomLen+1+2+2+1)
|
body := make([]byte, 2+fake.RandomLen+1+2+2+1)
|
||||||
binary.BigEndian.PutUint16(body[2+fake.RandomLen+1:], 2)
|
binary.BigEndian.PutUint16(body[2+fake.RandomLen+1:], 2)
|
||||||
|
binary.BigEndian.PutUint16(body[2+fake.RandomLen+1+2:], cryptotls.TLS_AES_128_GCM_SHA256)
|
||||||
body[2+fake.RandomLen+1+2+2] = 1
|
body[2+fake.RandomLen+1+2+2] = 1
|
||||||
|
|
||||||
suite.writeBody(body)
|
suite.writeBody(body)
|
||||||
@@ -282,6 +299,7 @@ func (suite *ParseClientHelloSNITestSuite) writeExtensions(extensions []byte) {
|
|||||||
// cipherSuite(2) + compressionLen(1) + compression(1) = 41
|
// cipherSuite(2) + compressionLen(1) + compression(1) = 41
|
||||||
body := make([]byte, 41)
|
body := make([]byte, 41)
|
||||||
binary.BigEndian.PutUint16(body[35:], 2)
|
binary.BigEndian.PutUint16(body[35:], 2)
|
||||||
|
binary.BigEndian.PutUint16(body[37:], cryptotls.TLS_AES_128_GCM_SHA256)
|
||||||
body[39] = 1
|
body[39] = 1
|
||||||
|
|
||||||
suite.readBuf.Write(body)
|
suite.readBuf.Write(body)
|
||||||
|
|||||||
Reference in New Issue
Block a user