diff --git a/cert/go.mod b/cert/go.mod new file mode 100644 index 000000000..29aa7fe7c --- /dev/null +++ b/cert/go.mod @@ -0,0 +1,3 @@ +module github.com/lightningnetwork/lnd/cert + +go 1.13 diff --git a/cert/selfsigned.go b/cert/selfsigned.go new file mode 100644 index 000000000..e8bd57101 --- /dev/null +++ b/cert/selfsigned.go @@ -0,0 +1,171 @@ +package cert + +import ( + "bytes" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "io/ioutil" + "math/big" + "net" + "os" + "time" +) + +const ( + // DefaultAutogenValidity is the default validity of a self-signed + // certificate. The value corresponds to 14 months + // (14 months * 30 days * 24 hours). + DefaultAutogenValidity = 14 * 30 * 24 * time.Hour +) + +var ( + // End of ASN.1 time. + endOfTime = time.Date(2049, 12, 31, 23, 59, 59, 0, time.UTC) + + // Max serial number. + serialNumberLimit = new(big.Int).Lsh(big.NewInt(1), 128) +) + +// GenCertPair generates a key/cert pair to the paths provided. The +// auto-generated certificates should *not* be used in production for public +// access as they're self-signed and don't necessarily contain all of the +// desired hostnames for the service. For production/public use, consider a +// real PKI. +// +// This function is adapted from https://github.com/btcsuite/btcd and +// https://github.com/btcsuite/btcutil +func GenCertPair(org, certFile, keyFile string, tlsExtraIPs, + tlsExtraDomains []string, certValidity time.Duration) error { + + now := time.Now() + validUntil := now.Add(certValidity) + + // Check that the certificate validity isn't past the ASN.1 end of time. + if validUntil.After(endOfTime) { + validUntil = endOfTime + } + + // Generate a serial number that's below the serialNumberLimit. + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + return fmt.Errorf("failed to generate serial number: %s", err) + } + + // Collect the host's IP addresses, including loopback, in a slice. + ipAddresses := []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("::1")} + + // addIP appends an IP address only if it isn't already in the slice. + addIP := func(ipAddr net.IP) { + for _, ip := range ipAddresses { + if ip.Equal(ipAddr) { + return + } + } + ipAddresses = append(ipAddresses, ipAddr) + } + + // Add all the interface IPs that aren't already in the slice. + addrs, err := net.InterfaceAddrs() + if err != nil { + return err + } + for _, a := range addrs { + ipAddr, _, err := net.ParseCIDR(a.String()) + if err == nil { + addIP(ipAddr) + } + } + + // Add extra IPs to the slice. + for _, ip := range tlsExtraIPs { + ipAddr := net.ParseIP(ip) + if ipAddr != nil { + addIP(ipAddr) + } + } + + // Collect the host's names into a slice. + host, err := os.Hostname() + if err != nil { + // Nothing much we can do here, other than falling back to + // localhost as fallback. A hostname can still be provided with + // the tlsExtraDomain parameter if the problem persists on a + // system. + host = "localhost" + } + + dnsNames := []string{host} + if host != "localhost" { + dnsNames = append(dnsNames, "localhost") + } + dnsNames = append(dnsNames, tlsExtraDomains...) + + // Also add fake hostnames for unix sockets, otherwise hostname + // verification will fail in the client. + dnsNames = append(dnsNames, "unix", "unixpacket") + + // Generate a private key for the certificate. + priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return err + } + + // Construct the certificate template. + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + Organization: []string{org}, + CommonName: host, + }, + NotBefore: now.Add(-time.Hour * 24), + NotAfter: validUntil, + + KeyUsage: x509.KeyUsageKeyEncipherment | + x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + IsCA: true, // so can sign self. + BasicConstraintsValid: true, + + DNSNames: dnsNames, + IPAddresses: ipAddresses, + } + + derBytes, err := x509.CreateCertificate(rand.Reader, &template, + &template, &priv.PublicKey, priv) + if err != nil { + return fmt.Errorf("failed to create certificate: %v", err) + } + + certBuf := &bytes.Buffer{} + err = pem.Encode(certBuf, &pem.Block{Type: "CERTIFICATE", + Bytes: derBytes}) + if err != nil { + return fmt.Errorf("failed to encode certificate: %v", err) + } + + keybytes, err := x509.MarshalECPrivateKey(priv) + if err != nil { + return fmt.Errorf("unable to encode privkey: %v", err) + } + keyBuf := &bytes.Buffer{} + err = pem.Encode(keyBuf, &pem.Block{Type: "EC PRIVATE KEY", + Bytes: keybytes}) + if err != nil { + return fmt.Errorf("failed to encode private key: %v", err) + } + + // Write cert and key files. + if err = ioutil.WriteFile(certFile, certBuf.Bytes(), 0644); err != nil { + return err + } + if err = ioutil.WriteFile(keyFile, keyBuf.Bytes(), 0600); err != nil { + os.Remove(certFile) + return err + } + + return nil +} diff --git a/cert/tls.go b/cert/tls.go new file mode 100644 index 000000000..a8783158e --- /dev/null +++ b/cert/tls.go @@ -0,0 +1,60 @@ +package cert + +import ( + "crypto/tls" + "crypto/x509" +) + +var ( + /* + * tlsCipherSuites is the list of cipher suites we accept for TLS + * connections. These cipher suites fit the following criteria: + * - Don't use outdated algorithms like SHA-1 and 3DES + * - Don't use ECB mode or other insecure symmetric methods + * - Included in the TLS v1.2 suite + * - Are available in the Go 1.7.6 standard library (more are + * available in 1.8.3 and will be added after lnd no longer + * supports 1.7, including suites that support CBC mode) + **/ + tlsCipherSuites = []uint16{ + tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, + } +) + +// LoadCert loads a certificate and its corresponding private key from the PEM +// files indicated and returns the certificate in the two formats it is most +// commonly used. +func LoadCert(certPath, keyPath string) (tls.Certificate, *x509.Certificate, + error) { + + // The certData returned here is just a wrapper around the PEM blocks + // loaded from the file. The PEM is not yet fully parsed but a basic + // check is performed that the certificate and private key actually + // belong together. + certData, err := tls.LoadX509KeyPair(certPath, keyPath) + if err != nil { + return tls.Certificate{}, nil, err + } + + // Now parse the the PEM block of the certificate into its x509 data + // structure so it can be examined in more detail. + x509Cert, err := x509.ParseCertificate(certData.Certificate[0]) + if err != nil { + return tls.Certificate{}, nil, err + } + + return certData, x509Cert, nil +} + +// TLSConfFromCert returns the default TLS configuration used for a server, +// using the given certificate as identity. +func TLSConfFromCert(certData tls.Certificate) *tls.Config { + return &tls.Config{ + Certificates: []tls.Certificate{certData}, + CipherSuites: tlsCipherSuites, + MinVersion: tls.VersionTLS12, + } +} diff --git a/go.mod b/go.mod index 2273f4685..5f6933e65 100644 --- a/go.mod +++ b/go.mod @@ -35,6 +35,7 @@ require ( github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec github.com/lightninglabs/neutrino v0.11.0 github.com/lightningnetwork/lightning-onion v0.0.0-20190909101754-850081b08b6a + github.com/lightningnetwork/lnd/cert v1.0.0 github.com/lightningnetwork/lnd/queue v1.0.1 github.com/lightningnetwork/lnd/ticker v1.0.0 github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 @@ -58,6 +59,8 @@ replace github.com/lightningnetwork/lnd/ticker => ./ticker replace github.com/lightningnetwork/lnd/queue => ./queue +replace github.com/lightningnetwork/lnd/cert => ./cert + replace git.schwanenlied.me/yawning/bsaes.git => github.com/Yawning/bsaes v0.0.0-20180720073208-c0276d75487e go 1.12 diff --git a/lnd.go b/lnd.go index f1eb59024..06379c1d1 100644 --- a/lnd.go +++ b/lnd.go @@ -5,18 +5,10 @@ package lnd import ( - "bytes" "context" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" "crypto/tls" - "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" "fmt" "io/ioutil" - "math/big" "net" "net/http" "os" @@ -42,6 +34,7 @@ import ( "github.com/lightningnetwork/lnd/autopilot" "github.com/lightningnetwork/lnd/build" + "github.com/lightningnetwork/lnd/cert" "github.com/lightningnetwork/lnd/chanacceptor" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/keychain" @@ -56,11 +49,6 @@ import ( "github.com/lightningnetwork/lnd/watchtower/wtdb" ) -const ( - // Make certificate valid for 14 months. - autogenCertValidity = 14 /*months*/ * 30 /*days*/ * 24 * time.Hour -) - var ( cfg *config registeredChains = newChainRegistry() @@ -69,28 +57,6 @@ var ( // network. This path will hold the files related to each different // network. networkDir string - - // End of ASN.1 time. - endOfTime = time.Date(2049, 12, 31, 23, 59, 59, 0, time.UTC) - - // Max serial number. - serialNumberLimit = new(big.Int).Lsh(big.NewInt(1), 128) - - /* - * These cipher suites fit the following criteria: - * - Don't use outdated algorithms like SHA-1 and 3DES - * - Don't use ECB mode or other insecure symmetric methods - * - Included in the TLS v1.2 suite - * - Are available in the Go 1.7.6 standard library (more are - * available in 1.8.3 and will be added after lnd no longer - * supports 1.7, including suites that support CBC mode) - **/ - tlsCipherSuites = []uint16{ - tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, - tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, - } ) // ListenerCfg is a wrapper around custom listeners that can be passed to lnd @@ -661,28 +627,28 @@ func getTLSConfig(tlsCertPath string, tlsKeyPath string, tlsExtraIPs, tlsExtraDomains []string, rpcListeners []net.Addr) (*tls.Config, *credentials.TransportCredentials, string, error) { - // Ensure we create TLS key and certificate if they don't exist + // Ensure we create TLS key and certificate if they don't exist. if !fileExists(tlsCertPath) && !fileExists(tlsKeyPath) { - err := genCertPair( - tlsCertPath, tlsKeyPath, tlsExtraIPs, tlsExtraDomains, + rpcsLog.Infof("Generating TLS certificates...") + err := cert.GenCertPair( + "lnd autogenerated cert", tlsCertPath, tlsKeyPath, + tlsExtraIPs, tlsExtraDomains, + cert.DefaultAutogenValidity, ) if err != nil { return nil, nil, "", err } + rpcsLog.Infof("Done generating TLS certificates") } - certData, err := tls.LoadX509KeyPair(tlsCertPath, tlsKeyPath) + certData, parsedCert, err := cert.LoadCert(tlsCertPath, tlsKeyPath) if err != nil { return nil, nil, "", err } - cert, err := x509.ParseCertificate(certData.Certificate[0]) - if err != nil { - return nil, nil, "", err - } - - // If the certificate expired, delete it and the TLS key and generate a new pair - if time.Now().After(cert.NotAfter) { + // If the certificate expired, delete it and the TLS key and generate a + // new pair. + if time.Now().After(parsedCert.NotAfter) { ltndLog.Info("TLS certificate is expired, generating a new one") err := os.Remove(tlsCertPath) @@ -695,21 +661,19 @@ func getTLSConfig(tlsCertPath string, tlsKeyPath string, tlsExtraIPs, return nil, nil, "", err } - err = genCertPair( - tlsCertPath, tlsKeyPath, tlsExtraIPs, tlsExtraDomains, + rpcsLog.Infof("Renewing TLS certificates...") + err = cert.GenCertPair( + "lnd autogenerated cert", tlsCertPath, tlsKeyPath, + tlsExtraIPs, tlsExtraDomains, + cert.DefaultAutogenValidity, ) if err != nil { return nil, nil, "", err } - - } - - tlsCfg := &tls.Config{ - Certificates: []tls.Certificate{certData}, - CipherSuites: tlsCipherSuites, - MinVersion: tls.VersionTLS12, + rpcsLog.Infof("Done renewing TLS certificates") } + tlsCfg := cert.TLSConfFromCert(certData) restCreds, err := credentials.NewClientTLSFromFile(tlsCertPath, "") if err != nil { return nil, nil, "", err @@ -742,147 +706,6 @@ func fileExists(name string) bool { return true } -// genCertPair generates a key/cert pair to the paths provided. The -// auto-generated certificates should *not* be used in production for public -// access as they're self-signed and don't necessarily contain all of the -// desired hostnames for the service. For production/public use, consider a -// real PKI. -// -// This function is adapted from https://github.com/btcsuite/btcd and -// https://github.com/btcsuite/btcutil -func genCertPair(certFile, keyFile string, tlsExtraIPs, - tlsExtraDomains []string) error { - - rpcsLog.Infof("Generating TLS certificates...") - - org := "lnd autogenerated cert" - now := time.Now() - validUntil := now.Add(autogenCertValidity) - - // Check that the certificate validity isn't past the ASN.1 end of time. - if validUntil.After(endOfTime) { - validUntil = endOfTime - } - - // Generate a serial number that's below the serialNumberLimit. - serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) - if err != nil { - return fmt.Errorf("failed to generate serial number: %s", err) - } - - // Collect the host's IP addresses, including loopback, in a slice. - ipAddresses := []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("::1")} - - // addIP appends an IP address only if it isn't already in the slice. - addIP := func(ipAddr net.IP) { - for _, ip := range ipAddresses { - if bytes.Equal(ip, ipAddr) { - return - } - } - ipAddresses = append(ipAddresses, ipAddr) - } - - // Add all the interface IPs that aren't already in the slice. - addrs, err := net.InterfaceAddrs() - if err != nil { - return err - } - for _, a := range addrs { - ipAddr, _, err := net.ParseCIDR(a.String()) - if err == nil { - addIP(ipAddr) - } - } - - // Add extra IPs to the slice. - for _, ip := range tlsExtraIPs { - ipAddr := net.ParseIP(ip) - if ipAddr != nil { - addIP(ipAddr) - } - } - - // Collect the host's names into a slice. - host, err := os.Hostname() - if err != nil { - rpcsLog.Errorf("Failed getting hostname, falling back to "+ - "localhost: %v", err) - host = "localhost" - } - - dnsNames := []string{host} - if host != "localhost" { - dnsNames = append(dnsNames, "localhost") - } - dnsNames = append(dnsNames, tlsExtraDomains...) - - // Also add fake hostnames for unix sockets, otherwise hostname - // verification will fail in the client. - dnsNames = append(dnsNames, "unix", "unixpacket") - - // Generate a private key for the certificate. - priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - if err != nil { - return err - } - - // Construct the certificate template. - template := x509.Certificate{ - SerialNumber: serialNumber, - Subject: pkix.Name{ - Organization: []string{org}, - CommonName: host, - }, - NotBefore: now.Add(-time.Hour * 24), - NotAfter: validUntil, - - KeyUsage: x509.KeyUsageKeyEncipherment | - x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, - IsCA: true, // so can sign self. - BasicConstraintsValid: true, - - DNSNames: dnsNames, - IPAddresses: ipAddresses, - } - - derBytes, err := x509.CreateCertificate(rand.Reader, &template, - &template, &priv.PublicKey, priv) - if err != nil { - return fmt.Errorf("failed to create certificate: %v", err) - } - - certBuf := &bytes.Buffer{} - err = pem.Encode(certBuf, &pem.Block{Type: "CERTIFICATE", - Bytes: derBytes}) - if err != nil { - return fmt.Errorf("failed to encode certificate: %v", err) - } - - keybytes, err := x509.MarshalECPrivateKey(priv) - if err != nil { - return fmt.Errorf("unable to encode privkey: %v", err) - } - keyBuf := &bytes.Buffer{} - err = pem.Encode(keyBuf, &pem.Block{Type: "EC PRIVATE KEY", - Bytes: keybytes}) - if err != nil { - return fmt.Errorf("failed to encode private key: %v", err) - } - - // Write cert and key files. - if err = ioutil.WriteFile(certFile, certBuf.Bytes(), 0644); err != nil { - return err - } - if err = ioutil.WriteFile(keyFile, keyBuf.Bytes(), 0600); err != nil { - os.Remove(certFile) - return err - } - - rpcsLog.Infof("Done generating TLS certificates") - return nil -} - // genMacaroons generates three macaroon files; one admin-level, one for // invoice access and one read-only. These can also be used to generate more // granular macaroons.