diff --git a/README.md b/README.md index 97b48b38..ea82b497 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,17 @@ # ![uTLS](logo_small.png) uTLS -[![Build Status](https://travis-ci.org/refraction-networking/utls.svg?branch=master)](https://travis-ci.org/refraction-networking/utls) + [![godoc](https://img.shields.io/badge/godoc-reference-blue.svg)](https://godoc.org/github.com/refraction-networking/utls#UConn) --- uTLS is a fork of "crypto/tls", which provides ClientHello fingerprinting resistance, low-level access to handshake, fake session tickets and some other features. Handshake is still performed by "crypto/tls", this library merely changes ClientHello part of it and provides low-level access. -Golang 1.11+ is required. -If you have any questions, bug reports or contributions, you are welcome to publish those on GitHub. If you want to do so in private, you can contact one of developers personally via sergey.frolov@colorado.edu +Golang 1.19+ is required. + +If you have any questions, bug reports or contributions, you are welcome to publish those on GitHub. If you want to do so in private, you can contact one of developers personally via sergey.frolov@colorado.edu. Documentation below may not keep up with all the changes and new features at all times, so you are encouraged to use [godoc](https://godoc.org/github.com/refraction-networking/utls#UConn). +*Note: Information provided below in this README.md could be obsolete.* + # Features ## Low-level access to handshake * Read/write access to all bits of client hello message. diff --git a/auth.go b/auth.go index a9df0da6..7c5675c6 100644 --- a/auth.go +++ b/auth.go @@ -169,6 +169,7 @@ var rsaSignatureSchemes = []struct { // and optionally filtered by its explicit SupportedSignatureAlgorithms. // // This function must be kept in sync with supportedSignatureAlgorithms. +// FIPS filtering is applied in the caller, selectSignatureScheme. func signatureSchemesForCertificate(version uint16, cert *Certificate) []SignatureScheme { priv, ok := cert.PrivateKey.(crypto.Signer) if !ok { @@ -241,6 +242,9 @@ func selectSignatureScheme(vers uint16, c *Certificate, peerAlgs []SignatureSche // Pick signature scheme in the peer's preference order, as our // preference order is not configurable. for _, preferredAlg := range peerAlgs { + if needFIPS() && !isSupportedSignatureAlgorithm(preferredAlg, fipsSupportedSignatureAlgorithms) { + continue + } if isSupportedSignatureAlgorithm(preferredAlg, supportedAlgs) { return preferredAlg, nil } diff --git a/auth_test.go b/auth_test.go index c42e3491..c23d93f3 100644 --- a/auth_test.go +++ b/auth_test.go @@ -153,7 +153,7 @@ func TestLegacyTypeAndHash(t *testing.T) { // TestSupportedSignatureAlgorithms checks that all supportedSignatureAlgorithms // have valid type and hash information. func TestSupportedSignatureAlgorithms(t *testing.T) { - for _, sigAlg := range supportedSignatureAlgorithms { + for _, sigAlg := range supportedSignatureAlgorithms() { sigType, hash, err := typeAndHashFromSignatureScheme(sigAlg) if err != nil { t.Errorf("%v: unexpected error: %v", sigAlg, err) diff --git a/cipher_suites.go b/cipher_suites.go index 4c8ff49b..b0b6af30 100644 --- a/cipher_suites.go +++ b/cipher_suites.go @@ -141,7 +141,7 @@ type cipherSuite struct { ka func(version uint16) keyAgreement // flags is a bitmask of the suite* values, above. flags int - cipher func(key, iv []byte, isRead bool) interface{} + cipher func(key, iv []byte, isRead bool) any mac func(key []byte) hash.Hash aead func(key, fixedNonce []byte) aead } @@ -399,12 +399,12 @@ func aesgcmPreferred(ciphers []uint16) bool { return false } -func cipherRC4(key, iv []byte, isRead bool) interface{} { +func cipherRC4(key, iv []byte, isRead bool) any { cipher, _ := rc4.NewCipher(key) return cipher } -func cipher3DES(key, iv []byte, isRead bool) interface{} { +func cipher3DES(key, iv []byte, isRead bool) any { block, _ := des.NewTripleDESCipher(key) if isRead { return cipher.NewCBCDecrypter(block, iv) @@ -412,7 +412,7 @@ func cipher3DES(key, iv []byte, isRead bool) interface{} { return cipher.NewCBCEncrypter(block, iv) } -func cipherAES(key, iv []byte, isRead bool) interface{} { +func cipherAES(key, iv []byte, isRead bool) any { block, _ := aes.NewCipher(key) if isRead { return cipher.NewCBCDecrypter(block, iv) @@ -422,7 +422,13 @@ func cipherAES(key, iv []byte, isRead bool) interface{} { // macSHA1 returns a SHA-1 based constant time MAC. func macSHA1(key []byte) hash.Hash { - return hmac.New(newConstantTimeHash(sha1.New), key) + h := sha1.New + // The BoringCrypto SHA1 does not have a constant-time + // checksum function, so don't try to use it. + if !boring.Enabled { + h = newConstantTimeHash(h) + } + return hmac.New(h, key) } // macSHA256 returns a SHA-256 based MAC. This is only supported in TLS 1.2 and @@ -510,7 +516,13 @@ func aeadAESGCM(key, noncePrefix []byte) aead { if err != nil { panic(err) } - aead, err := cipher.NewGCM(aes) + var aead cipher.AEAD + if boring.Enabled { + aead, err = boring.NewGCMTLS(aes) + } else { + boring.Unreachable() + aead, err = cipher.NewGCM(aes) + } if err != nil { panic(err) } @@ -570,6 +582,7 @@ func (c *cthWrapper) Write(p []byte) (int, error) { return c.h.Write(p) } func (c *cthWrapper) Sum(b []byte) []byte { return c.h.ConstantTimeSum(b) } func newConstantTimeHash(h func() hash.Hash) func() hash.Hash { + boring.Unreachable() return func() hash.Hash { return &cthWrapper{h().(constantTimeHash)} } diff --git a/common.go b/common.go index 65308a0f..b3d7f89b 100644 --- a/common.go +++ b/common.go @@ -100,7 +100,7 @@ const ( extensionCertificateAuthorities uint16 = 47 extensionSignatureAlgorithmsCert uint16 = 50 extensionKeyShare uint16 = 51 - extensionNextProtoNeg uint16 = 13172 // not IANA assigned + extensionNextProtoNeg uint16 = 13172 // not IANA assigned // Pending discussion on whether or not remove this. crypto/tls removed it on Nov 21, 2019. extensionRenegotiationInfo uint16 = 0xff01 ) @@ -173,11 +173,11 @@ const ( // hash function associated with the Ed25519 signature scheme. var directSigning crypto.Hash = 0 -// supportedSignatureAlgorithms contains the signature and hash algorithms that +// defaultSupportedSignatureAlgorithms contains the signature and hash algorithms that // the code advertises as supported in a TLS 1.2+ ClientHello and in a TLS 1.2+ // CertificateRequest. The two fields are merged to match with TLS 1.3. // Note that in TLS 1.2, the ECDSA algorithms are not constrained to P-256, etc. -var supportedSignatureAlgorithms = []SignatureScheme{ +var defaultSupportedSignatureAlgorithms = []SignatureScheme{ PSSWithSHA256, ECDSAWithP256AndSHA256, Ed25519, @@ -962,6 +962,9 @@ func (c *Config) time() time.Time { } func (c *Config) cipherSuites() []uint16 { + if needFIPS() { + return fipsCipherSuites(c) + } if c.CipherSuites != nil { return c.CipherSuites } @@ -975,10 +978,6 @@ var supportedVersions = []uint16{ VersionTLS10, } -// debugEnableTLS10 enables TLS 1.0. See issue 45428. -// [uTLS] disabled TLS 1.0 -var debugEnableTLS10 = false - // roleClient and roleServer are meant to call supportedVersions and parents // with more readability at the callsite. const roleClient = true @@ -987,7 +986,10 @@ const roleServer = false func (c *Config) supportedVersions(isClient bool) []uint16 { versions := make([]uint16, 0, len(supportedVersions)) for _, v := range supportedVersions { - if (c == nil || c.MinVersion == 0) && !debugEnableTLS10 && + if needFIPS() && (v < fipsMinVersion(c) || v > fipsMaxVersion(c)) { + continue + } + if (c == nil || c.MinVersion == 0) && isClient && v < VersionTLS12 { continue } @@ -1027,6 +1029,9 @@ func supportedVersionsFromMax(maxVersion uint16) []uint16 { var defaultCurvePreferences = []CurveID{X25519, CurveP256, CurveP384, CurveP521} func (c *Config) curvePreferences() []CurveID { + if needFIPS() { + return fipsCurvePreferences(c) + } if c == nil || len(c.CurvePreferences) == 0 { return defaultCurvePreferences } @@ -1468,7 +1473,7 @@ func defaultConfig() *Config { return &emptyConfig } -func unexpectedMessageError(wanted, got interface{}) error { +func unexpectedMessageError(wanted, got any) error { return fmt.Errorf("tls: received unexpected handshake message of type %T when waiting for %T", got, wanted) } diff --git a/conn.go b/conn.go index 43eed7f2..805354b7 100644 --- a/conn.go +++ b/conn.go @@ -164,16 +164,16 @@ func (c *Conn) NetConn() net.Conn { type halfConn struct { sync.Mutex - err error // first permanent error - version uint16 // protocol version - cipher interface{} // cipher algorithm + err error // first permanent error + version uint16 // protocol version + cipher any // cipher algorithm mac hash.Hash seq [8]byte // 64-bit sequence number scratchBuf [13]byte // to avoid allocs; interface method args escape - nextCipher interface{} // next encryption state - nextMac hash.Hash // next MAC algorithm + nextCipher any // next encryption state + nextMac hash.Hash // next MAC algorithm trafficSecret []byte // current TLS 1.3 traffic secret } @@ -198,7 +198,7 @@ func (hc *halfConn) setErrorLocked(err error) error { // prepareCipherSpec sets the encryption and MAC states // that a subsequent changeCipherSpec will use. -func (hc *halfConn) prepareCipherSpec(version uint16, cipher interface{}, mac hash.Hash) { +func (hc *halfConn) prepareCipherSpec(version uint16, cipher any, mac hash.Hash) { hc.version = version hc.nextCipher = cipher hc.nextMac = mac @@ -761,7 +761,7 @@ func (c *Conn) readRecordOrCCS(expectChangeCipherSpec bool) error { return nil } -// retryReadRecord recurses into readRecordOrCCS to drop a non-advancing record, like +// retryReadRecord recurs into readRecordOrCCS to drop a non-advancing record, like // a warning alert, empty application_data, or a change_cipher_spec in TLS 1.3. func (c *Conn) retryReadRecord(expectChangeCipherSpec bool) error { c.retryCount++ @@ -938,7 +938,7 @@ func (c *Conn) flush() (int, error) { // outBufPool pools the record-sized scratch buffers used by writeRecordLocked. var outBufPool = sync.Pool{ - New: func() interface{} { + New: func() any { return new([]byte) }, } @@ -1014,7 +1014,7 @@ func (c *Conn) writeRecord(typ recordType, data []byte) (int, error) { // readHandshake reads the next handshake message from // the record layer. -func (c *Conn) readHandshake() (interface{}, error) { +func (c *Conn) readHandshake() (any, error) { for c.hand.Len() < 4 { if err := c.readRecord(); err != nil { return nil, err diff --git a/fipsonly/fipsonly.go b/fipsonly/fipsonly.go new file mode 100644 index 00000000..e5e47835 --- /dev/null +++ b/fipsonly/fipsonly.go @@ -0,0 +1,29 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build boringcrypto + +// Package fipsonly restricts all TLS configuration to FIPS-approved settings. +// +// The effect is triggered by importing the package anywhere in a program, as in: +// +// import _ "crypto/tls/fipsonly" +// +// This package only exists when using Go compiled with GOEXPERIMENT=boringcrypto. +package fipsonly + +// This functionality is provided as a side effect of an import to make +// it trivial to add to an existing program. It requires only a single line +// added to an existing source file, or it can be done by adding a whole +// new source file and not modifying any existing source files. + +import ( + "crypto/internal/boring/fipstls" + "crypto/internal/boring/sig" +) + +func init() { + fipstls.Force() + sig.FIPSOnly() +} diff --git a/fipsonly/fipsonly_test.go b/fipsonly/fipsonly_test.go new file mode 100644 index 00000000..f8485dc3 --- /dev/null +++ b/fipsonly/fipsonly_test.go @@ -0,0 +1,18 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build boringcrypto + +package fipsonly + +import ( + "crypto/internal/boring/fipstls" + "testing" +) + +func Test(t *testing.T) { + if !fipstls.Required() { + t.Fatal("fipstls.Required() = false, must be true") + } +} diff --git a/generate_cert.go b/generate_cert.go index 7ea90f8a..74509c9d 100644 --- a/generate_cert.go +++ b/generate_cert.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build ignore -// +build ignore // Generate a self-signed X.509 certificate for a TLS server. Outputs to // 'cert.pem' and 'key.pem' and will overwrite existing files. @@ -38,7 +37,7 @@ var ( ed25519Key = flag.Bool("ed25519", false, "Generate an Ed25519 key") ) -func publicKey(priv interface{}) interface{} { +func publicKey(priv any) any { switch k := priv.(type) { case *rsa.PrivateKey: return &k.PublicKey @@ -58,7 +57,7 @@ func main() { log.Fatalf("Missing required --host parameter") } - var priv interface{} + var priv any var err error switch *ecdsaCurve { case "": diff --git a/go.mod b/go.mod index 8dcb2619..6d81e07e 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,15 @@ module github.com/refraction-networking/utls -go 1.16 +go 1.19 require ( github.com/andybalholm/brotli v1.0.4 - github.com/klauspost/compress v1.15.9 - golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 - golang.org/x/net v0.0.0-20220909164309-bea034e7d591 + github.com/klauspost/compress v1.15.12 + golang.org/x/crypto v0.1.0 + golang.org/x/net v0.1.0 +) + +require ( + golang.org/x/sys v0.1.0 // indirect + golang.org/x/text v0.4.0 // indirect ) diff --git a/go.sum b/go.sum index 20803ed2..16fe15cd 100644 --- a/go.sum +++ b/go.sum @@ -1,20 +1,12 @@ github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= -github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= -github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= -golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= -golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220909164309-bea034e7d591 h1:D0B/7al0LLrVC8aWF4+oxpv/m8bc7ViFfVS8/gXGdqI= -golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +github.com/klauspost/compress v1.15.12 h1:YClS/PImqYbn+UILDnqxQCZ3RehC9N318SU3kElDUEM= +github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= +golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= diff --git a/handshake_client.go b/handshake_client.go index 96a6bce1..39a3355e 100644 --- a/handshake_client.go +++ b/handshake_client.go @@ -36,6 +36,8 @@ type clientHandshakeState struct { uconn *UConn // [uTLS] } +var testingOnlyForceClientHelloSignatureAlgorithms []SignatureScheme + func (c *Conn) makeClientHello() (*clientHelloMsg, ecdheParameters, error) { config := c.config if len(config.ServerName) == 0 && !config.InsecureSkipVerify { @@ -119,7 +121,10 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, ecdheParameters, error) { } if hello.vers >= VersionTLS12 { - hello.supportedSignatureAlgorithms = supportedSignatureAlgorithms + hello.supportedSignatureAlgorithms = supportedSignatureAlgorithms() + } + if testingOnlyForceClientHelloSignatureAlgorithms != nil { + hello.supportedSignatureAlgorithms = testingOnlyForceClientHelloSignatureAlgorithms } var params ecdheParameters @@ -663,7 +668,7 @@ func (hs *clientHandshakeState) establishKeys() error { clientMAC, serverMAC, clientKey, serverKey, clientIV, serverIV := keysFromMasterSecret(c.vers, hs.suite, hs.masterSecret, hs.hello.random, hs.serverHello.random, hs.suite.macLen, hs.suite.keyLen, hs.suite.ivLen) - var clientCipher, serverCipher interface{} + var clientCipher, serverCipher any var clientHash, serverHash hash.Hash if hs.suite.cipher != nil { clientCipher = hs.suite.cipher(clientKey, clientIV, false /* not for reading */) @@ -867,6 +872,7 @@ func (c *Conn) verifyServerCertificate(certificates [][]byte) error { DNSName: c.config.ServerName, Intermediates: x509.NewCertPool(), } + for _, cert := range certs[1:] { opts.Intermediates.AddCert(cert) } diff --git a/handshake_client_test.go b/handshake_client_test.go index 2158f324..380de9f6 100644 --- a/handshake_client_test.go +++ b/handshake_client_test.go @@ -134,7 +134,7 @@ type clientTest struct { cert []byte // key, if not nil, contains either a *rsa.PrivateKey, ed25519.PrivateKey or // *ecdsa.PrivateKey which is the private key for the reference server. - key interface{} + key any // extensions, if not nil, contains a list of extension data to be returned // from the ServerHello. The data should be in standard TLS format with // a 2-byte uint16 type, 2-byte data length, followed by the extension data. @@ -171,7 +171,7 @@ func (test *clientTest) connFromCommand() (conn *recordingConn, child *exec.Cmd, certPath := tempFile(string(cert)) defer os.Remove(certPath) - var key interface{} = testRSAPrivateKey + var key any = testRSAPrivateKey if test.key != nil { key = test.key } @@ -2564,7 +2564,7 @@ func testResumptionKeepsOCSPAndSCT(t *testing.T, ver uint16) { } } -// TestClientHandshakeContextCancellation tests that cancelling +// TestClientHandshakeContextCancellation tests that canceling // the context given to the client side conn.HandshakeContext // interrupts the in-progress handshake. func TestClientHandshakeContextCancellation(t *testing.T) { diff --git a/handshake_client_tls13.go b/handshake_client_tls13.go index 1724f245..47aa2026 100644 --- a/handshake_client_tls13.go +++ b/handshake_client_tls13.go @@ -49,6 +49,10 @@ type clientHandshakeStateTLS13 struct { func (hs *clientHandshakeStateTLS13) handshake() error { c := hs.c + if needFIPS() { + return errors.New("tls: internal error: TLS 1.3 reached in FIPS mode") + } + // The server must not select TLS 1.3 in a renegotiation. See RFC 8446, // sections 4.1.2 and 4.1.3. if c.handshakes > 0 { @@ -564,7 +568,7 @@ func (hs *clientHandshakeStateTLS13) readServerCertificate() error { } // See RFC 8446, Section 4.4.3. - if !isSupportedSignatureAlgorithm(certVerify.signatureAlgorithm, supportedSignatureAlgorithms) { + if !isSupportedSignatureAlgorithm(certVerify.signatureAlgorithm, supportedSignatureAlgorithms()) { c.sendAlert(alertIllegalParameter) return errors.New("tls: certificate used with invalid signature algorithm") } diff --git a/handshake_messages.go b/handshake_messages.go index 55821d17..6daf19c5 100644 --- a/handshake_messages.go +++ b/handshake_messages.go @@ -388,6 +388,7 @@ func (m *clientHelloMsg) unmarshal(data []byte) bool { return false } + seenExts := make(map[uint16]bool) for !extensions.Empty() { var extension uint16 var extData cryptobyte.String @@ -396,6 +397,11 @@ func (m *clientHelloMsg) unmarshal(data []byte) bool { return false } + if seenExts[extension] { + return false + } + seenExts[extension] = true + switch extension { case extensionServerName: // RFC 6066, Section 3 @@ -759,6 +765,7 @@ func (m *serverHelloMsg) unmarshal(data []byte) bool { return false } + seenExts := make(map[uint16]bool) for !extensions.Empty() { var extension uint16 var extData cryptobyte.String @@ -767,6 +774,11 @@ func (m *serverHelloMsg) unmarshal(data []byte) bool { return false } + if seenExts[extension] { + return false + } + seenExts[extension] = true + switch extension { case extensionStatusRequest: m.ocspStapling = true diff --git a/handshake_messages_test.go b/handshake_messages_test.go index 914a4ed7..93ac1b1c 100644 --- a/handshake_messages_test.go +++ b/handshake_messages_test.go @@ -6,6 +6,7 @@ package tls import ( "bytes" + "encoding/hex" "math/rand" "reflect" "strings" @@ -14,7 +15,7 @@ import ( "time" ) -var tests = []interface{}{ +var tests = []any{ &clientHelloMsg{}, &serverHelloMsg{}, &finishedMsg{}, @@ -148,10 +149,10 @@ func (*clientHelloMsg) Generate(rand *rand.Rand, size int) reflect.Value { } } if rand.Intn(10) > 5 { - m.supportedSignatureAlgorithms = supportedSignatureAlgorithms + m.supportedSignatureAlgorithms = supportedSignatureAlgorithms() } if rand.Intn(10) > 5 { - m.supportedSignatureAlgorithmsCert = supportedSignatureAlgorithms + m.supportedSignatureAlgorithmsCert = supportedSignatureAlgorithms() } for i := 0; i < rand.Intn(5); i++ { m.alpnProtocols = append(m.alpnProtocols, randomString(rand.Intn(20)+1, rand)) @@ -370,10 +371,10 @@ func (*certificateRequestMsgTLS13) Generate(rand *rand.Rand, size int) reflect.V m.scts = true } if rand.Intn(10) > 5 { - m.supportedSignatureAlgorithms = supportedSignatureAlgorithms + m.supportedSignatureAlgorithms = supportedSignatureAlgorithms() } if rand.Intn(10) > 5 { - m.supportedSignatureAlgorithmsCert = supportedSignatureAlgorithms + m.supportedSignatureAlgorithmsCert = supportedSignatureAlgorithms() } if rand.Intn(10) > 5 { m.certificateAuthorities = make([][]byte, 3) @@ -473,3 +474,23 @@ func TestRejectEmptySCT(t *testing.T) { t.Fatal("Unmarshaled ServerHello with zero-length SCT") } } + +func TestRejectDuplicateExtensions(t *testing.T) { + clientHelloBytes, err := hex.DecodeString("010000440303000000000000000000000000000000000000000000000000000000000000000000000000001c0000000a000800000568656c6c6f0000000a000800000568656c6c6f") + if err != nil { + t.Fatalf("failed to decode test ClientHello: %s", err) + } + var clientHelloCopy clientHelloMsg + if clientHelloCopy.unmarshal(clientHelloBytes) { + t.Error("Unmarshaled ClientHello with duplicate extensions") + } + + serverHelloBytes, err := hex.DecodeString("02000030030300000000000000000000000000000000000000000000000000000000000000000000000000080005000000050000") + if err != nil { + t.Fatalf("failed to decode test ServerHello: %s", err) + } + var serverHelloCopy serverHelloMsg + if serverHelloCopy.unmarshal(serverHelloBytes) { + t.Fatal("Unmarshaled ServerHello with duplicate extensions") + } +} diff --git a/handshake_server.go b/handshake_server.go index 5cb15275..92b38cb1 100644 --- a/handshake_server.go +++ b/handshake_server.go @@ -240,7 +240,7 @@ func (hs *serverHandshakeState) processClientHello() error { hs.ecdheOk = supportsECDHE(c.config, hs.clientHello.supportedCurves, hs.clientHello.supportedPoints) - if hs.ecdheOk { + if hs.ecdheOk && len(hs.clientHello.supportedPoints) > 0 { // Although omitting the ec_point_formats extension is permitted, some // old OpenSSL version will refuse to handshake if not present. // @@ -321,6 +321,13 @@ func supportsECDHE(c *Config, supportedCurves []CurveID, supportedPoints []uint8 break } } + // Per RFC 8422, Section 5.1.2, if the Supported Point Formats extension is + // missing, uncompressed points are supported. If supportedPoints is empty, + // the extension must be missing, as an empty extension body is rejected by + // the parser. See https://go.dev/issue/49126. + if len(supportedPoints) == 0 { + supportsPointFormat = true + } return supportsCurve && supportsPointFormat } @@ -541,7 +548,7 @@ func (hs *serverHandshakeState) doFullHandshake() error { } if c.vers >= VersionTLS12 { certReq.hasSignatureAlgorithm = true - certReq.supportedSignatureAlgorithms = supportedSignatureAlgorithms + certReq.supportedSignatureAlgorithms = supportedSignatureAlgorithms() } // An empty list of certificateAuthorities signals to @@ -681,7 +688,7 @@ func (hs *serverHandshakeState) establishKeys() error { clientMAC, serverMAC, clientKey, serverKey, clientIV, serverIV := keysFromMasterSecret(c.vers, hs.suite, hs.masterSecret, hs.clientHello.random, hs.hello.random, hs.suite.macLen, hs.suite.keyLen, hs.suite.ivLen) - var clientCipher, serverCipher interface{} + var clientCipher, serverCipher any var clientHash, serverHash hash.Hash if hs.suite.aead == nil { diff --git a/handshake_server_test.go b/handshake_server_test.go index 5fb2ebbb..39b65f6d 100644 --- a/handshake_server_test.go +++ b/handshake_server_test.go @@ -249,7 +249,7 @@ func TestTLS12OnlyCipherSuites(t *testing.T) { } c, s := localPipe(t) - replyChan := make(chan interface{}) + replyChan := make(chan any) go func() { cli := Client(c, testConfig) cli.vers = clientHello.vers @@ -281,7 +281,7 @@ func TestTLS12OnlyCipherSuites(t *testing.T) { func TestTLSPointFormats(t *testing.T) { // Test that a Server returns the ec_point_format extension when ECC is - // negotiated, and not returned on RSA handshake. + // negotiated, and not on a RSA handshake or if ec_point_format is missing. tests := []struct { name string cipherSuites []uint16 @@ -289,8 +289,11 @@ func TestTLSPointFormats(t *testing.T) { supportedPoints []uint8 wantSupportedPoints bool }{ - {"ECC", []uint16{TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA}, []CurveID{CurveP256}, []uint8{compressionNone}, true}, + {"ECC", []uint16{TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA}, []CurveID{CurveP256}, []uint8{pointFormatUncompressed}, true}, + {"ECC without ec_point_format", []uint16{TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA}, []CurveID{CurveP256}, nil, false}, + {"ECC with extra values", []uint16{TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA}, []CurveID{CurveP256}, []uint8{13, 37, pointFormatUncompressed, 42}, true}, {"RSA", []uint16{TLS_RSA_WITH_AES_256_GCM_SHA384}, nil, nil, false}, + {"RSA with ec_point_format", []uint16{TLS_RSA_WITH_AES_256_GCM_SHA384}, nil, []uint8{pointFormatUncompressed}, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -304,7 +307,7 @@ func TestTLSPointFormats(t *testing.T) { } c, s := localPipe(t) - replyChan := make(chan interface{}) + replyChan := make(chan any) go func() { cli := Client(c, testConfig) cli.vers = clientHello.vers @@ -330,22 +333,12 @@ func TestTLSPointFormats(t *testing.T) { t.Fatalf("didn't get ServerHello message in reply. Got %v\n", reply) } if tt.wantSupportedPoints { - if len(serverHello.supportedPoints) < 1 { - t.Fatal("missing ec_point_format extension from server") - } - found := false - for _, p := range serverHello.supportedPoints { - if p == pointFormatUncompressed { - found = true - break - } - } - if !found { - t.Fatal("missing uncompressed format in ec_point_format extension from server") + if !bytes.Equal(serverHello.supportedPoints, []uint8{pointFormatUncompressed}) { + t.Fatal("incorrect ec_point_format extension from server") } } else { if len(serverHello.supportedPoints) != 0 { - t.Fatalf("unexcpected ec_point_format extension from server: %v", serverHello.supportedPoints) + t.Fatalf("unexpected ec_point_format extension from server: %v", serverHello.supportedPoints) } } }) @@ -400,16 +393,6 @@ func TestVersion(t *testing.T) { if err == nil { t.Fatalf("expected failure to connect with TLS 1.0/1.1") } - - defer func(old bool) { debugEnableTLS10 = old }(debugEnableTLS10) - debugEnableTLS10 = true - _, _, err = testHandshake(t, clientConfig, serverConfig) - if err != nil { - t.Fatalf("handshake failed: %s", err) - } - if state.Version != VersionTLS11 { - t.Fatalf("incorrect version %x, should be %x", state.Version, VersionTLS11) - } } func TestCipherSuitePreference(t *testing.T) { @@ -600,7 +583,7 @@ func (test *serverTest) connFromCommand() (conn *recordingConn, child *exec.Cmd, return nil, nil, err } - connChan := make(chan interface{}, 1) + connChan := make(chan any, 1) go func() { tcpConn, err := l.Accept() if err != nil { @@ -1944,7 +1927,7 @@ func TestAESCipherReorderingTLS13(t *testing.T) { } } -// TestServerHandshakeContextCancellation tests that cancelling +// TestServerHandshakeContextCancellation tests that canceling // the context given to the server side conn.HandshakeContext // interrupts the in-progress handshake. func TestServerHandshakeContextCancellation(t *testing.T) { diff --git a/handshake_server_tls13.go b/handshake_server_tls13.go index 54e612ae..03a477f7 100644 --- a/handshake_server_tls13.go +++ b/handshake_server_tls13.go @@ -45,6 +45,10 @@ type serverHandshakeStateTLS13 struct { func (hs *serverHandshakeStateTLS13) handshake() error { c := hs.c + if needFIPS() { + return errors.New("tls: internal error: TLS 1.3 reached in FIPS mode") + } + // For an overview of the TLS 1.3 handshake, see RFC 8446, Section 2. if err := hs.processClientHello(); err != nil { return err @@ -584,7 +588,7 @@ func (hs *serverHandshakeStateTLS13) sendServerCertificate() error { certReq := new(certificateRequestMsgTLS13) certReq.ocspStapling = true certReq.scts = true - certReq.supportedSignatureAlgorithms = supportedSignatureAlgorithms + certReq.supportedSignatureAlgorithms = supportedSignatureAlgorithms() if c.config.ClientCAs != nil { certReq.certificateAuthorities = c.config.ClientCAs.Subjects() } @@ -816,7 +820,7 @@ func (hs *serverHandshakeStateTLS13) readClientCertificate() error { } // See RFC 8446, Section 4.4.3. - if !isSupportedSignatureAlgorithm(certVerify.signatureAlgorithm, supportedSignatureAlgorithms) { + if !isSupportedSignatureAlgorithm(certVerify.signatureAlgorithm, supportedSignatureAlgorithms()) { c.sendAlert(alertIllegalParameter) return errors.New("tls: client certificate used with invalid signature algorithm") } diff --git a/handshake_unix_test.go b/handshake_unix_test.go index 19fc6986..86a48f29 100644 --- a/handshake_unix_test.go +++ b/handshake_unix_test.go @@ -2,8 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris -// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris +//go:build unix package tls diff --git a/notboring.go b/notboring.go new file mode 100644 index 00000000..a8fcdf53 --- /dev/null +++ b/notboring.go @@ -0,0 +1,39 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +package tls + +import ( + "crypto/cipher" + "errors" +) + +func needFIPS() bool { return false } + +func supportedSignatureAlgorithms() []SignatureScheme { + return defaultSupportedSignatureAlgorithms +} + +func fipsMinVersion(c *Config) uint16 { panic("fipsMinVersion") } +func fipsMaxVersion(c *Config) uint16 { panic("fipsMaxVersion") } +func fipsCurvePreferences(c *Config) []CurveID { panic("fipsCurvePreferences") } +func fipsCipherSuites(c *Config) []uint16 { panic("fipsCipherSuites") } + +var fipsSupportedSignatureAlgorithms []SignatureScheme + +// [uTLS] +// Boring struct is only to be used to record static env variables +// in boring package. We do not implement BoringSSL compatibliity here. +type Boring struct { + Enabled bool +} + +func (*Boring) NewGCMTLS(_ cipher.Block) (cipher.AEAD, error) { + return nil, errors.New("boring not implemented") +} + +func (*Boring) Unreachable() { + // do nothing +} + +var boring Boring \ No newline at end of file diff --git a/ticket.go b/ticket.go index cbe94eab..9ce14540 100644 --- a/ticket.go +++ b/ticket.go @@ -144,20 +144,8 @@ func (c *Conn) encryptTicket(state []byte) ([]byte, error) { return encrypted, nil } -// [uTLS] changed to use exported DecryptTicketWith func below +// [uTLS] added exported DecryptTicketWith func below func (c *Conn) decryptTicket(encrypted []byte) (plaintext []byte, usedOldKey bool) { - tks := ticketKeys(c.config.ticketKeys(c.config)).ToPublic() - return DecryptTicketWith(encrypted, tks) -} - -// DecryptTicketWith decrypts an encrypted session ticket -// using a TicketKeys (ie []TicketKey) struct -// -// usedOldKey will be true if the key used for decryption is -// not the first in the []TicketKey slice -// -// [uTLS] changed to be made public and take a TicketKeys instead of use a Conn receiver -func DecryptTicketWith(encrypted []byte, tks TicketKeys) (plaintext []byte, usedOldKey bool) { if len(encrypted) < ticketKeyNameLen+aes.BlockSize+sha256.Size { return nil, false } @@ -168,8 +156,8 @@ func DecryptTicketWith(encrypted []byte, tks TicketKeys) (plaintext []byte, used ciphertext := encrypted[ticketKeyNameLen+aes.BlockSize : len(encrypted)-sha256.Size] keyIndex := -1 - for i, candidateKey := range tks { - if bytes.Equal(keyName, candidateKey.KeyName[:]) { + for i, candidateKey := range c.ticketKeys { + if bytes.Equal(keyName, candidateKey.keyName[:]) { keyIndex = i break } @@ -177,9 +165,9 @@ func DecryptTicketWith(encrypted []byte, tks TicketKeys) (plaintext []byte, used if keyIndex == -1 { return nil, false } - key := &tks[keyIndex] + key := &c.ticketKeys[keyIndex] - mac := hmac.New(sha256.New, key.HmacKey[:]) + mac := hmac.New(sha256.New, key.hmacKey[:]) mac.Write(encrypted[:len(encrypted)-sha256.Size]) expected := mac.Sum(nil) @@ -187,7 +175,7 @@ func DecryptTicketWith(encrypted []byte, tks TicketKeys) (plaintext []byte, used return nil, false } - block, err := aes.NewCipher(key.AesKey[:]) + block, err := aes.NewCipher(key.aesKey[:]) if err != nil { return nil, false } @@ -196,3 +184,19 @@ func DecryptTicketWith(encrypted []byte, tks TicketKeys) (plaintext []byte, used return plaintext, keyIndex > 0 } + +// DecryptTicketWith decrypts an encrypted session ticket +// using a TicketKeys (ie []TicketKey) struct +// +// usedOldKey will be true if the key used for decryption is +// not the first in the []TicketKey slice +// +// [uTLS] changed to be made public and take a TicketKeys and use a fake conn receiver +func DecryptTicketWith(encrypted []byte, tks TicketKeys) (plaintext []byte, usedOldKey bool) { + // create fake conn + c := &Conn{ + ticketKeys: tks.ToPrivate(), + } + + return c.decryptTicket(encrypted) +}