Skip to content
Open
32 changes: 31 additions & 1 deletion config/configtls/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,23 @@ By default, TLS is enabled:
the exporter's HTTPs or gRPC connection. See
[grpc.WithInsecure()](https://godoc.org/google.golang.org/grpc#WithInsecure)
for gRPC.
- `curve_preferences` (default = []): specify your curve preferences that will
- `curve_preferences` (default = optimal order based on Go version and build mode): specify your curve preferences that will
be used in an ECDHE handshake, in preference order. Accepted values are:
- X25519MLKEM768 (post-quantum hybrid key exchange, requires Go 1.24+)
- X25519
- P521
- P256
- P384

__Note__: `X25519MLKEM768` provides quantum-resistant security through hybrid key exchange,
combining classical X25519 with post-quantum ML-KEM-768 (Module-Lattice-Based Key-Encapsulation Mechanism).
This is only available when compiling with Go 1.24 or later and is not available in FIPS mode.

When `curve_preferences` is not explicitly configured:
- **Non-FIPS builds (Go 1.24+)**: Automatically uses `[X25519MLKEM768, X25519, P256, P384, P521]`
providing post-quantum security by default with backward compatibility.
- **FIPS builds**: Automatically uses `[P256, P384, P521]` (only NIST P-curves are FIPS-approved).

As a result, the following parameters are also required:

- `cert_file`: Path to the TLS cert to use for TLS required connections. Should
Expand Down Expand Up @@ -117,6 +127,16 @@ exporters:
tls:
insecure: false
insecure_skip_verify: true
otlp/pqc:
endpoint: myserver.local:55690
tls:
ca_file: server.crt
cert_file: client.crt
key_file: client.key
curve_preferences:
- X25519MLKEM768
- X25519
- P256
```

## Server Configuration
Expand Down Expand Up @@ -152,6 +172,16 @@ receivers:
client_ca_file: client.pem
cert_file: server.crt
key_file: server.key
otlp/pqc:
protocols:
grpc:
endpoint: mysite.local:55690
tls:
cert_file: server.crt
key_file: server.key
curve_preferences:
- X25519MLKEM768
- X25519
otlp/notls:
protocols:
grpc:
Expand Down
7 changes: 4 additions & 3 deletions config/configtls/configtls.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,10 +282,11 @@ func (c Config) loadTLSConfig() (*tls.Config, error) {
}

// If no curve preferences were explicitly specified in the configuration, use
// the ones we allow. This helps in particular with FIPS builds where not all curves
// are allowed.
// the default preference order. This provides optimal security by prioritizing
// post-quantum hybrid key exchange (X25519MLKEM768) when available (Go 1.24+),
// while maintaining compatibility with FIPS builds.
if len(curvePreferences) == 0 {
curvePreferences = allowedCurves
curvePreferences = defaultCurvePreferences
}

return &tls.Config{
Expand Down
62 changes: 61 additions & 1 deletion config/configtls/configtls_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -921,7 +921,7 @@ func TestCurvePreferences(t *testing.T) {
testCase{
name: "X25519MLKEM768",
preferences: []string{"X25519MLKEM768"},
expectedCurveIDs: []tls.CurveID{tls.X25519MLKEM768},
expectedCurveIDs: []tls.CurveID{X25519MLKEM768}, // 0x11EC
},
testCase{
name: "X25519",
Expand Down Expand Up @@ -949,6 +949,66 @@ func TestCurvePreferences(t *testing.T) {
}
}

func TestDefaultCurvePreferences(t *testing.T) {
type testCase struct {
name string
isFIPS bool
expectedCurveIDs []tls.CurveID
expectedFirstCurve tls.CurveID
}

// Detect actual build type by checking defaultCurvePreferences length
// FIPS builds have 3 curves, non-FIPS builds have 5 curves
actualIsFIPS := len(defaultCurvePreferences) == 3

tests := []testCase{
{
name: "non-FIPS-default",
isFIPS: false,
expectedCurveIDs: []tls.CurveID{X25519MLKEM768, tls.X25519, tls.CurveP256, tls.CurveP384, tls.CurveP521},
expectedFirstCurve: X25519MLKEM768,
},
{
name: "FIPS-default",
isFIPS: true,
expectedCurveIDs: []tls.CurveID{tls.CurveP256, tls.CurveP384, tls.CurveP521},
expectedFirstCurve: tls.CurveP256,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
// Skip test case if it doesn't match the actual build type
if test.isFIPS != actualIsFIPS {
t.Skipf("Skipping %s test case: actual build is %s", test.name, map[bool]string{true: "FIPS", false: "non-FIPS"}[actualIsFIPS])
}

tlsSetting := ClientConfig{
Config: Config{
// CurvePreferences is intentionally not set
},
}
config, err := tlsSetting.LoadTLSConfig(context.Background())
require.NoError(t, err)
require.NotNil(t, config)

// Verify curve preferences match expected
require.Equal(t, len(test.expectedCurveIDs), len(config.CurvePreferences),
"Expected %d curves, got %d", len(test.expectedCurveIDs), len(config.CurvePreferences))

// Verify order is preserved
for i, expectedCurve := range test.expectedCurveIDs {
require.Equal(t, expectedCurve, config.CurvePreferences[i],
"Expected curve at position %d to be %v, got %v", i, expectedCurve, config.CurvePreferences[i])
}

// Verify first curve
require.Equal(t, test.expectedFirstCurve, config.CurvePreferences[0],
"Expected first curve to be %v, got %v", test.expectedFirstCurve, config.CurvePreferences[0])
})
}
}

func TestServerConfigValidate(t *testing.T) {
tests := []struct {
name string
Expand Down
8 changes: 8 additions & 0 deletions config/configtls/curves_fips.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,11 @@ var tlsCurveTypes = map[string]tls.CurveID{
//"X25519": tls.X25519,
//"X25519MLKEM768": tls.X25519MLKEM768,
}

// defaultCurvePreferences defines the default order of curve preferences for FIPS builds.
// Only NIST P-curves are available in FIPS mode.
var defaultCurvePreferences = []tls.CurveID{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's the same set as tlsCurveTypes right above

tls.CurveP256, // FIPS approved, widely supported
tls.CurveP384, // FIPS approved, higher security
tls.CurveP521, // FIPS approved, highest security
}
19 changes: 18 additions & 1 deletion config/configtls/curves_nofips.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,27 @@ package configtls // import "go.opentelemetry.io/collector/config/configtls"

import "crypto/tls"

// For backward compatability with older versions before Go 1.24.0
// This constant has the same value as tls.X25519MLKEM768 in Go 1.24.0+
// Value: 0x11EC (verified against Go 1.24.0 source code)
const (
X25519MLKEM768 tls.CurveID = 0x11EC
)

var tlsCurveTypes = map[string]tls.CurveID{
"P256": tls.CurveP256,
"P384": tls.CurveP384,
"P521": tls.CurveP521,
"X25519": tls.X25519,
"X25519MLKEM768": tls.X25519MLKEM768,
"X25519MLKEM768": X25519MLKEM768,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you breaking convention?

}

// defaultCurvePreferences defines the default order of curve preferences.
// X25519MLKEM768 is prioritized for post-quantum security when compiled with Go 1.24+.
var defaultCurvePreferences = []tls.CurveID{
X25519MLKEM768, // Post-quantum hybrid key exchange
tls.X25519, // Modern, fast elliptic curve
tls.CurveP256, // Widely supported
tls.CurveP384, // Higher security margin
tls.CurveP521, // Highest security margin
}
16 changes: 16 additions & 0 deletions config/configtls/curves_nofips_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//go:build go1.24
// +build go1.24

package configtls

import (
"crypto/tls"
"testing"
)

func TestX25519MLKEM768Value(t *testing.T) {
if X25519MLKEM768 != tls.X25519MLKEM768 {
t.Errorf("X25519MLKEM768 constant (%v) does not match tls.X25519MLKEM768 (%v)",
X25519MLKEM768, tls.X25519MLKEM768)
}
}