diff --git a/docs/release-notes/release-notes-0.21.0.md b/docs/release-notes/release-notes-0.21.0.md index 04b4290e2..d15df4114 100644 --- a/docs/release-notes/release-notes-0.21.0.md +++ b/docs/release-notes/release-notes-0.21.0.md @@ -26,6 +26,11 @@ sub-server is still starting. This allows clients to reliably detect the transient condition and retry without brittle string matching. +- [Fixed an issue](https://github.com/lightningnetwork/lnd/pull/10399) where the + TLS manager would fail to start if only one of the TLS pair files (certificate + or key) existed. The manager now correctly regenerates both files when either + is missing, preventing "file not found" errors on startup. + # New Features - Basic Support for [onion messaging forwarding](https://github.com/lightningnetwork/lnd/pull/9868) @@ -77,4 +82,5 @@ * Boris Nagaev * Elle Mouton +* Mohamed Awnallah * Nishant Bansal diff --git a/tls_manager.go b/tls_manager.go index 076cf44bc..242fd378b 100644 --- a/tls_manager.go +++ b/tls_manager.go @@ -208,8 +208,8 @@ func (t *TLSManager) generateOrRenewCert() (*tls.Config, error) { // is already written to disk, this function overwrites the plaintext key with // the encrypted form. func (t *TLSManager) generateCertPair(keyRing keychain.SecretKeyRing) error { - // Ensure we create TLS key and certificate if they don't exist. - if lnrpc.FileExists(t.cfg.TLSCertPath) || + // Ensure we create TLS key and certificate if they don't both exist. + if lnrpc.FileExists(t.cfg.TLSCertPath) && lnrpc.FileExists(t.cfg.TLSKeyPath) { // Handle discrepencies related to the TLSEncryptKey setting. diff --git a/tls_manager_test.go b/tls_manager_test.go index 42f010411..541b123c4 100644 --- a/tls_manager_test.go +++ b/tls_manager_test.go @@ -369,3 +369,92 @@ func newTestDirectory(t *testing.T) (string, string, string) { return tempDir, certPath, keyPath } + +// TestGenerateCertPairWithPartialFiles tests that generateCertPair regenerates +// a cert/key pair when only one file exists. +func TestGenerateCertPairWithPartialFiles(t *testing.T) { + t.Parallel() + + keyRing := &mock.SecretKeyRing{ + RootKey: privKey, + } + + testCases := []struct { + name string + setup func(t *testing.T, certPath, keyPath string) + }{ + { + name: "only key exists", + setup: func(t *testing.T, certPath, keyPath string) { + // Create only a key file. It simulates leftover + // from previous run. + _, keyBytes := genCertPair(t, false) + keyBuf := &bytes.Buffer{} + err := pem.Encode( + keyBuf, &pem.Block{ + Type: "EC PRIVATE KEY", + Bytes: keyBytes, + }, + ) + require.NoError(t, err) + + err = os.WriteFile( + keyPath, keyBuf.Bytes(), 0600, + ) + require.NoError(t, err) + }, + }, + { + name: "only cert exists", + setup: func(t *testing.T, certPath, keyPath string) { + // Create only a cert file. It simulates + // leftover from previous run. + certBytes, _ := genCertPair(t, false) + certBuf := &bytes.Buffer{} + err := pem.Encode( + certBuf, &pem.Block{ + Type: "CERTIFICATE", + Bytes: certBytes, + }, + ) + require.NoError(t, err) + + err = os.WriteFile( + certPath, certBuf.Bytes(), 0644, + ) + require.NoError(t, err) + }, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + tempDir := t.TempDir() + certPath := tempDir + "/tls.cert" + keyPath := tempDir + "/tls.key" + + tc.setup(t, certPath, keyPath) + + cfg := &TLSManagerCfg{ + TLSCertPath: certPath, + TLSKeyPath: keyPath, + TLSCertDuration: testTLSCertDuration, + } + tlsManager := NewTLSManager(cfg) + + err := tlsManager.generateCertPair(keyRing) + require.NoError( + t, err, "should generate new cert pair when %s", + tc.name, + ) + + _, _, err = cert.GetCertBytesFromPath(certPath, keyPath) + require.NoError( + t, err, "should be able to load cert pair", + ) + }) + } +}