kirin: allow handling client requests over Tor onion services

In this commit, we integrate Tor onion services into the proxy. Clients
can now make their requests through Tor's encrypted network. To make
this possible, there were a few quirks, the most important being that
clients were unable to establish encrypted HTTP/2 connections due to
TLS certificates not being able to verify onion services. To work around
this, we now spin up an additional HTTP/2 server _without TLS_ that's
not exposed to the outside world and can only be accessed through the
onion services, which already provide encryption.

Once the onion services are created, we store their private keys within
etcd to ensure we can recover them later on as the proxy is intended to
be long-lived.
This commit is contained in:
Wilmer Paulino
2020-03-06 17:46:08 -08:00
parent 34b4e1f6a5
commit 494fdcc0a3
7 changed files with 287 additions and 11 deletions

View File

@@ -33,6 +33,14 @@ type authConfig struct {
Network string `long:"network"` Network string `long:"network"`
} }
type torConfig struct {
Control string `long:"control" description:"The host:port of the Tor instance."`
ListenPort uint16 `long:"listenport" description:"The port we should listen on for client requests over Tor. Note that this port should not be exposed to the outside world, it is only intended to be reached by clients through the onion service."`
VirtualPort uint16 `long:"virtualport" description:"The port through which the onion services created can be reached at."`
V2 bool `long:"v2" description:"Whether we should listen for client requests through a v2 onion service."`
V3 bool `long:"v3" description:"Whether we should listen for client requests through a v3 onion service."`
}
type config struct { type config struct {
// ListenAddr is the listening address that we should use to allow Kirin // ListenAddr is the listening address that we should use to allow Kirin
// to listen for requests. // to listen for requests.
@@ -54,6 +62,8 @@ type config struct {
Authenticator *authConfig `long:"authenticator" description:"Configuration for the authenticator."` Authenticator *authConfig `long:"authenticator" description:"Configuration for the authenticator."`
Tor *torConfig `long:"tor" description:"Configuration for the Tor instance backing the proxy."`
// Services is a list of JSON objects in string format, which specify // Services is a list of JSON objects in string format, which specify
// each backend service to Kirin. // each backend service to Kirin.
Services []*proxy.Service `long:"service" description:"Configurations for each Kirin backend service."` Services []*proxy.Service `long:"service" description:"Configurations for each Kirin backend service."`

4
go.mod
View File

@@ -18,7 +18,7 @@ require (
github.com/jonboulle/clockwork v0.1.0 // indirect github.com/jonboulle/clockwork v0.1.0 // indirect
github.com/json-iterator/go v1.1.8 // indirect github.com/json-iterator/go v1.1.8 // indirect
github.com/lightninglabs/loop v0.3.0-alpha.0.20200103135410-5e00ce62677a github.com/lightninglabs/loop v0.3.0-alpha.0.20200103135410-5e00ce62677a
github.com/lightningnetwork/lnd v0.8.0-beta-rc3.0.20200103000305-22e1f006b194 github.com/lightningnetwork/lnd v0.9.0-beta-rc4.0.20200313014957-4cb518c17498
github.com/lightningnetwork/lnd/cert v1.0.0 github.com/lightningnetwork/lnd/cert v1.0.0
github.com/modern-go/reflect2 v1.0.1 // indirect github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/soheilhy/cmux v0.1.4 // indirect github.com/soheilhy/cmux v0.1.4 // indirect
@@ -26,7 +26,7 @@ require (
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
go.uber.org/zap v1.13.0 // indirect go.uber.org/zap v1.13.0 // indirect
golang.org/x/crypto v0.0.0-20200109152110-61a87790db17 golang.org/x/crypto v0.0.0-20200109152110-61a87790db17
golang.org/x/net v0.0.0-20191112182307-2180aed22343 // indirect golang.org/x/net v0.0.0-20191112182307-2180aed22343
golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea // indirect golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea // indirect
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a // indirect google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a // indirect
google.golang.org/grpc v1.25.1 google.golang.org/grpc v1.25.1

13
go.sum
View File

@@ -29,8 +29,9 @@ github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6ApMkVy8cUxV0XrxdP9rVf6D87/Mng= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6ApMkVy8cUxV0XrxdP9rVf6D87/Mng=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
github.com/btcsuite/btcwallet v0.11.0 h1:XhwqdhEchy5a0q6R+y3F82roD2hYycPCHovgNyJS08w=
github.com/btcsuite/btcwallet v0.11.0/go.mod h1:qtPAohN1ioo0pvJt/j7bZM8ANBWlYWVCVFL0kkijs7s= github.com/btcsuite/btcwallet v0.11.0/go.mod h1:qtPAohN1ioo0pvJt/j7bZM8ANBWlYWVCVFL0kkijs7s=
github.com/btcsuite/btcwallet v0.11.1-0.20200219004649-ae9416ad7623 h1:ZuJRjucNsTmlrbZncsqzD0z3EaXrOobCx2I4lc12R4g=
github.com/btcsuite/btcwallet v0.11.1-0.20200219004649-ae9416ad7623/go.mod h1:1O1uRHMPXHdwA4/od8nqYqrgclVKp+wtfXUAqHmeRvE=
github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0 h1:KGHMW5sd7yDdDMkCZ/JpP0KltolFsQcB973brBnfj4c= github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0 h1:KGHMW5sd7yDdDMkCZ/JpP0KltolFsQcB973brBnfj4c=
github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0/go.mod h1:VufDts7bd/zs3GV13f/lXc/0lXrPnvxD/NvmpG/FEKU= github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0/go.mod h1:VufDts7bd/zs3GV13f/lXc/0lXrPnvxD/NvmpG/FEKU=
github.com/btcsuite/btcwallet/wallet/txrules v1.0.0 h1:2VsfS0sBedcM5KmDzRMT3+b6xobqWveZGvjb+jFez5w= github.com/btcsuite/btcwallet/wallet/txrules v1.0.0 h1:2VsfS0sBedcM5KmDzRMT3+b6xobqWveZGvjb+jFez5w=
@@ -39,8 +40,9 @@ github.com/btcsuite/btcwallet/wallet/txsizes v1.0.0 h1:6DxkcoMnCPY4E9cUDPB5tbuuf
github.com/btcsuite/btcwallet/wallet/txsizes v1.0.0/go.mod h1:pauEU8UuMFiThe5PB3EO+gO5kx87Me5NvdQDsTuq6cs= github.com/btcsuite/btcwallet/wallet/txsizes v1.0.0/go.mod h1:pauEU8UuMFiThe5PB3EO+gO5kx87Me5NvdQDsTuq6cs=
github.com/btcsuite/btcwallet/walletdb v1.0.0 h1:mheT7vCWK5EP6rZzhxsQ7ms9+yX4VE8bwiJctECBeNw= github.com/btcsuite/btcwallet/walletdb v1.0.0 h1:mheT7vCWK5EP6rZzhxsQ7ms9+yX4VE8bwiJctECBeNw=
github.com/btcsuite/btcwallet/walletdb v1.0.0/go.mod h1:bZTy9RyYZh9fLnSua+/CD48TJtYJSHjjYcSaszuxCCk= github.com/btcsuite/btcwallet/walletdb v1.0.0/go.mod h1:bZTy9RyYZh9fLnSua+/CD48TJtYJSHjjYcSaszuxCCk=
github.com/btcsuite/btcwallet/walletdb v1.1.0 h1:JHAL7wZ8pX4SULabeAv/wPO9sseRWMGzE80lfVmRw6Y=
github.com/btcsuite/btcwallet/walletdb v1.1.0/go.mod h1:bZTy9RyYZh9fLnSua+/CD48TJtYJSHjjYcSaszuxCCk= github.com/btcsuite/btcwallet/walletdb v1.1.0/go.mod h1:bZTy9RyYZh9fLnSua+/CD48TJtYJSHjjYcSaszuxCCk=
github.com/btcsuite/btcwallet/walletdb v1.2.0 h1:E0+M4jHOToAvGWZ27ew5AaDAHDi6fUiXkjUJUnoEOD0=
github.com/btcsuite/btcwallet/walletdb v1.2.0/go.mod h1:9cwc1Yyg4uvd4ZdfdoMnALji+V9gfWSMfxEdLdR5Vwc=
github.com/btcsuite/btcwallet/wtxmgr v1.0.0 h1:aIHgViEmZmZfe0tQQqF1xyd2qBqFWxX5vZXkkbjtbeA= github.com/btcsuite/btcwallet/wtxmgr v1.0.0 h1:aIHgViEmZmZfe0tQQqF1xyd2qBqFWxX5vZXkkbjtbeA=
github.com/btcsuite/btcwallet/wtxmgr v1.0.0/go.mod h1:vc4gBprll6BP0UJ+AIGDaySoc7MdAmZf8kelfNb8CFY= github.com/btcsuite/btcwallet/wtxmgr v1.0.0/go.mod h1:vc4gBprll6BP0UJ+AIGDaySoc7MdAmZf8kelfNb8CFY=
github.com/btcsuite/fastsha256 v0.0.0-20160815193821-637e65642941 h1:kij1x2aL7VE6gtx8KMIt8PGPgI5GV9LgtHFG5KaEMPY= github.com/btcsuite/fastsha256 v0.0.0-20160815193821-637e65642941 h1:kij1x2aL7VE6gtx8KMIt8PGPgI5GV9LgtHFG5KaEMPY=
@@ -173,10 +175,12 @@ github.com/lightninglabs/loop v0.3.0-alpha.0.20200103135410-5e00ce62677a/go.mod
github.com/lightninglabs/neutrino v0.11.0 h1:lPpYFCtsfJX2W5zI4pWycPmbbBdr7zU+BafYdLoD6k0= github.com/lightninglabs/neutrino v0.11.0 h1:lPpYFCtsfJX2W5zI4pWycPmbbBdr7zU+BafYdLoD6k0=
github.com/lightninglabs/neutrino v0.11.0/go.mod h1:CuhF0iuzg9Sp2HO6ZgXgayviFTn1QHdSTJlMncK80wg= github.com/lightninglabs/neutrino v0.11.0/go.mod h1:CuhF0iuzg9Sp2HO6ZgXgayviFTn1QHdSTJlMncK80wg=
github.com/lightninglabs/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce75d/go.mod h1:KDb67YMzoh4eudnzClmvs2FbiLG9vxISmLApUkCa4uI= github.com/lightninglabs/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce75d/go.mod h1:KDb67YMzoh4eudnzClmvs2FbiLG9vxISmLApUkCa4uI=
github.com/lightningnetwork/lightning-onion v0.0.0-20191214001659-f34e9dc1651d h1:U50MHOOeL6gR3Ee/l0eMvZMpmRo+ydzmlQuIruCyCsA=
github.com/lightningnetwork/lightning-onion v0.0.0-20191214001659-f34e9dc1651d/go.mod h1:rigfi6Af/KqsF7Za0hOgcyq2PNH4AN70AaMRxcJkff4= github.com/lightningnetwork/lightning-onion v0.0.0-20191214001659-f34e9dc1651d/go.mod h1:rigfi6Af/KqsF7Za0hOgcyq2PNH4AN70AaMRxcJkff4=
github.com/lightningnetwork/lnd v0.8.0-beta-rc3.0.20200103000305-22e1f006b194 h1:PCzjJcVWcMbkiQvzFNc3ta0JmiMprFDqzMZsSpd/km8= github.com/lightningnetwork/lightning-onion v1.0.1 h1:qChGgS5+aPxFeR6JiUsGvanei1bn6WJpYbvosw/1604=
github.com/lightningnetwork/lightning-onion v1.0.1/go.mod h1:rigfi6Af/KqsF7Za0hOgcyq2PNH4AN70AaMRxcJkff4=
github.com/lightningnetwork/lnd v0.8.0-beta-rc3.0.20200103000305-22e1f006b194/go.mod h1:WHK90FD3m2n6OyWzondS7ho0Uhtgfp30Nxvj24lQYX4= github.com/lightningnetwork/lnd v0.8.0-beta-rc3.0.20200103000305-22e1f006b194/go.mod h1:WHK90FD3m2n6OyWzondS7ho0Uhtgfp30Nxvj24lQYX4=
github.com/lightningnetwork/lnd v0.9.0-beta-rc4.0.20200313014957-4cb518c17498 h1:v9WzPrzY/BOPfs7D1Lg5IuIxhao3BAJaL8EFFcLTzTY=
github.com/lightningnetwork/lnd v0.9.0-beta-rc4.0.20200313014957-4cb518c17498/go.mod h1:fImtTwhIXK91glN8iArkrGuScc0sNEKpZq43pTvVq3w=
github.com/lightningnetwork/lnd/cert v1.0.0 h1:J0gtf2UNQX2U+/j5cXnX2wIMSTuJuwrXv7m9qJr2wtw= github.com/lightningnetwork/lnd/cert v1.0.0 h1:J0gtf2UNQX2U+/j5cXnX2wIMSTuJuwrXv7m9qJr2wtw=
github.com/lightningnetwork/lnd/cert v1.0.0/go.mod h1:fmtemlSMf5t4hsQmcprSoOykypAPp+9c+0d0iqTScMo= github.com/lightningnetwork/lnd/cert v1.0.0/go.mod h1:fmtemlSMf5t4hsQmcprSoOykypAPp+9c+0d0iqTScMo=
github.com/lightningnetwork/lnd/queue v1.0.1 h1:jzJKcTy3Nj5lQrooJ3aaw9Lau3I0IwvQR5sqtjdv2R0= github.com/lightningnetwork/lnd/queue v1.0.1 h1:jzJKcTy3Nj5lQrooJ3aaw9Lau3I0IwvQR5sqtjdv2R0=
@@ -297,6 +301,7 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=

View File

@@ -17,7 +17,10 @@ import (
"github.com/lightningnetwork/lnd/build" "github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/cert" "github.com/lightningnetwork/lnd/cert"
"github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/tor"
"golang.org/x/crypto/acme/autocert" "golang.org/x/crypto/acme/autocert"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
@@ -77,9 +80,10 @@ func start() error {
if err != nil { if err != nil {
return err return err
} }
server := &http.Server{ handler := http.HandlerFunc(servicesProxy.ServeHTTP)
httpsServer := &http.Server{
Addr: cfg.ListenAddr, Addr: cfg.ListenAddr,
Handler: http.HandlerFunc(servicesProxy.ServeHTTP), Handler: handler,
} }
// Create TLS certificates. // Create TLS certificates.
@@ -112,7 +116,7 @@ func start() error {
log.Errorf("autocert http: %v", err) log.Errorf("autocert http: %v", err)
} }
}() }()
server.TLSConfig = &tls.Config{ httpsServer.TLSConfig = &tls.Config{
GetCertificate: manager.GetCertificate, GetCertificate: manager.GetCertificate,
} }
@@ -139,11 +143,43 @@ func start() error {
// The ListenAndServeTLS below will block until shut down or an error // The ListenAndServeTLS below will block until shut down or an error
// occurs. So we can just defer a cleanup function here that will close // occurs. So we can just defer a cleanup function here that will close
// everything on shutdown. // everything on shutdown.
defer cleanup(etcdClient, server) defer cleanup(etcdClient, httpsServer)
// Finally start the server. // Finally start the server.
log.Infof("Starting the server, listening on %s.", cfg.ListenAddr) log.Infof("Starting the server, listening on %s.", cfg.ListenAddr)
return server.ListenAndServeTLS(tlsCertFile, tlsKeyFile)
errChan := make(chan error)
go func() {
errChan <- httpsServer.ListenAndServeTLS(tlsCertFile, tlsKeyFile)
}()
// If we need to listen over Tor as well, we'll set up the onion
// services now. We're not able to use TLS for onion services since they
// can't be verified, so we'll spin up an additional HTTP/2 server
// _without_ TLS that is not exposed to the outside world. This server
// will only be reached through the onion services, which already
// provide encryption, so running this additional HTTP server should be
// relatively safe.
if cfg.Tor.V2 || cfg.Tor.V3 {
torController, err := initTorListener(cfg, etcdClient)
if err != nil {
return err
}
defer func() {
_ = torController.Stop()
}()
httpServer := &http.Server{
Addr: fmt.Sprintf("localhost:%d", cfg.Tor.ListenPort),
Handler: h2c.NewHandler(handler, &http2.Server{}),
}
go func() {
errChan <- httpServer.ListenAndServe()
}()
defer httpServer.Close()
}
return <-errChan
} }
// fileExists reports whether the named file or directory exists. // fileExists reports whether the named file or directory exists.
@@ -195,6 +231,45 @@ func setupLogging(cfg *config) error {
return build.ParseAndSetDebugLevels(cfg.DebugLevel, logWriter) return build.ParseAndSetDebugLevels(cfg.DebugLevel, logWriter)
} }
// initTorListener initiates a Tor controller instance with the Tor server
// specified in the config. Onion services will be created over which the proxy
// can be reached at.
func initTorListener(cfg *config, etcd *clientv3.Client) (*tor.Controller, error) {
// Establish a controller connection with the backing Tor server and
// proceed to create the requested onion services.
onionCfg := tor.AddOnionConfig{
VirtualPort: int(cfg.Tor.VirtualPort),
TargetPorts: []int{int(cfg.Tor.ListenPort)},
Store: newOnionStore(etcd),
}
torController := tor.NewController(cfg.Tor.Control, "", "")
if err := torController.Start(); err != nil {
return nil, err
}
if cfg.Tor.V2 {
onionCfg.Type = tor.V2
addr, err := torController.AddOnion(onionCfg)
if err != nil {
return nil, err
}
log.Infof("Listening over Tor on %v", addr)
}
if cfg.Tor.V3 {
onionCfg.Type = tor.V3
addr, err := torController.AddOnion(onionCfg)
if err != nil {
return nil, err
}
log.Infof("Listening over Tor on %v", addr)
}
return torController, nil
}
// createProxy creates the proxy with all the services it needs. // createProxy creates the proxy with all the services it needs.
func createProxy(cfg *config, genInvoiceReq InvoiceRequestGenerator, func createProxy(cfg *config, genInvoiceReq InvoiceRequestGenerator,
etcdClient *clientv3.Client) (*proxy.Proxy, error) { etcdClient *clientv3.Client) (*proxy.Proxy, error) {

98
onion_store.go Normal file
View File

@@ -0,0 +1,98 @@
package kirin
import (
"context"
"fmt"
"strings"
"github.com/coreos/etcd/clientv3"
"github.com/lightningnetwork/lnd/tor"
)
const (
// onionDir is the directory we'll use to store all onion service
// related information.
onionDir = "onion"
// onionV2Dir is the directory we'll use to store a v2 onion service's
// private key, such that it can be restored after restarts.
onionV2Dir = "v2"
// onionV2Dir is the directory we'll use to store a v3 onion service's
// private key, such that it can be restored after restarts.
onionV3Dir = "v3"
)
// onionPath returns the full path to an onion service's private key of the
// given type.
func onionPath(onionType tor.OnionType) (string, error) {
var typeDir string
switch onionType {
case tor.V2:
typeDir = onionV2Dir
case tor.V3:
typeDir = onionV3Dir
default:
return "", fmt.Errorf("unknown onion type %v", onionType)
}
return strings.Join(
[]string{topLevelKey, onionDir, typeDir}, etcdKeyDelimeter,
), nil
}
// onionStore is an etcd-based implementation of tor.OnionStore.
type onionStore struct {
*clientv3.Client
}
// A compile-time constraint to ensure onionStore implements tor.OnionStore.
var _ tor.OnionStore = (*onionStore)(nil)
// newOnionStore creates an etcd-based implementation of tor.OnionStore.
func newOnionStore(client *clientv3.Client) *onionStore {
return &onionStore{Client: client}
}
// StorePrivateKey stores the given private key.
func (s *onionStore) StorePrivateKey(onionType tor.OnionType,
privateKey []byte) error {
onionPath, err := onionPath(onionType)
if err != nil {
return err
}
_, err = s.Client.Put(context.Background(), onionPath, string(privateKey))
return err
}
// PrivateKey retrieves a stored private key. If it is not found, then
// ErrNoPrivateKey should be returned.
func (s *onionStore) PrivateKey(onionType tor.OnionType) ([]byte, error) {
onionPath, err := onionPath(onionType)
if err != nil {
return nil, err
}
resp, err := s.Get(context.Background(), onionPath)
if err != nil {
return nil, err
}
if len(resp.Kvs) == 0 {
return nil, tor.ErrNoPrivateKey
}
return resp.Kvs[0].Value, nil
}
// DeletePrivateKey securely removes the private key from the store.
func (s *onionStore) DeletePrivateKey(onionType tor.OnionType) error {
onionPath, err := onionPath(onionType)
if err != nil {
return err
}
_, err = s.Client.Delete(context.Background(), onionPath)
return err
}

81
onion_store_test.go Normal file
View File

@@ -0,0 +1,81 @@
package kirin
import (
"bytes"
"testing"
"github.com/lightningnetwork/lnd/tor"
)
// assertPrivateKeyExists is a helper to determine if the private key for an
// onion service exists in the store. If it does, it's compared against what's
// expected.
func assertPrivateKeyExists(t *testing.T, store *onionStore,
onionType tor.OnionType, expPrivateKey *[]byte) {
t.Helper()
exists := expPrivateKey != nil
privateKey, err := store.PrivateKey(onionType)
switch {
case exists && err != nil:
t.Fatalf("unable to retrieve private key: %v", err)
case !exists && err != tor.ErrNoPrivateKey:
t.Fatalf("expected error ErrNoPrivateKey, got \"%v\"", err)
case exists:
if !bytes.Equal(privateKey, *expPrivateKey) {
t.Fatalf("expected private key %v, got %v",
string(*expPrivateKey), string(privateKey))
}
default:
return
}
}
// TestOnionStore ensures the different operations of the onionStore behave as
// espected.
func TestOnionStore(t *testing.T) {
etcdClient, serverCleanup := etcdSetup(t)
defer etcdClient.Close()
defer serverCleanup()
// Upon a fresh initialization of the store, no private keys should
// exist for any onion service type.
store := newOnionStore(etcdClient)
assertPrivateKeyExists(t, store, tor.V2, nil)
assertPrivateKeyExists(t, store, tor.V3, nil)
// Store a private key for a V2 onion service and check it was stored
// correctly.
privateKeyV2 := []byte("hide_me_plz_v2")
if err := store.StorePrivateKey(tor.V2, privateKeyV2); err != nil {
t.Fatalf("unable to store private key for v2 onion service: %v",
err)
}
assertPrivateKeyExists(t, store, tor.V2, &privateKeyV2)
// Store a private key for a V3 onion service and check it was stored
// correctly.
privateKeyV3 := []byte("hide_me_plz_v3")
if err := store.StorePrivateKey(tor.V3, privateKeyV3); err != nil {
t.Fatalf("unable to store private key for v3 onion service: %v",
err)
}
assertPrivateKeyExists(t, store, tor.V3, &privateKeyV3)
// Delete the private key for the V2 onion service and check that it was
// indeed successful.
if err := store.DeletePrivateKey(tor.V2); err != nil {
t.Fatalf("unable to remove private key for v2 onion service: %v",
err)
}
assertPrivateKeyExists(t, store, tor.V2, nil)
// Delete the private key for the V3 onion service and check that it was
// indeed successful.
if err := store.DeletePrivateKey(tor.V3); err != nil {
t.Fatalf("unable to remove private key for v3 onion service: %v",
err)
}
assertPrivateKeyExists(t, store, tor.V3, nil)
}

View File

@@ -10,6 +10,13 @@ authenticator:
macdir: "/path/to/lnd/data/chain/bitcoin/simnet" macdir: "/path/to/lnd/data/chain/bitcoin/simnet"
network: "simnet" network: "simnet"
tor:
control: "localhost:9051"
listenport: 8082
virtualport: 8082
v2: false
v3: false
etcd: etcd:
host: "localhost:2379" host: "localhost:2379"
user: "user" user: "user"