diff --git a/cert/selfsigned.go b/cert/selfsigned.go new file mode 100644 index 00000000..7e3dbf63 --- /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 ( + // defaultAutogenCertValidity is the default validity of a self-signed + // certificate. The value corresponds to 14 months + // (14 months * 30 days * 24 hours). + defaultAutogenCertValidity = 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 00000000..49da5f46 --- /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, + } +}