浏览代码

feat: use Ed25519 keys for sync connections (#10162)

This updates our key generation to use Ed25519 keys/certificates for
sync connections. Certificates for browser use remain ECDSA for wider
compatibility.

Ed25519 is more modern and has fewer concerns for the future than the
ECDSA curves we used previously. It is supported from Go 1.13 and
forwards, which is Syncthing 1.3.0 (October 2019).
Jakob Borg 4 月之前
父节点
当前提交
8afc9855f2

+ 1 - 1
cmd/infra/strelaypoolsrv/main.go

@@ -620,7 +620,7 @@ func createTestCertificate() tls.Certificate {
 	}
 	}
 
 
 	certFile, keyFile := filepath.Join(tmpDir, "cert.pem"), filepath.Join(tmpDir, "key.pem")
 	certFile, keyFile := filepath.Join(tmpDir, "cert.pem"), filepath.Join(tmpDir, "key.pem")
-	cert, err := tlsutil.NewCertificate(certFile, keyFile, "relaypoolsrv", 20*365)
+	cert, err := tlsutil.NewCertificate(certFile, keyFile, "relaypoolsrv", 20*365, false)
 	if err != nil {
 	if err != nil {
 		log.Fatalln("Failed to create test X509 key pair:", err)
 		log.Fatalln("Failed to create test X509 key pair:", err)
 	}
 	}

+ 1 - 1
cmd/stdiscosrv/apisrv_test.go

