-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added an option to use Post-Quantum secure algorithms
Added an option to use Post-Quantum secure algorithms for establishing TLS connections. This option is hidden under a new `--experiment` flag that is described in README.md. Closes #15
- Loading branch information
Showing
11 changed files
with
342 additions
and
130 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
// Package cfcrypto is a package that uses Cloudflare's TLS fork to provide | ||
// features missing in crypto/tls. | ||
package cfcrypto | ||
|
||
import ( | ||
"crypto/tls" | ||
"net" | ||
"slices" | ||
|
||
ctls "github.com/ameshkov/cfcrypto/tls" | ||
"github.com/ameshkov/gocurl/internal/config" | ||
"github.com/ameshkov/gocurl/internal/output" | ||
"github.com/ameshkov/gocurl/internal/resolve" | ||
) | ||
|
||
// Handshake attempts to establish a TLS connection using Cloudflare's TLS fork. | ||
// | ||
// Depending on the arguments, it may do the following: | ||
// | ||
// - Encrypted ClientHello. | ||
// - Post-quantum cryptography. | ||
// | ||
// # Arguments | ||
// | ||
// - conn is the underlying network connection that should already be | ||
// established. | ||
// - tlsConfig is the original tls.Config, its properties will be copied to | ||
// the ctls.Config used by this method. | ||
// - resolver is specified enables ECH support. | ||
// - cfg is the *config.Config configuration object. | ||
// - out is the *output.Output object that is used to write logs. | ||
// | ||
// # Encrypted ClientHello | ||
// | ||
// It is used if enabled in the cfg argument. A few things about the tlsConfig | ||
// that is passed to it: | ||
// | ||
// - ServerName will be used in the inner ClientHello. For the outer | ||
// ClientHello it will attempt to use the "public name" field of the ECH | ||
// configuration. | ||
// - Regarding the multiple ECHConfig passed, it chooses the first with | ||
// a suitable cipher suite which effectively means that it will almost | ||
// always simply use the first ECHConfig from the slice. | ||
// | ||
// # Post-quantum cryptography | ||
// | ||
// This basically means that new curves will be added to CurvePreferences. | ||
func Handshake( | ||
conn net.Conn, | ||
tlsConfig *tls.Config, | ||
resolver *resolve.Resolver, | ||
cfg *config.Config, | ||
out *output.Output, | ||
) (tlsConn net.Conn, err error) { | ||
out.Debug("Attempting to establish a TLS connection") | ||
|
||
var echConfigs []ctls.ECHConfig | ||
if cfg.ECH { | ||
echConfigs, err = resolver.LookupECHConfigs(tlsConfig.ServerName) | ||
if err != nil { | ||
return nil, err | ||
} | ||
} | ||
|
||
_, postQuantum := cfg.Experiments[config.ExpPostQuantum] | ||
|
||
// Copying the original tls config fields to ECH-enabled one. | ||
conf := &ctls.Config{ | ||
ServerName: tlsConfig.ServerName, | ||
MinVersion: tlsConfig.MinVersion, | ||
MaxVersion: tlsConfig.MaxVersion, | ||
InsecureSkipVerify: tlsConfig.InsecureSkipVerify, | ||
NextProtos: tlsConfig.NextProtos, | ||
} | ||
|
||
// In the case of regular http.Transport it can handle h2 upgrade with the | ||
// regular tls.Conn only so remove h2 from NextProtos in this case. | ||
// | ||
// TODO(ameshkov): remove this when transport is reworked to dial first. | ||
if slices.Contains(tlsConfig.NextProtos, "http/1.1") && | ||
slices.Contains(tlsConfig.NextProtos, "h2") { | ||
conf.NextProtos = []string{"http/1.1"} | ||
} | ||
|
||
if len(echConfigs) > 0 { | ||
conf.ECHEnabled = true | ||
conf.ClientECHConfigs = echConfigs | ||
} | ||
|
||
if postQuantum { | ||
conf.CurvePreferences = []ctls.CurveID{ | ||
ctls.X25519Kyber768Draft00, | ||
ctls.X25519, | ||
ctls.CurveP256, | ||
} | ||
} | ||
|
||
c := ctls.Client(conn, conf) | ||
err = c.Handshake() | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
out.Debug("TLS connection has been established successfully") | ||
|
||
return &connWrapper{ | ||
baseConn: c, | ||
}, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
// TODO(ameshkov): tests depend on third-party services, rework this. | ||
package cfcrypto_test | ||
|
||
import ( | ||
"context" | ||
"crypto/tls" | ||
"fmt" | ||
"io" | ||
"net" | ||
"net/http" | ||
"testing" | ||
|
||
"github.com/ameshkov/gocurl/internal/client/cfcrypto" | ||
"github.com/ameshkov/gocurl/internal/config" | ||
"github.com/ameshkov/gocurl/internal/output" | ||
"github.com/ameshkov/gocurl/internal/resolve" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestHandshake_encryptedClientHello(t *testing.T) { | ||
const relayDomain = "crypto.cloudflare.com" | ||
const privateDomain = "cloudflare.com" | ||
const path = "cdn-cgi/trace" | ||
|
||
out, err := output.NewOutput("", false) | ||
require.NoError(t, err) | ||
|
||
cfg := &config.Config{ECH: true} | ||
|
||
r, err := resolve.NewResolver(cfg, out) | ||
require.NoError(t, err) | ||
|
||
echConfigs, err := r.LookupECHConfigs("crypto.cloudflare.com") | ||
require.NoError(t, err) | ||
require.NotEmpty(t, echConfigs) | ||
|
||
// Make sure that the resolved ECH configs will be used. | ||
cfg.ECHConfigs = echConfigs | ||
|
||
conn, err := net.Dial("tcp", fmt.Sprintf("%s:443", relayDomain)) | ||
require.NoError(t, err) | ||
|
||
tlsConf := &tls.Config{ | ||
ServerName: privateDomain, | ||
NextProtos: []string{"http/1.1"}, | ||
} | ||
|
||
tlsConn, err := cfcrypto.Handshake(conn, tlsConf, r, cfg, out) | ||
require.NoError(t, err) | ||
|
||
u := fmt.Sprintf("https://%s/%s", privateDomain, path) | ||
req, err := http.NewRequest(http.MethodGet, u, nil) | ||
require.NoError(t, err) | ||
|
||
transport := &http.Transport{ | ||
DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) { | ||
return tlsConn, nil | ||
}, | ||
} | ||
resp, err := transport.RoundTrip(req) | ||
require.NoError(t, err) | ||
defer func(Body io.ReadCloser) { | ||
_ = Body.Close() | ||
}(resp.Body) | ||
|
||
body, err := io.ReadAll(resp.Body) | ||
require.NoError(t, err) | ||
require.NotEmpty(t, body) | ||
|
||
bodyStr := string(body) | ||
require.Contains(t, bodyStr, "sni=encrypted") | ||
} | ||
|
||
func TestHandshake_postQuantum(t *testing.T) { | ||
const domainName = "pq.cloudflareresearch.com" | ||
|
||
out, err := output.NewOutput("", false) | ||
require.NoError(t, err) | ||
|
||
conn, err := net.Dial("tcp", fmt.Sprintf("%s:443", domainName)) | ||
require.NoError(t, err) | ||
|
||
tlsConf := &tls.Config{ | ||
ServerName: "pq.cloudflareresearch.com", | ||
NextProtos: []string{"http/1.1"}, | ||
} | ||
|
||
cfg := &config.Config{ | ||
Experiments: map[config.Experiment]string{ | ||
config.ExpPostQuantum: "", | ||
}, | ||
} | ||
|
||
tlsConn, err := cfcrypto.Handshake(conn, tlsConf, nil, cfg, out) | ||
require.NoError(t, err) | ||
|
||
u := fmt.Sprintf("https://%s/", domainName) | ||
req, err := http.NewRequest(http.MethodGet, u, nil) | ||
require.NoError(t, err) | ||
|
||
transport := &http.Transport{ | ||
DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) { | ||
return tlsConn, nil | ||
}, | ||
} | ||
|
||
resp, err := transport.RoundTrip(req) | ||
require.NoError(t, err) | ||
defer func(Body io.ReadCloser) { | ||
_ = Body.Close() | ||
}(resp.Body) | ||
|
||
body, err := io.ReadAll(resp.Body) | ||
require.NoError(t, err) | ||
require.NotEmpty(t, body) | ||
|
||
bodyStr := string(body) | ||
require.Contains(t, bodyStr, "You are using <em>X25519Kyber768Draft00</em>") | ||
} |
46 changes: 1 addition & 45 deletions
46
internal/client/ech/ech.go → internal/client/cfcrypto/connwrapper.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.