From 24ca95ab10694a36512c0c14f6fee9a60cd929ee Mon Sep 17 00:00:00 2001 From: Turtle Date: Sun, 23 Jun 2019 00:05:20 -0400 Subject: [PATCH 1/2] lnd_test: remove TLSAutoRegeneration test --- lntest/itest/lnd_test.go | 139 --------------------------------------- 1 file changed, 139 deletions(-) diff --git a/lntest/itest/lnd_test.go b/lntest/itest/lnd_test.go index daacd334..123975b7 100644 --- a/lntest/itest/lnd_test.go +++ b/lntest/itest/lnd_test.go @@ -4,21 +4,13 @@ package itest import ( "bytes" - "crypto/ecdsa" - "crypto/elliptic" "crypto/rand" "crypto/sha256" - "crypto/tls" - "crypto/x509" - "crypto/x509/pkix" "encoding/hex" - "encoding/pem" "fmt" "io" "io/ioutil" "math" - "math/big" - "net" "os" "path/filepath" "reflect" @@ -13880,133 +13872,6 @@ func testHoldInvoicePersistence(net *lntest.NetworkHarness, t *harnessTest) { } } -// testTLSAutoRegeneration creates an expired TLS certificate, to test that a -// new TLS certificate pair is regenerated when the old pair expires. This is -// necessary because the pair expires after a little over a year. -func testTLSAutoRegeneration(lnNet *lntest.NetworkHarness, t *harnessTest) { - certPath := lnNet.Alice.TLSCertStr() - keyPath := lnNet.Alice.TLSKeyStr() - - // Create an expired certificate. - expiredCert := genExpiredCertPair( - t, lnNet, certPath, keyPath, - ) - - // Restart the node to test that the cert is automatically regenerated. - lnNet.RestartNode(lnNet.Alice, nil, nil) - - // Grab the newly generated certificate. - newCertData, err := tls.LoadX509KeyPair(certPath, keyPath) - if err != nil { - t.Fatalf("couldn't grab new certificate") - } - - newCert, err := x509.ParseCertificate(newCertData.Certificate[0]) - if err != nil { - t.Fatalf("couldn't parse new certificate") - } - - // Check that the expired certificate was successfully deleted and - // replaced with a new one. - if !newCert.NotAfter.After(expiredCert.NotAfter) { - t.Fatalf("New certificate expiration is too old") - } -} - -// genExpiredCertPair generates an expired key/cert pair to the paths -// provided to test that expired certificates are being regenerated correctly. -func genExpiredCertPair(t *harnessTest, lnNet *lntest.NetworkHarness, certPath, - keyPath string) *x509.Certificate { - // Max serial number. - serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) - - // Generate a serial number that's below the serialNumberLimit. - serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) - if err != nil { - t.Fatalf("failed to generate serial number: %s", err) - } - - host := "lightning" - - // Create a simple ip address for the fake certificate. - ipAddresses := []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("::1")} - - dnsNames := []string{host, "unix", "unixpacket"} - - // Construct the certificate template. - template := x509.Certificate{ - SerialNumber: serialNumber, - Subject: pkix.Name{ - Organization: []string{"lnd autogenerated cert"}, - CommonName: host, - }, - NotBefore: time.Now().Add(-time.Hour * 24), - NotAfter: time.Now(), - - KeyUsage: x509.KeyUsageKeyEncipherment | - x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, - IsCA: true, // so can sign self. - BasicConstraintsValid: true, - - DNSNames: dnsNames, - IPAddresses: ipAddresses, - } - - // Generate a private key for the certificate. - priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - if err != nil { - t.Fatalf("failed to generate a private key") - } - - derBytes, err := x509.CreateCertificate( - rand.Reader, &template, &template, &priv.PublicKey, priv) - if err != nil { - t.Fatalf("failed to create certificate: %v", err) - } - - expiredCert, err := x509.ParseCertificate(derBytes) - if err != nil { - t.Fatalf("failed to parse certificate: %v", err) - } - - certBuf := bytes.Buffer{} - err = pem.Encode( - &certBuf, &pem.Block{ - Type: "CERTIFICATE", - Bytes: derBytes, - }, - ) - if err != nil { - t.Fatalf("failed to encode certificate: %v", err) - } - - keybytes, err := x509.MarshalECPrivateKey(priv) - if err != nil { - t.Fatalf("unable to encode privkey: %v", err) - } - keyBuf := bytes.Buffer{} - err = pem.Encode( - &keyBuf, &pem.Block{ - Type: "EC PRIVATE KEY", - Bytes: keybytes, - }, - ) - if err != nil { - t.Fatalf("failed to encode private key: %v", err) - } - - // Write cert and key files. - if err = ioutil.WriteFile(certPath, certBuf.Bytes(), 0644); err != nil { - t.Fatalf("failed to write cert file: %v", err) - } - if err = ioutil.WriteFile(keyPath, keyBuf.Bytes(), 0600); err != nil { - os.Remove(certPath) - t.Fatalf("failed to write key file: %v", err) - } - - return expiredCert -} - type testCase struct { name string test func(net *lntest.NetworkHarness, t *harnessTest) @@ -14258,10 +14123,6 @@ var testsCases = []*testCase{ name: "cpfp", test: testCPFP, }, - { - name: "automatic certificate regeneration", - test: testTLSAutoRegeneration, - }, } // TestLightningNetworkDaemon performs a series of integration tests amongst a From f958555ce39ea20c6f20f7e1eb90fd4b466f53f7 Mon Sep 17 00:00:00 2001 From: Turtle Date: Sun, 23 Jun 2019 00:07:10 -0400 Subject: [PATCH 2/2] Lnd + server_test: Add unit test for TLS cert autoregeneration --- lnd.go | 43 ++++++++------ server_test.go | 158 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 183 insertions(+), 18 deletions(-) diff --git a/lnd.go b/lnd.go index d1461511..6a02d730 100644 --- a/lnd.go +++ b/lnd.go @@ -181,7 +181,11 @@ func Main() error { ctx, cancel := context.WithCancel(ctx) defer cancel() - tlsCfg, restCreds, restProxyDest, err := getTLSConfig(cfg) + tlsCfg, restCreds, restProxyDest, err := getTLSConfig( + cfg.TLSCertPath, + cfg.TLSKeyPath, + cfg.RPCListeners, + ) if err != nil { return err } @@ -503,18 +507,19 @@ func Main() error { // getTLSConfig returns a TLS configuration for the gRPC server and credentials // and a proxy destination for the REST reverse proxy. -func getTLSConfig(cfg *config) (*tls.Config, *credentials.TransportCredentials, - string, error) { +func getTLSConfig(tlsCertPath string, tlsKeyPath string, + rpcListeners []net.Addr) (*tls.Config, + *credentials.TransportCredentials, string, error) { // Ensure we create TLS key and certificate if they don't exist - if !fileExists(cfg.TLSCertPath) && !fileExists(cfg.TLSKeyPath) { - err := genCertPair(cfg.TLSCertPath, cfg.TLSKeyPath) + if !fileExists(tlsCertPath) && !fileExists(tlsKeyPath) { + err := genCertPair(tlsCertPath, tlsKeyPath) if err != nil { return nil, nil, "", err } } - certData, err := tls.LoadX509KeyPair(cfg.TLSCertPath, cfg.TLSKeyPath) + certData, err := tls.LoadX509KeyPair(tlsCertPath, tlsKeyPath) if err != nil { return nil, nil, "", err } @@ -528,17 +533,17 @@ func getTLSConfig(cfg *config) (*tls.Config, *credentials.TransportCredentials, if time.Now().After(cert.NotAfter) { ltndLog.Info("TLS certificate is expired, generating a new one") - err := os.Remove(cfg.TLSCertPath) + err := os.Remove(tlsCertPath) if err != nil { return nil, nil, "", err } - err = os.Remove(cfg.TLSKeyPath) + err = os.Remove(tlsKeyPath) if err != nil { return nil, nil, "", err } - err = genCertPair(cfg.TLSCertPath, cfg.TLSKeyPath) + err = genCertPair(tlsCertPath, tlsKeyPath) if err != nil { return nil, nil, "", err } @@ -551,12 +556,12 @@ func getTLSConfig(cfg *config) (*tls.Config, *credentials.TransportCredentials, MinVersion: tls.VersionTLS12, } - restCreds, err := credentials.NewClientTLSFromFile(cfg.TLSCertPath, "") + restCreds, err := credentials.NewClientTLSFromFile(tlsCertPath, "") if err != nil { return nil, nil, "", err } - restProxyDest := cfg.RPCListeners[0].String() + restProxyDest := rpcListeners[0].String() switch { case strings.Contains(restProxyDest, "0.0.0.0"): restProxyDest = strings.Replace( @@ -634,11 +639,13 @@ func genCertPair(certFile, keyFile string) error { } } - // Add extra IPs to the slice. - for _, ip := range cfg.TLSExtraIPs { - ipAddr := net.ParseIP(ip) - if ipAddr != nil { - addIP(ipAddr) + if cfg != nil { + // Add extra IPs to the slice. + for _, ip := range cfg.TLSExtraIPs { + ipAddr := net.ParseIP(ip) + if ipAddr != nil { + addIP(ipAddr) + } } } @@ -654,7 +661,9 @@ func genCertPair(certFile, keyFile string) error { if host != "localhost" { dnsNames = append(dnsNames, "localhost") } - dnsNames = append(dnsNames, cfg.TLSExtraDomains...) + if cfg != nil { + dnsNames = append(dnsNames, cfg.TLSExtraDomains...) + } // Also add fake hostnames for unix sockets, otherwise hostname // verification will fail in the client. diff --git a/server_test.go b/server_test.go index ad2c0413..e7375117 100644 --- a/server_test.go +++ b/server_test.go @@ -2,7 +2,22 @@ package lnd -import "testing" +import ( + "bytes" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "io/ioutil" + "math/big" + "net" + "os" + "testing" + "time" +) func TestParseHexColor(t *testing.T) { var colorTestCases = []struct { @@ -41,3 +56,144 @@ func TestParseHexColor(t *testing.T) { } } } + +// TestTLSAutoRegeneration creates an expired TLS certificate, to test that a +// new TLS certificate pair is regenerated when the old pair expires. This is +// necessary because the pair expires after a little over a year. +func TestTLSAutoRegeneration(t *testing.T) { + tempDirPath, err := ioutil.TempDir("", ".testLnd") + if err != nil { + t.Fatalf("couldn't create temporary cert directory") + } + defer os.RemoveAll(tempDirPath) + + certPath := tempDirPath + "/tls.cert" + keyPath := tempDirPath + "/tls.key" + + certDerBytes, keyBytes := genExpiredCertPair(t, tempDirPath) + + expiredCert, err := x509.ParseCertificate(certDerBytes) + if err != nil { + t.Fatalf("failed to parse certificate: %v", err) + } + + certBuf := bytes.Buffer{} + err = pem.Encode( + &certBuf, &pem.Block{ + Type: "CERTIFICATE", + Bytes: certDerBytes, + }, + ) + if err != nil { + t.Fatalf("failed to encode certificate: %v", err) + } + + keyBuf := bytes.Buffer{} + err = pem.Encode( + &keyBuf, &pem.Block{ + Type: "EC PRIVATE KEY", + Bytes: keyBytes, + }, + ) + if err != nil { + t.Fatalf("failed to encode private key: %v", err) + } + + // Write cert and key files. + err = ioutil.WriteFile(tempDirPath+"/tls.cert", certBuf.Bytes(), 0644) + if err != nil { + t.Fatalf("failed to write cert file: %v", err) + } + err = ioutil.WriteFile(tempDirPath+"/tls.key", keyBuf.Bytes(), 0600) + if err != nil { + os.Remove(tempDirPath + "tls.cert") + t.Fatalf("failed to write key file: %v", err) + } + + rpcListener := net.IPAddr{IP: net.ParseIP("127.0.0.1"), Zone: ""} + rpcListeners := make([]net.Addr, 0) + rpcListeners = append(rpcListeners, &rpcListener) + + // Now let's run getTLSConfig. If it works properly, it should delete + // the cert and create a new one. + _, _, _, err = getTLSConfig(certPath, keyPath, rpcListeners) + if err != nil { + t.Fatalf("couldn't retrieve TLS config") + } + + // Grab the certificate to test that getTLSConfig did its job correctly + // and generated a new cert. + newCertData, err := tls.LoadX509KeyPair(certPath, keyPath) + if err != nil { + t.Fatalf("couldn't grab new certificate") + } + + newCert, err := x509.ParseCertificate(newCertData.Certificate[0]) + if err != nil { + t.Fatalf("couldn't parse new certificate") + } + + // Check that the expired certificate was successfully deleted and + // replaced with a new one. + if !newCert.NotAfter.After(expiredCert.NotAfter) { + t.Fatalf("New certificate expiration is too old") + } +} + +// genExpiredCertPair generates an expired key/cert pair to test that expired +// certificates are being regenerated correctly. +func genExpiredCertPair(t *testing.T, certDirPath string) ([]byte, []byte) { + // Max serial number. + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + + // Generate a serial number that's below the serialNumberLimit. + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + t.Fatalf("failed to generate serial number: %s", err) + } + + host := "lightning" + + // Create a simple ip address for the fake certificate. + ipAddresses := []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("::1")} + + dnsNames := []string{host, "unix", "unixpacket"} + + // Construct the certificate template. + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + Organization: []string{"lnd autogenerated cert"}, + CommonName: host, + }, + NotBefore: time.Now().Add(-time.Hour * 24), + NotAfter: time.Now(), + + KeyUsage: x509.KeyUsageKeyEncipherment | + x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + IsCA: true, // so can sign self. + BasicConstraintsValid: true, + + DNSNames: dnsNames, + IPAddresses: ipAddresses, + } + + // Generate a private key for the certificate. + priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatalf("failed to generate a private key") + } + + certDerBytes, err := x509.CreateCertificate( + rand.Reader, &template, &template, &priv.PublicKey, priv) + if err != nil { + t.Fatalf("failed to create certificate: %v", err) + } + + keyBytes, err := x509.MarshalECPrivateKey(priv) + if err != nil { + t.Fatalf("unable to encode privkey: %v", err) + } + + return certDerBytes, keyBytes +}