Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix TLS #253

Merged
merged 1 commit into from
Aug 18, 2017
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
167 changes: 156 additions & 11 deletions lnd.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
package main

import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"io/ioutil"
"math/big"
"net"
"net/http"
_ "net/http/pprof"
Expand Down Expand Up @@ -33,13 +40,20 @@ import (
)

const (
autogenCertValidity = 10 * 365 * 24 * time.Hour
// Make certificate valid for 14 months.
autogenCertValidity = 14 /*months*/ * 30 /*days*/ * 24 * time.Hour
)

var (
cfg *config
shutdownChannel = make(chan struct{})
registeredChains = newChainRegistry()

// 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)
)

// lndMain is the true entry point for lnd. This function is required since
Expand Down Expand Up @@ -229,11 +243,38 @@ func lndMain() error {
if err := rpcServer.Start(); err != nil {
return err
}
sCreds, err := credentials.NewServerTLSFromFile(cfg.TLSCertPath,
cfg.TLSKeyPath)
cert, err := tls.LoadX509KeyPair(cfg.TLSCertPath, cfg.TLSKeyPath)
if err != nil {
return err
}
tlsConf := &tls.Config{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the weirdness observed when interfacing with gRPC clients in other languages, it would be nice to have a comment here explaining why these cipher suites and their ordering were chosen :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a comment about the selection criteria for the cipher suites and reordered them from strongest to weakest primitives, but client preference order has a stronger effect on negotiation (and I'm not sure ordering matters at all on the server).

Certificates: []tls.Certificate{cert},
/*
* 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)
*
* The cipher suites are ordered from strongest to weakest
* primitives, but the client's preference order has more
* effect during negotiation.
**/
// TODO(aakselrod): add more cipher suites when 1.7 isn't
// supported.
CipherSuites: []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
},
MinVersion: tls.VersionTLS12,
}
sCreds := credentials.NewTLS(tlsConf)
opts := []grpc.ServerOption{grpc.Creds(sCreds)}
grpcServer := grpc.NewServer(opts...)
lnrpc.RegisterLightningServer(grpcServer, rpcServer)
Expand Down Expand Up @@ -268,9 +309,14 @@ func lndMain() error {
}
go func() {
restEndpoint := fmt.Sprintf(":%d", loadedConfig.RESTPort)
listener, err := tls.Listen("tcp", restEndpoint, tlsConf)
if err != nil {
ltndLog.Errorf("gRPC proxy unable to listen on "+
"localhost%s", restEndpoint)
return
}
rpcsLog.Infof("gRPC proxy started at localhost%s", restEndpoint)
http.ListenAndServeTLS(restEndpoint, cfg.TLSCertPath,
cfg.TLSKeyPath, mux)
http.Serve(listener, mux)
}()

// If we're not in simnet mode, We'll wait until we're fully synced to
Expand Down Expand Up @@ -379,23 +425,122 @@ func fileExists(name string) bool {
return true
}

// genCertPair generates a key/cert pair to the paths provided.
// This function is adapted from https://github.com/btcsuite/btcd
// 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) error {
rpcsLog.Infof("Generating TLS certificates...")

org := "lnd autogenerated cert"
validUntil := time.Now().Add(autogenCertValidity)
cert, key, err := btcutil.NewTLSCertPair(org, validUntil, nil)
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)
}
}

// Collect the host's names into a slice.
host, err := os.Hostname()
if err != nil {
return err
}
dnsNames := []string{host}
if host != "localhost" {
dnsNames = append(dnsNames, "localhost")
}

// Generate a private key for the certificate.
priv, err := rsa.GenerateKey(rand.Reader, 4096)
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,

// This signature algorithm is most likely to be compatible
// with clients using less-common TLS libraries like BoringSSL.
SignatureAlgorithm: x509.SHA256WithRSA,
}

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 := x509.MarshalPKCS1PrivateKey(priv)
keyBuf := &bytes.Buffer{}
err = pem.Encode(keyBuf, &pem.Block{Type: "RSA 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, cert, 0644); err != nil {
if err = ioutil.WriteFile(certFile, certBuf.Bytes(), 0644); err != nil {
return err
}
if err = ioutil.WriteFile(keyFile, key, 0600); err != nil {
if err = ioutil.WriteFile(keyFile, keyBuf.Bytes(), 0600); err != nil {
os.Remove(certFile)
return err
}
Expand Down