@@ -115,7 +115,7 @@ func BenchmarkAPIRequests(b *testing.B) {
 	srv := httptest.NewServer(http.HandlerFunc(api.handler))
 	srv := httptest.NewServer(http.HandlerFunc(api.handler))
 
 
 	kf := b.TempDir() + "/cert"
 	kf := b.TempDir() + "/cert"
-	crt, err := tlsutil.NewCertificate(kf+".crt", kf+".key", "localhost", 7)
+	crt, err := tlsutil.NewCertificate(kf+".crt", kf+".key", "localhost", 7, true)
 	if err != nil {
 	if err != nil {
 		b.Fatal(err)
 		b.Fatal(err)
 	}
 	}

+ 1 - 1
cmd/stdiscosrv/main.go

@@ -107,7 +107,7 @@ func main() {
 		cert, err = tls.LoadX509KeyPair(cli.Cert, cli.Key)
 		cert, err = tls.LoadX509KeyPair(cli.Cert, cli.Key)
 		if os.IsNotExist(err) {
 		if os.IsNotExist(err) {
 			log.Println("Failed to load keypair. Generating one, this might take a while...")
 			log.Println("Failed to load keypair. Generating one, this might take a while...")
-			cert, err = tlsutil.NewCertificate(cli.Cert, cli.Key, "stdiscosrv", 20*365)
+			cert, err = tlsutil.NewCertificate(cli.Cert, cli.Key, "stdiscosrv", 20*365, false)
 			if err != nil {
 			if err != nil {
 				log.Fatalln("Failed to generate X509 key pair:", err)
 				log.Fatalln("Failed to generate X509 key pair:", err)
 			}
 			}

+ 1 - 1
cmd/strelaysrv/main.go

@@ -157,7 +157,7 @@ func main() {
 	cert, err := tls.LoadX509KeyPair(certFile, keyFile)
 	cert, err := tls.LoadX509KeyPair(certFile, keyFile)
 	if err != nil {
 	if err != nil {
 		log.Println("Failed to load keypair. Generating one, this might take a while...")
 		log.Println("Failed to load keypair. Generating one, this might take a while...")
-		cert, err = tlsutil.NewCertificate(certFile, keyFile, "strelaysrv", 20*365)
+		cert, err = tlsutil.NewCertificate(certFile, keyFile, "strelaysrv", 20*365, false)
 		if err != nil {
 		if err != nil {
 			log.Fatalln("Failed to generate X509 key pair:", err)
 			log.Fatalln("Failed to generate X509 key pair:", err)
 		}
 		}

+ 1 - 1
lib/api/api.go

@@ -166,7 +166,7 @@ func (s *service) getListener(guiCfg config.GUIConfiguration) (net.Listener, err
 			name = s.tlsDefaultCommonName
 			name = s.tlsDefaultCommonName
 		}
 		}
 
 
-		cert, err = tlsutil.NewCertificate(httpsCertFile, httpsKeyFile, name, httpsCertLifetimeDays)
+		cert, err = tlsutil.NewCertificate(httpsCertFile, httpsKeyFile, name, httpsCertLifetimeDays, true)
 	}
 	}
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err

+ 2 - 2
lib/syncthing/utils.go

@@ -60,8 +60,8 @@ func LoadOrGenerateCertificate(certFile, keyFile string) (tls.Certificate, error
 }
 }
 
 
 func GenerateCertificate(certFile, keyFile string) (tls.Certificate, error) {
 func GenerateCertificate(certFile, keyFile string) (tls.Certificate, error) {
-	l.Infof("Generating ECDSA key and certificate for %s...", tlsDefaultCommonName)
-	return tlsutil.NewCertificate(certFile, keyFile, tlsDefaultCommonName, deviceCertLifetimeDays)
+	l.Infof("Generating key and certificate for %s...", tlsDefaultCommonName)
+	return tlsutil.NewCertificate(certFile, keyFile, tlsDefaultCommonName, deviceCertLifetimeDays, false)
 }
 }
 
 
 func DefaultConfig(path string, myID protocol.DeviceID, evLogger events.Logger, skipPortProbing bool) (config.Wrapper, error) {
 func DefaultConfig(path string, myID protocol.DeviceID, evLogger events.Logger, skipPortProbing bool) (config.Wrapper, error) {

+ 41 - 11
lib/tlsutil/tlsutil.go

@@ -8,6 +8,7 @@ package tlsutil
 
 
 import (
 import (
 	"crypto/ecdsa"
 	"crypto/ecdsa"
+	"crypto/ed25519"
 	"crypto/elliptic"
 	"crypto/elliptic"
 	"crypto/rsa"
 	"crypto/rsa"
 	"crypto/tls"
 	"crypto/tls"
@@ -87,9 +88,28 @@ func SecureDefaultWithTLS12() *tls.Config {
 	}
 	}
 }
 }
 
 
-// generateCertificate generates a PEM formatted key pair and self-signed certificate in memory.
-func generateCertificate(commonName string, lifetimeDays int) (*pem.Block, *pem.Block, error) {
-	priv, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
+// generateCertificate generates a PEM formatted key pair and self-signed
+// certificate in memory. The compatible flag indicates whether we aim for
+// compatibility (browsers) or maximum efficiency/security (sync
+// connections).
+func generateCertificate(commonName string, lifetimeDays int, compatible bool) (*pem.Block, *pem.Block, error) {
+	var pub, priv any
+	var err error
+	var sigAlgo x509.SignatureAlgorithm
+	if compatible {
+		// For browser connections we prefer ECDSA-P256
+		sigAlgo = x509.ECDSAWithSHA256
+		var pk *ecdsa.PrivateKey
+		pk, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+		if err == nil {
+			priv = pk
+			pub = pk.Public()
+		}
+	} else {
+		// For sync connections we use Ed25519
+		sigAlgo = x509.PureEd25519
+		pub, priv, err = ed25519.GenerateKey(rand.Reader)
+	}
 	if err != nil {
 	if err != nil {
 		return nil, nil, fmt.Errorf("generate key: %w", err)
 		return nil, nil, fmt.Errorf("generate key: %w", err)
 	}
 	}
@@ -110,13 +130,13 @@ func generateCertificate(commonName string, lifetimeDays int) (*pem.Block, *pem.
 		DNSNames:              []string{commonName},
 		DNSNames:              []string{commonName},
 		NotBefore:             notBefore,
 		NotBefore:             notBefore,
 		NotAfter:              notAfter,
 		NotAfter:              notAfter,
-		SignatureAlgorithm:    x509.ECDSAWithSHA256,
+		SignatureAlgorithm:    sigAlgo,
 		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
 		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
 		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
 		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
 		BasicConstraintsValid: true,
 		BasicConstraintsValid: true,
 	}
 	}
 
 
-	derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, priv.Public(), priv)
+	derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, pub, priv)
 	if err != nil {
 	if err != nil {
 		return nil, nil, fmt.Errorf("create cert: %w", err)
 		return nil, nil, fmt.Errorf("create cert: %w", err)
 	}
 	}
@@ -130,9 +150,12 @@ func generateCertificate(commonName string, lifetimeDays int) (*pem.Block, *pem.
 	return certBlock, keyBlock, nil
 	return certBlock, keyBlock, nil
 }
 }
 
 
-// NewCertificate generates and returns a new TLS certificate, saved to the given PEM files.
-func NewCertificate(certFile, keyFile string, commonName string, lifetimeDays int) (tls.Certificate, error) {
-	certBlock, keyBlock, err := generateCertificate(commonName, lifetimeDays)
+// NewCertificate generates and returns a new TLS certificate, saved to the
+// given PEM files. The compatible flag indicates whether we aim for
+// compatibility (browsers) or maximum efficiency/security (sync
+// connections).
+func NewCertificate(certFile, keyFile string, commonName string, lifetimeDays int, compatible bool) (tls.Certificate, error) {
+	certBlock, keyBlock, err := generateCertificate(commonName, lifetimeDays, compatible)
 	if err != nil {
 	if err != nil {
 		return tls.Certificate{}, err
 		return tls.Certificate{}, err
 	}
 	}
@@ -162,9 +185,10 @@ func NewCertificate(certFile, keyFile string, commonName string, lifetimeDays in
 	return tls.X509KeyPair(pem.EncodeToMemory(certBlock), pem.EncodeToMemory(keyBlock))
 	return tls.X509KeyPair(pem.EncodeToMemory(certBlock), pem.EncodeToMemory(keyBlock))
 }
 }
 
 
-// NewCertificateInMemory generates and returns a new TLS certificate, kept only in memory.
+// NewCertificateInMemory generates and returns a new TLS certificate, kept
+// only in memory.
 func NewCertificateInMemory(commonName string, lifetimeDays int) (tls.Certificate, error) {
 func NewCertificateInMemory(commonName string, lifetimeDays int) (tls.Certificate, error) {
-	certBlock, keyBlock, err := generateCertificate(commonName, lifetimeDays)
+	certBlock, keyBlock, err := generateCertificate(commonName, lifetimeDays, false)
 	if err != nil {
 	if err != nil {
 		return tls.Certificate{}, err
 		return tls.Certificate{}, err
 	}
 	}
@@ -246,7 +270,13 @@ func pemBlockForKey(priv interface{}) (*pem.Block, error) {
 			return nil, err
 			return nil, err
 		}
 		}
 		return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}, nil
 		return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}, nil
+	case ed25519.PrivateKey:
+		bs, err := x509.MarshalPKCS8PrivateKey(k)
+		if err != nil {
+			return nil, err
+		}
+		return &pem.Block{Type: "PRIVATE KEY", Bytes: bs}, nil
 	default:
 	default:
-		return nil, errors.New("unknown key type")
+		return nil, fmt.Errorf("unknown key type: %T", priv)
 	}
 	}
 }
 }