diff --git a/cmd/infra/strelaypoolsrv/main.go b/cmd/infra/strelaypoolsrv/main.go index a2e5f65ef..ee982633f 100644 --- a/cmd/infra/strelaypoolsrv/main.go +++ b/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") - cert, err := tlsutil.NewCertificate(certFile, keyFile, "relaypoolsrv", 20*365) + cert, err := tlsutil.NewCertificate(certFile, keyFile, "relaypoolsrv", 20*365, false) if err != nil { log.Fatalln("Failed to create test X509 key pair:", err) } diff --git a/cmd/stdiscosrv/apisrv_test.go b/cmd/stdiscosrv/apisrv_test.go index 07dfa2f17..99f788763 100644 --- a/cmd/stdiscosrv/apisrv_test.go +++ b/cmd/stdiscosrv/apisrv_test.go @@ -115,7 +115,7 @@ func BenchmarkAPIRequests(b *testing.B) { srv := httptest.NewServer(http.HandlerFunc(api.handler)) 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 { b.Fatal(err) } diff --git a/cmd/stdiscosrv/main.go b/cmd/stdiscosrv/main.go index a870651d1..c704f4697 100644 --- a/cmd/stdiscosrv/main.go +++ b/cmd/stdiscosrv/main.go @@ -107,7 +107,7 @@ func main() { cert, err = tls.LoadX509KeyPair(cli.Cert, cli.Key) if os.IsNotExist(err) { 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 { log.Fatalln("Failed to generate X509 key pair:", err) } diff --git a/cmd/strelaysrv/main.go b/cmd/strelaysrv/main.go index c1d264081..dca94b703 100644 --- a/cmd/strelaysrv/main.go +++ b/cmd/strelaysrv/main.go @@ -157,7 +157,7 @@ func main() { cert, err := tls.LoadX509KeyPair(certFile, keyFile) if err != nil { 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 { log.Fatalln("Failed to generate X509 key pair:", err) } diff --git a/lib/api/api.go b/lib/api/api.go index c7952d6af..4cb7a97ed 100644 --- a/lib/api/api.go +++ b/lib/api/api.go @@ -166,7 +166,7 @@ func (s *service) getListener(guiCfg config.GUIConfiguration) (net.Listener, err name = s.tlsDefaultCommonName } - cert, err = tlsutil.NewCertificate(httpsCertFile, httpsKeyFile, name, httpsCertLifetimeDays) + cert, err = tlsutil.NewCertificate(httpsCertFile, httpsKeyFile, name, httpsCertLifetimeDays, true) } if err != nil { return nil, err diff --git a/lib/syncthing/utils.go b/lib/syncthing/utils.go index 79d2cd6b3..79ffd8c46 100644 --- a/lib/syncthing/utils.go +++ b/lib/syncthing/utils.go @@ -60,8 +60,8 @@ func LoadOrGenerateCertificate(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) { diff --git a/lib/tlsutil/tlsutil.go b/lib/tlsutil/tlsutil.go index a105c9e87..e1b9b7e86 100644 --- a/lib/tlsutil/tlsutil.go +++ b/lib/tlsutil/tlsutil.go @@ -8,6 +8,7 @@ package tlsutil import ( "crypto/ecdsa" + "crypto/ed25519" "crypto/elliptic" "crypto/rsa" "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 { 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}, NotBefore: notBefore, NotAfter: notAfter, - SignatureAlgorithm: x509.ECDSAWithSHA256, + SignatureAlgorithm: sigAlgo, KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, 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 { 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 } -// 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 { 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)) } -// 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) { - certBlock, keyBlock, err := generateCertificate(commonName, lifetimeDays) + certBlock, keyBlock, err := generateCertificate(commonName, lifetimeDays, false) if err != nil { return tls.Certificate{}, err } @@ -246,7 +270,13 @@ func pemBlockForKey(priv interface{}) (*pem.Block, error) { return nil, err } 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: - return nil, errors.New("unknown key type") + return nil, fmt.Errorf("unknown key type: %T", priv) } }