123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633 |
- package lnd
- import (
- "bytes"
- "context"
- "crypto/tls"
- "crypto/x509"
- "errors"
- "fmt"
- "net"
- "net/http"
- "os"
- "time"
- "github.com/lightningnetwork/lnd/cert"
- "github.com/lightningnetwork/lnd/keychain"
- "github.com/lightningnetwork/lnd/lncfg"
- "github.com/lightningnetwork/lnd/lnencrypt"
- "github.com/lightningnetwork/lnd/lnrpc"
- "golang.org/x/crypto/acme/autocert"
- "google.golang.org/grpc"
- "google.golang.org/grpc/credentials"
- )
- const (
- // modifyFilePermissons is the file permission used for writing
- // encrypted tls files.
- modifyFilePermissions = 0600
- // validityHours is the number of hours the ephemeral tls certificate
- // will be valid, if encrypting tls certificates is turned on.
- validityHours = 24
- )
- var (
- // privateKeyPrefix is the prefix to a plaintext TLS key.
- // It should match these two key formats:
- // - `-----BEGIN PRIVATE KEY-----` (PKCS8).
- // - `-----BEGIN EC PRIVATE KEY-----` (SEC1/rfc5915, the legacy format).
- privateKeyPrefix = []byte("-----BEGIN ")
- )
- // TLSManagerCfg houses a set of values and methods that is passed to the
- // TLSManager for it to properly manage LND's TLS options.
- type TLSManagerCfg struct {
- TLSCertPath string
- TLSKeyPath string
- TLSEncryptKey bool
- TLSExtraIPs []string
- TLSExtraDomains []string
- TLSAutoRefresh bool
- TLSDisableAutofill bool
- TLSCertDuration time.Duration
- LetsEncryptDir string
- LetsEncryptDomain string
- LetsEncryptListen string
- DisableRestTLS bool
- HTTPHeaderTimeout time.Duration
- }
- // TLSManager generates/renews a TLS cert/key pair when needed. When required,
- // it encrypts the TLS key. It also returns the certificate configuration
- // options needed for gRPC and REST.
- type TLSManager struct {
- cfg *TLSManagerCfg
- // tlsReloader is able to reload the certificate with the
- // GetCertificate function. In getConfig, tlsCfg.GetCertificate is
- // pointed towards t.tlsReloader.GetCertificateFunc(). When
- // TLSReloader's AttemptReload is called, the cert that tlsReloader
- // holds is changed, in turn changing the cert data
- // tlsCfg.GetCertificate will return.
- tlsReloader *cert.TLSReloader
- // These options are only used if we're currently using an ephemeral
- // TLS certificate, used when we're encrypting the TLS key.
- ephemeralKey []byte
- ephemeralCert []byte
- ephemeralCertPath string
- }
- // NewTLSManager returns a reference to a new TLSManager.
- func NewTLSManager(cfg *TLSManagerCfg) *TLSManager {
- return &TLSManager{
- cfg: cfg,
- }
- }
- // getConfig returns a TLS configuration for the gRPC server and credentials
- // and a proxy destination for the REST reverse proxy.
- func (t *TLSManager) getConfig() ([]grpc.ServerOption, []grpc.DialOption,
- func(net.Addr) (net.Listener, error), func(), error) {
- var (
- keyBytes, certBytes []byte
- err error
- )
- if t.ephemeralKey != nil {
- keyBytes = t.ephemeralKey
- certBytes = t.ephemeralCert
- } else {
- certBytes, keyBytes, err = cert.GetCertBytesFromPath(
- t.cfg.TLSCertPath, t.cfg.TLSKeyPath,
- )
- if err != nil {
- return nil, nil, nil, nil, err
- }
- }
- certData, _, err := cert.LoadCertFromBytes(certBytes, keyBytes)
- if err != nil {
- return nil, nil, nil, nil, err
- }
- if t.tlsReloader == nil {
- tlsr, err := cert.NewTLSReloader(certBytes, keyBytes)
- if err != nil {
- return nil, nil, nil, nil, err
- }
- t.tlsReloader = tlsr
- }
- tlsCfg := cert.TLSConfFromCert(certData)
- tlsCfg.GetCertificate = t.tlsReloader.GetCertificateFunc()
- // If Let's Encrypt is enabled, we need to set up the autocert manager
- // and override the TLS config's GetCertificate function.
- cleanUp := t.setUpLetsEncrypt(&certData, tlsCfg)
- // Now that we know that we have a certificate, let's generate the
- // required config options.
- serverCreds := credentials.NewTLS(tlsCfg)
- serverOpts := []grpc.ServerOption{grpc.Creds(serverCreds)}
- // For our REST dial options, we skip TLS verification, and we also
- // increase the max message size that we'll decode to allow clients to
- // hit endpoints which return more data such as the DescribeGraph call.
- // We set this to 200MiB atm. Should be the same value as maxMsgRecvSize
- // in cmd/lncli/main.go.
- restDialOpts := []grpc.DialOption{
- // We are forwarding the requests directly to the address of our
- // own local listener. To not need to mess with the TLS
- // certificate (which might be tricky if we're using Let's
- // Encrypt or if the ephemeral tls cert is being used), we just
- // skip the certificate verification. Injecting a malicious
- // hostname into the listener address will result in an error
- // on startup so this should be quite safe.
- grpc.WithTransportCredentials(credentials.NewTLS(
- &tls.Config{InsecureSkipVerify: true},
- )),
- grpc.WithDefaultCallOptions(
- grpc.MaxCallRecvMsgSize(lnrpc.MaxGrpcMsgSize),
- ),
- }
- // Return a function closure that can be used to listen on a given
- // address with the current TLS config.
- restListen := func(addr net.Addr) (net.Listener, error) {
- // For restListen we will call ListenOnAddress if TLS is
- // disabled.
- if t.cfg.DisableRestTLS {
- return lncfg.ListenOnAddress(addr)
- }
- return lncfg.TLSListenOnAddress(addr, tlsCfg)
- }
- return serverOpts, restDialOpts, restListen, cleanUp, nil
- }
- // generateOrRenewCert generates a new TLS certificate if we're not using one
- // yet or renews it if it's outdated.
- func (t *TLSManager) generateOrRenewCert() (*tls.Config, error) {
- // Generete a TLS pair if we don't have one yet.
- var emptyKeyRing keychain.SecretKeyRing
- err := t.generateCertPair(emptyKeyRing)
- if err != nil {
- return nil, err
- }
- certData, parsedCert, err := cert.LoadCert(
- t.cfg.TLSCertPath, t.cfg.TLSKeyPath,
- )
- if err != nil {
- return nil, err
- }
- // Check to see if the certificate needs to be renewed. If it does, we
- // return the newly generated certificate data instead.
- reloadedCertData, err := t.maintainCert(parsedCert)
- if err != nil {
- return nil, err
- }
- if reloadedCertData != nil {
- certData = *reloadedCertData
- }
- tlsCfg := cert.TLSConfFromCert(certData)
- return tlsCfg, nil
- }
- // generateCertPair creates and writes a TLS pair to disk if the pair
- // doesn't exist yet. If the TLSEncryptKey setting is on, and a plaintext key
- // 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) ||
- lnrpc.FileExists(t.cfg.TLSKeyPath) {
- // Handle discrepencies related to the TLSEncryptKey setting.
- return t.ensureEncryption(keyRing)
- }
- rpcsLog.Infof("Generating TLS certificates...")
- certBytes, keyBytes, err := cert.GenCertPair(
- "lnd autogenerated cert", t.cfg.TLSExtraIPs,
- t.cfg.TLSExtraDomains, t.cfg.TLSDisableAutofill,
- t.cfg.TLSCertDuration,
- )
- if err != nil {
- return err
- }
- if t.cfg.TLSEncryptKey {
- var b bytes.Buffer
- e, err := lnencrypt.KeyRingEncrypter(keyRing)
- if err != nil {
- return fmt.Errorf("unable to create "+
- "encrypt key %v", err)
- }
- err = e.EncryptPayloadToWriter(
- keyBytes, &b,
- )
- if err != nil {
- return err
- }
- keyBytes = b.Bytes()
- }
- err = cert.WriteCertPair(
- t.cfg.TLSCertPath, t.cfg.TLSKeyPath, certBytes, keyBytes,
- )
- rpcsLog.Infof("Done generating TLS certificates")
- return err
- }
- // ensureEncryption takes a look at a couple of things:
- // 1) If the TLS key is in plaintext, but TLSEncryptKey is set, we need to
- // encrypt the file and rewrite it to disk.
- // 2) On the flip side, if TLSEncryptKey is not set, but the key on disk
- // is encrypted, we need to error out and warn the user.
- func (t *TLSManager) ensureEncryption(keyRing keychain.SecretKeyRing) error {
- _, keyBytes, err := cert.GetCertBytesFromPath(
- t.cfg.TLSCertPath, t.cfg.TLSKeyPath,
- )
- if err != nil {
- return err
- }
- if t.cfg.TLSEncryptKey && bytes.HasPrefix(keyBytes, privateKeyPrefix) {
- var b bytes.Buffer
- e, err := lnencrypt.KeyRingEncrypter(keyRing)
- if err != nil {
- return fmt.Errorf("unable to generate encrypt key %w",
- err)
- }
- err = e.EncryptPayloadToWriter(keyBytes, &b)
- if err != nil {
- return err
- }
- err = os.WriteFile(
- t.cfg.TLSKeyPath, b.Bytes(), modifyFilePermissions,
- )
- if err != nil {
- return err
- }
- }
- // If the private key is encrypted but the user didn't pass
- // --tlsencryptkey we error out. This is because the wallet is not
- // unlocked yet and we don't have access to the keys yet for decryption.
- if !t.cfg.TLSEncryptKey && !bytes.HasPrefix(keyBytes,
- privateKeyPrefix) {
- ltndLog.Errorf("The TLS private key is encrypted on disk.")
- return errors.New("the TLS key is encrypted but the " +
- "--tlsencryptkey flag is not passed. Please either " +
- "restart lnd with the --tlsencryptkey flag or delete " +
- "the TLS files for regeneration")
- }
- return nil
- }
- // decryptTLSKeyBytes decrypts the TLS key.
- func decryptTLSKeyBytes(keyRing keychain.SecretKeyRing,
- encryptedData []byte) ([]byte, error) {
- reader := bytes.NewReader(encryptedData)
- encrypter, err := lnencrypt.KeyRingEncrypter(keyRing)
- if err != nil {
- return nil, err
- }
- plaintext, err := encrypter.DecryptPayloadFromReader(
- reader,
- )
- if err != nil {
- return nil, err
- }
- return plaintext, nil
- }
- // maintainCert checks if the certificate IP and domains matches the config,
- // and renews the certificate if either this data is outdated or the
- // certificate is expired.
- func (t *TLSManager) maintainCert(
- parsedCert *x509.Certificate) (*tls.Certificate, error) {
- // We check whether the certificate we have on disk match the IPs and
- // domains specified by the config. If the extra IPs or domains have
- // changed from when the certificate was created, we will refresh the
- // certificate if auto refresh is active.
- refresh := false
- var err error
- if t.cfg.TLSAutoRefresh {
- refresh, err = cert.IsOutdated(
- parsedCert, t.cfg.TLSExtraIPs,
- t.cfg.TLSExtraDomains, t.cfg.TLSDisableAutofill,
- )
- if err != nil {
- return nil, err
- }
- }
- // If the certificate expired or it was outdated, delete it and the TLS
- // key and generate a new pair.
- if !time.Now().After(parsedCert.NotAfter) && !refresh {
- return nil, nil
- }
- ltndLog.Info("TLS certificate is expired or outdated, " +
- "generating a new one")
- err = os.Remove(t.cfg.TLSCertPath)
- if err != nil {
- return nil, err
- }
- err = os.Remove(t.cfg.TLSKeyPath)
- if err != nil {
- return nil, err
- }
- rpcsLog.Infof("Renewing TLS certificates...")
- certBytes, keyBytes, err := cert.GenCertPair(
- "lnd autogenerated cert", t.cfg.TLSExtraIPs,
- t.cfg.TLSExtraDomains, t.cfg.TLSDisableAutofill,
- t.cfg.TLSCertDuration,
- )
- if err != nil {
- return nil, err
- }
- err = cert.WriteCertPair(
- t.cfg.TLSCertPath, t.cfg.TLSKeyPath, certBytes, keyBytes,
- )
- if err != nil {
- return nil, err
- }
- rpcsLog.Infof("Done renewing TLS certificates")
- // Reload the certificate data.
- reloadedCertData, _, err := cert.LoadCert(
- t.cfg.TLSCertPath, t.cfg.TLSKeyPath,
- )
- return &reloadedCertData, err
- }
- // setUpLetsEncrypt automatically generates a Let's Encrypt certificate if the
- // option is set.
- func (t *TLSManager) setUpLetsEncrypt(certData *tls.Certificate,
- tlsCfg *tls.Config) func() {
- // If Let's Encrypt is enabled, instantiate autocert to request/renew
- // the certificates.
- cleanUp := func() {}
- if t.cfg.LetsEncryptDomain == "" {
- return cleanUp
- }
- ltndLog.Infof("Using Let's Encrypt certificate for domain %v",
- t.cfg.LetsEncryptDomain)
- manager := autocert.Manager{
- Cache: autocert.DirCache(t.cfg.LetsEncryptDir),
- Prompt: autocert.AcceptTOS,
- HostPolicy: autocert.HostWhitelist(
- t.cfg.LetsEncryptDomain,
- ),
- }
- srv := &http.Server{
- Addr: t.cfg.LetsEncryptListen,
- Handler: manager.HTTPHandler(nil),
- ReadHeaderTimeout: t.cfg.HTTPHeaderTimeout,
- }
- shutdownCompleted := make(chan struct{})
- cleanUp = func() {
- err := srv.Shutdown(context.Background())
- if err != nil {
- ltndLog.Errorf("Autocert listener shutdown "+
- " error: %v", err)
- return
- }
- <-shutdownCompleted
- ltndLog.Infof("Autocert challenge listener stopped")
- }
- go func() {
- ltndLog.Infof("Autocert challenge listener started "+
- "at %v", t.cfg.LetsEncryptListen)
- err := srv.ListenAndServe()
- if err != http.ErrServerClosed {
- ltndLog.Errorf("autocert http: %v", err)
- }
- close(shutdownCompleted)
- }()
- getCertificate := func(h *tls.ClientHelloInfo) (
- *tls.Certificate, error) {
- lecert, err := manager.GetCertificate(h)
- if err != nil {
- ltndLog.Errorf("GetCertificate: %v", err)
- return certData, nil
- }
- return lecert, err
- }
- // The self-signed tls.cert remains available as fallback.
- tlsCfg.GetCertificate = getCertificate
- return cleanUp
- }
- // SetCertificateBeforeUnlock takes care of loading the certificate before
- // the wallet is unlocked. If the TLSEncryptKey setting is on, we need to
- // generate an ephemeral certificate we're able to use until the wallet is
- // unlocked and a new TLS pair can be encrypted to disk. Otherwise we can
- // process the certificate normally.
- func (t *TLSManager) SetCertificateBeforeUnlock() ([]grpc.ServerOption,
- []grpc.DialOption, func(net.Addr) (net.Listener, error), func(),
- error) {
- if t.cfg.TLSEncryptKey {
- _, err := t.loadEphemeralCertificate()
- if err != nil {
- return nil, nil, nil, nil, fmt.Errorf("unable to load "+
- "ephemeral certificate: %v", err)
- }
- } else {
- _, err := t.generateOrRenewCert()
- if err != nil {
- return nil, nil, nil, nil, fmt.Errorf("unable to "+
- "generate or renew TLS certificate: %v", err)
- }
- }
- serverOpts, restDialOpts, restListen, cleanUp, err := t.getConfig()
- if err != nil {
- return nil, nil, nil, nil, fmt.Errorf("unable to load TLS "+
- "credentials: %v", err)
- }
- return serverOpts, restDialOpts, restListen, cleanUp, nil
- }
- // loadEphemeralCertificate creates and loads the ephemeral certificate which
- // is used temporarily for secure communications before the wallet is unlocked.
- func (t *TLSManager) loadEphemeralCertificate() ([]byte, error) {
- rpcsLog.Infof("Generating ephemeral TLS certificates...")
- tmpValidity := validityHours * time.Hour
- // Append .tmp to the end of the cert for differentiation.
- tmpCertPath := t.cfg.TLSCertPath + ".tmp"
- // Pass in a blank string for the key path so the
- // function doesn't write them to disk.
- certBytes, keyBytes, err := cert.GenCertPair(
- "lnd ephemeral autogenerated cert", t.cfg.TLSExtraIPs,
- t.cfg.TLSExtraDomains, t.cfg.TLSDisableAutofill, tmpValidity,
- )
- if err != nil {
- return nil, err
- }
- t.setEphemeralSettings(keyBytes, certBytes)
- err = cert.WriteCertPair(tmpCertPath, "", certBytes, keyBytes)
- if err != nil {
- return nil, err
- }
- rpcsLog.Infof("Done generating ephemeral TLS certificates")
- return keyBytes, nil
- }
- // LoadPermanentCertificate deletes the ephemeral certificate file and
- // generates a new one with the real keyring.
- func (t *TLSManager) LoadPermanentCertificate(
- keyRing keychain.SecretKeyRing) error {
- if !t.cfg.TLSEncryptKey {
- return nil
- }
- tmpCertPath := t.cfg.TLSCertPath + ".tmp"
- err := os.Remove(tmpCertPath)
- if err != nil {
- ltndLog.Warn("Unable to delete temp cert at %v",
- tmpCertPath)
- }
- err = t.generateCertPair(keyRing)
- if err != nil {
- return err
- }
- certBytes, encryptedKeyBytes, err := cert.GetCertBytesFromPath(
- t.cfg.TLSCertPath, t.cfg.TLSKeyPath,
- )
- if err != nil {
- return err
- }
- reader := bytes.NewReader(encryptedKeyBytes)
- e, err := lnencrypt.KeyRingEncrypter(keyRing)
- if err != nil {
- return fmt.Errorf("unable to generate encrypt key %w",
- err)
- }
- keyBytes, err := e.DecryptPayloadFromReader(reader)
- if err != nil {
- return err
- }
- // Switch the server's TLS certificate to the persistent one. By
- // changing the cert data the TLSReloader points to,
- err = t.tlsReloader.AttemptReload(certBytes, keyBytes)
- if err != nil {
- return err
- }
- t.deleteEphemeralSettings()
- return nil
- }
- // setEphemeralSettings sets the TLSManager settings needed when an ephemeral
- // certificate is created.
- func (t *TLSManager) setEphemeralSettings(keyBytes, certBytes []byte) {
- t.ephemeralKey = keyBytes
- t.ephemeralCert = certBytes
- t.ephemeralCertPath = t.cfg.TLSCertPath + ".tmp"
- }
- // deleteEphemeralSettings deletes the TLSManager ephemeral settings that are
- // no longer needed when the ephemeral certificate is deleted so the Manager
- // knows we're no longer using it.
- func (t *TLSManager) deleteEphemeralSettings() {
- t.ephemeralKey = nil
- t.ephemeralCert = nil
- t.ephemeralCertPath = ""
- }
- // IsCertExpired checks if the current TLS certificate is expired.
- func (t *TLSManager) IsCertExpired(keyRing keychain.SecretKeyRing) (bool,
- time.Time, error) {
- certBytes, keyBytes, err := cert.GetCertBytesFromPath(
- t.cfg.TLSCertPath, t.cfg.TLSKeyPath,
- )
- if err != nil {
- return false, time.Time{}, err
- }
- // If TLSEncryptKey is set, there are two states the
- // certificate can be in: ephemeral or permanent.
- // Retrieve the key depending on which state it is in.
- if t.ephemeralKey != nil {
- keyBytes = t.ephemeralKey
- } else if t.cfg.TLSEncryptKey {
- keyBytes, err = decryptTLSKeyBytes(keyRing, keyBytes)
- if err != nil {
- return false, time.Time{}, err
- }
- }
- _, parsedCert, err := cert.LoadCertFromBytes(
- certBytes, keyBytes,
- )
- if err != nil {
- return false, time.Time{}, err
- }
- // If the current time is passed the certificate's
- // expiry time, then it is considered expired
- if time.Now().After(parsedCert.NotAfter) {
- return true, parsedCert.NotAfter, nil
- }
- return false, parsedCert.NotAfter, nil
- }
|