mirror of
https://github.com/octoleo/syncthing.git
synced 2025-01-31 02:48:27 +00:00
This adds a certificate lifetime parameter to our certificate generation and hard codes it to twenty years in some uninteresting places. In the main binary there are a couple of constants but it results in twenty years for the device certificate and 820 days for the HTTPS one. 820 is less than the 825 maximum Apple allows nowadays. This also means we must be prepared for certificates to expire, so I add some handling for that and generate a new certificate when needed. For self signed certificates we regenerate a month ahead of time. For other certificates we leave well enough alone.
This commit is contained in:
parent
1a06ab68eb
commit
4736cccda1
@ -100,7 +100,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, "stdiscosrv")
|
||||
cert, err = tlsutil.NewCertificate(certFile, keyFile, "stdiscosrv", 20*365)
|
||||
if err != nil {
|
||||
log.Fatalln("Failed to generate X509 key pair:", err)
|
||||
}
|
||||
|
@ -633,7 +633,7 @@ func createTestCertificate() tls.Certificate {
|
||||
}
|
||||
|
||||
certFile, keyFile := filepath.Join(tmpDir, "cert.pem"), filepath.Join(tmpDir, "key.pem")
|
||||
cert, err := tlsutil.NewCertificate(certFile, keyFile, "relaypoolsrv")
|
||||
cert, err := tlsutil.NewCertificate(certFile, keyFile, "relaypoolsrv", 20*365)
|
||||
if err != nil {
|
||||
log.Fatalln("Failed to create test X509 key pair:", err)
|
||||
}
|
||||
|
@ -155,7 +155,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")
|
||||
cert, err = tlsutil.NewCertificate(certFile, keyFile, "strelaysrv", 20*365)
|
||||
if err != nil {
|
||||
log.Fatalln("Failed to generate X509 key pair:", err)
|
||||
}
|
||||
|
@ -53,11 +53,12 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
bepProtocolName = "bep/1.0"
|
||||
tlsDefaultCommonName = "syncthing"
|
||||
maxSystemErrors = 5
|
||||
initialSystemLog = 10
|
||||
maxSystemLog = 250
|
||||
bepProtocolName = "bep/1.0"
|
||||
tlsDefaultCommonName = "syncthing"
|
||||
maxSystemErrors = 5
|
||||
initialSystemLog = 10
|
||||
maxSystemLog = 250
|
||||
deviceCertLifetimeDays = 20 * 365
|
||||
)
|
||||
|
||||
const (
|
||||
@ -424,7 +425,7 @@ func generate(generateDir string) error {
|
||||
if err == nil {
|
||||
l.Warnln("Key exists; will not overwrite.")
|
||||
} else {
|
||||
cert, err = tlsutil.NewCertificate(certFile, keyFile, tlsDefaultCommonName)
|
||||
cert, err = tlsutil.NewCertificate(certFile, keyFile, tlsDefaultCommonName, deviceCertLifetimeDays)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "create certificate")
|
||||
}
|
||||
|
@ -9,7 +9,9 @@ package api
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@ -56,10 +58,11 @@ import (
|
||||
var bcryptExpr = regexp.MustCompile(`^\$2[aby]\$\d+\$.{50,}`)
|
||||
|
||||
const (
|
||||
DefaultEventMask = events.AllEvents &^ events.LocalChangeDetected &^ events.RemoteChangeDetected
|
||||
DiskEventMask = events.LocalChangeDetected | events.RemoteChangeDetected
|
||||
EventSubBufferSize = 1000
|
||||
defaultEventTimeout = time.Minute
|
||||
DefaultEventMask = events.AllEvents &^ events.LocalChangeDetected &^ events.RemoteChangeDetected
|
||||
DiskEventMask = events.LocalChangeDetected | events.RemoteChangeDetected
|
||||
EventSubBufferSize = 1000
|
||||
defaultEventTimeout = time.Minute
|
||||
httpsCertLifetimeDays = 820
|
||||
)
|
||||
|
||||
type service struct {
|
||||
@ -146,6 +149,12 @@ func (s *service) getListener(guiCfg config.GUIConfiguration) (net.Listener, err
|
||||
httpsCertFile := locations.Get(locations.HTTPSCertFile)
|
||||
httpsKeyFile := locations.Get(locations.HTTPSKeyFile)
|
||||
cert, err := tls.LoadX509KeyPair(httpsCertFile, httpsKeyFile)
|
||||
|
||||
// If the certificate has expired or will expire in the next month, fail
|
||||
// it and generate a new one.
|
||||
if err == nil {
|
||||
err = checkExpiry(cert)
|
||||
}
|
||||
if err != nil {
|
||||
l.Infoln("Loading HTTPS certificate:", err)
|
||||
l.Infoln("Creating new HTTPS certificate")
|
||||
@ -158,7 +167,7 @@ func (s *service) getListener(guiCfg config.GUIConfiguration) (net.Listener, err
|
||||
name = s.tlsDefaultCommonName
|
||||
}
|
||||
|
||||
cert, err = tlsutil.NewCertificate(httpsCertFile, httpsKeyFile, name)
|
||||
cert, err = tlsutil.NewCertificate(httpsCertFile, httpsKeyFile, name, httpsCertLifetimeDays)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -1672,3 +1681,45 @@ func addressIsLocalhost(addr string) bool {
|
||||
return ip.IsLoopback()
|
||||
}
|
||||
}
|
||||
|
||||
func checkExpiry(cert tls.Certificate) error {
|
||||
leaf := cert.Leaf
|
||||
if leaf == nil {
|
||||
// Leaf can be nil or not, depending on how parsed the certificate
|
||||
// was when we got it.
|
||||
if len(cert.Certificate) < 1 {
|
||||
// can't happen
|
||||
return errors.New("no certificate in certificate")
|
||||
}
|
||||
var err error
|
||||
leaf, err = x509.ParseCertificate(cert.Certificate[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if leaf.Subject.String() != leaf.Issuer.String() ||
|
||||
len(leaf.DNSNames) != 0 || len(leaf.IPAddresses) != 0 {
|
||||
// The certificate is not self signed, or has DNS/IP attributes we don't
|
||||
// add, so we leave it alone.
|
||||
return nil
|
||||
}
|
||||
|
||||
if leaf.NotAfter.Before(time.Now()) {
|
||||
return errors.New("certificate has expired")
|
||||
}
|
||||
if leaf.NotAfter.Before(time.Now().Add(30 * 24 * time.Hour)) {
|
||||
return errors.New("certificate will soon expire")
|
||||
}
|
||||
|
||||
// On macOS, check for certificates issued on or after July 1st, 2019,
|
||||
// with a longer validity time than 825 days.
|
||||
cutoff := time.Date(2019, 7, 1, 0, 0, 0, 0, time.UTC)
|
||||
if runtime.GOOS == "darwin" &&
|
||||
leaf.NotBefore.After(cutoff) &&
|
||||
leaf.NotAfter.Sub(leaf.NotBefore) > 825*24*time.Hour {
|
||||
return errors.New("certificate incompatible with macOS 10.15 (Catalina)")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ import (
|
||||
"github.com/syncthing/syncthing/lib/model"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/sync"
|
||||
"github.com/syncthing/syncthing/lib/tlsutil"
|
||||
"github.com/syncthing/syncthing/lib/ur"
|
||||
"github.com/thejerf/suture"
|
||||
)
|
||||
@ -1119,6 +1120,44 @@ func TestPrefixMatch(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckExpiry(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "syncthing-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
// Self signed certificates expiring in less than a month are errored so we
|
||||
// can regenerate in time.
|
||||
crt, err := tlsutil.NewCertificate(filepath.Join(dir, "crt"), filepath.Join(dir, "key"), "foo.example.com", 29)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := checkExpiry(crt); err == nil {
|
||||
t.Error("expected expiry error")
|
||||
}
|
||||
|
||||
// Certificates with at least 31 days of life left are fine.
|
||||
crt, err = tlsutil.NewCertificate(filepath.Join(dir, "crt"), filepath.Join(dir, "key"), "foo.example.com", 31)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := checkExpiry(crt); err != nil {
|
||||
t.Error("expected no error:", err)
|
||||
}
|
||||
|
||||
if runtime.GOOS == "darwin" {
|
||||
// Certificates with too long an expiry time are not allowed on macOS
|
||||
crt, err = tlsutil.NewCertificate(filepath.Join(dir, "crt"), filepath.Join(dir, "key"), "foo.example.com", 1000)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := checkExpiry(crt); err == nil {
|
||||
t.Error("expected expiry error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func equalStrings(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
|
@ -112,7 +112,7 @@ func TestGlobalOverHTTPS(t *testing.T) {
|
||||
}
|
||||
|
||||
// Generate a server certificate.
|
||||
cert, err := tlsutil.NewCertificate(dir+"/cert.pem", dir+"/key.pem", "syncthing")
|
||||
cert, err := tlsutil.NewCertificate(dir+"/cert.pem", dir+"/key.pem", "syncthing", 30)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -177,7 +177,7 @@ func TestGlobalAnnounce(t *testing.T) {
|
||||
}
|
||||
|
||||
// Generate a server certificate.
|
||||
cert, err := tlsutil.NewCertificate(dir+"/cert.pem", dir+"/key.pem", "syncthing")
|
||||
cert, err := tlsutil.NewCertificate(dir+"/cert.pem", dir+"/key.pem", "syncthing", 30)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -37,11 +37,12 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
bepProtocolName = "bep/1.0"
|
||||
tlsDefaultCommonName = "syncthing"
|
||||
maxSystemErrors = 5
|
||||
initialSystemLog = 10
|
||||
maxSystemLog = 250
|
||||
bepProtocolName = "bep/1.0"
|
||||
tlsDefaultCommonName = "syncthing"
|
||||
maxSystemErrors = 5
|
||||
initialSystemLog = 10
|
||||
maxSystemLog = 250
|
||||
deviceCertLifetimeDays = 20 * 365
|
||||
)
|
||||
|
||||
type ExitStatus int
|
||||
|
@ -35,6 +35,7 @@ func LoadOrGenerateCertificate(certFile, keyFile string) (tls.Certificate, error
|
||||
locations.Get(locations.CertFile),
|
||||
locations.Get(locations.KeyFile),
|
||||
tlsDefaultCommonName,
|
||||
deviceCertLifetimeDays,
|
||||
)
|
||||
}
|
||||
return cert, nil
|
||||
|
@ -95,15 +95,17 @@ func SecureDefault() *tls.Config {
|
||||
}
|
||||
|
||||
// NewCertificate generates and returns a new TLS certificate.
|
||||
func NewCertificate(certFile, keyFile, commonName string) (tls.Certificate, error) {
|
||||
func NewCertificate(certFile, keyFile, commonName string, lifetimeDays int) (tls.Certificate, error) {
|
||||
priv, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, fmt.Errorf("generate key: %s", err)
|
||||
}
|
||||
|
||||
notBefore := time.Now()
|
||||
notAfter := time.Date(2049, 12, 31, 23, 59, 59, 0, time.UTC)
|
||||
notBefore := time.Now().Truncate(24 * time.Hour)
|
||||
notAfter := notBefore.Add(time.Duration(lifetimeDays*24) * time.Hour)
|
||||
|
||||
// NOTE: update checkExpiry() appropriately if you add or change attributes
|
||||
// in here, especially DNSNames or IPAddresses.
|
||||
template := x509.Certificate{
|
||||
SerialNumber: new(big.Int).SetInt64(rand.Int63()),
|
||||
Subject: pkix.Name{
|
||||
|
Loading…
x
Reference in New Issue
Block a user