From 23e498933ee951e662a0c344407023b9c8fe8dca Mon Sep 17 00:00:00 2001 From: Pierre Tomasina Date: Sun, 31 May 2020 19:26:04 +0200 Subject: [PATCH] Expose password with API (#10) * expose password with API * update secret model * HTTP API sharing request for secret * improve peer p2p design * mock keychain for linux peer insecure running * linux keychain mock * Use same DB Connection for all app * sharing request p2p exchange * Encryption / Decryption during secret exchange --- .gitignore | 1 + Makefile | 37 +++- src/business/exposure/control.go | 205 +++++++++++++++++++++ src/business/exposure/model.go | 107 +++++++++++ src/business/owner/model.go | 23 ++- src/business/secret/control.go | 22 +-- src/business/secret/model.go | 49 +++-- src/communication/control/control.go | 2 + src/communication/event/event.go | 7 +- src/communication/peer/peer.go | 128 +++++++++++--- src/communication/peer/secret.go | 256 ++++++++++++++++++++++++++- src/crypto/crypto.go | 15 ++ src/crypto/keychain.go | 17 +- src/crypto/linuxchain.go | 100 +++++++++++ src/database/bbolt.go | 28 ++- src/go.mod | 6 +- src/go.sum | 12 ++ src/identity/identity.go | 1 - src/main.go | 10 +- 19 files changed, 922 insertions(+), 104 deletions(-) create mode 100644 src/crypto/linuxchain.go diff --git a/.gitignore b/.gitignore index af1d2e5..5423a10 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,5 @@ .idea bin peervault.db +.peervault-linux.db .DS_Store diff --git a/Makefile b/Makefile index f980422..58316f0 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,41 @@ +GOVERSION=1.12 +DEFAULT_RELAY="/ip4/37.187.1.229/tcp/23003/ipfs/QmeFecyqtgzYx1TFN9vYTroMGNo3DELtDZ63FpjqUd6xfW" + +.peervault-linux.db: + touch .peervault-linux.db + +# Code Style +fmt: + go fmt + +lint: + golint src/... + +sanity: fmt lint + build: - cd src && go build -ldflags "-w" -o ../bin/peervault main.go + cd src && GOOS=darwin go build -ldflags "-w" -o ../bin/peervault main.go + +build-linux: + cd src && GOOS=linux go build -ldflags "-w" -o ../bin/peervault-linux main.go + +run-linux: build-linux .peervault-linux.db + docker run \ + --rm -it \ + -p 4445:4444 -p 5556:5555 \ + -v $$(pwd)/bin/peervault-linux:/usr/local/bin/peervault-linux \ + -v $$(pwd)/.peervault-linux.db:/var/peervault.db \ + golang:$(GOVERSION) \ + /usr/local/bin/peervault-linux \ + -dev \ + --log 9 \ + --apiAddr 0.0.0.0:4444 \ + --wsAddr 0.0.0.0:5555 \ + --relay "$(DEFAULT_RELAY)" \ + --bbolt /var/peervault.db run: build - ./bin/peervault -dev --log 9 --relay "/ip4/37.187.1.229/tcp/23003/ipfs/QmeFecyqtgzYx1TFN9vYTroMGNo3DELtDZ63FpjqUd6xfW" --bbolt /Users/pierozi/.peervault/bbolt-dev.db + ./bin/peervault -dev --log 9 --relay "$(DEFAULT_RELAY)" --bbolt /Users/pierozi/.peervault/bbolt-dev.db test: cd src && go test -v -coverprofile=/tmp/profile.out github.com/Power-LAB/PeerVault/crypto diff --git a/src/business/exposure/control.go b/src/business/exposure/control.go index e69de29..e50d4b1 100644 --- a/src/business/exposure/control.go +++ b/src/business/exposure/control.go @@ -0,0 +1,205 @@ +package exposure + +import ( + "encoding/json" + "fmt" + "github.com/Power-LAB/PeerVault/business/owner" + "github.com/Power-LAB/PeerVault/business/secret" + "github.com/Power-LAB/PeerVault/communication/peer" + "github.com/Power-LAB/PeerVault/crypto" + "github.com/google/uuid" + "github.com/op/go-logging" + "net/http" + "path" + "time" +) + +var ( + log = logging.MustGetLogger("peerVaultLogger") +) + +// Manage Secret GET / POST +func Controller(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + if !owner.PasswordVerification(r, true) { + http.Error(w, "{\"error\": \"X-OWNER-CODE is required\"}", http.StatusUnauthorized) + return + } + + switch r.Method { + case http.MethodGet: + getSecretValue(w, r) + default: + http.Error(w, "Invalid request method.", 405) + } +} + +// Manage Exposure Request +// POST : Create a request for sharing secret with other peer +// GET : List requests, both sent and received +// DELETE : Decline or remove request +func ControllerRequest(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + switch r.Method { + case http.MethodPost: + createShareRequest(w, r) + case http.MethodGet: + getShareRequest(w, r) + case http.MethodDelete: + deleteShareRequest(w, r) + case http.MethodPut: + shareResponse(w, r) + default: + http.Error(w, "Invalid request method.", 405) + } +} + +func getSecretValue(w http.ResponseWriter, r *http.Request) { + keyPath := []byte(path.Base(r.RequestURI)) + s, err := secret.FetchSecret(keyPath) + + if err == secret.ErrorSecretNotFound { + http.Error(w, "{\"error\": \"Secret Not Found\"}", http.StatusNotFound) + return + } + if err != nil { + log.Error(err) + w.Header().Set("Content-Type", "application/json") + http.Error(w, "{\"error\": \"internal server error\"}", http.StatusInternalServerError) + return + } + + o := owner.Owner{} + if o.FetchOwner() != nil { + log.Notice(err) + http.Error(w, "{\"error\": \"Owner not found\"}", http.StatusNotFound) + } + identity, err := o.GetIdentity() + if err != nil { + log.Debug("Cannot find Identity of current owner") + log.Error(err) + http.Error(w, "{\"error\": \"Cannot find Identity of current owner\"}", http.StatusInternalServerError) + } + plainText, err := crypto.DecryptAes(identity.GetChildKeyAsByte(), []byte(s.Value)) + if err != nil { + log.Debug("Error during secret decipher") + log.Error(err) + http.Error(w, "{\"error\": \"Secret cannot be decrypted\"}", http.StatusInternalServerError) + } + s.Value = string(plainText) + + resultJson, _ := json.Marshal(s) + w.WriteHeader(http.StatusOK) + _, _ = w.Write(resultJson) +} + +func createShareRequest(w http.ResponseWriter, r *http.Request) { + shareRequest := &ShareRequest{} + // Verify and decode Owner input data + decoder := json.NewDecoder(r.Body) + err := decoder.Decode(&shareRequest) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte("{\"error\": \"Payload must be struct of ShareRequest\"}")) + return + } + + // Check if KeyPath exist + _, err = secret.FetchSecret([]byte(shareRequest.KeyPath)) + if err == secret.ErrorSecretNotFound { + http.Error(w, "{\"error\": \"Not secret found with KeyPath specified\"}", http.StatusNotFound) + return + } + if err != nil { + log.Error(err) + http.Error(w, "{\"error\": \"internal server error\"}", http.StatusInternalServerError) + return + } + + // Get Owner + o := owner.Owner{} + if o.FetchOwner() != nil { + log.Notice(err) + http.Error(w, "{\"error\": \"Owner not found\"}", http.StatusNotFound) + } + + // Create Share + share := &Share{ + Uuid: uuid.New().String(), + Sender: o.QmPeerId, + Receiver: shareRequest.Receiver, + Expiration: time.Now().UTC().Add(shareRequest.ExpirationDelay * time.Hour).Format(time.RFC3339), + KeyPath: shareRequest.KeyPath, + } + err = share.Save() + if err != nil { + log.Error(err) + http.Error(w, "{\"error\": \"internal server error\"}", http.StatusInternalServerError) + return + } + resultJson, _ := json.Marshal(share) + + go func() { + // Dial to receiver + err := peer.Dial(shareRequest.Receiver, peer.PidShareRequest, resultJson) + if err != nil { + log.Error("Error during share request dial") + } + }() + + w.WriteHeader(http.StatusOK) + _, _ = w.Write(resultJson) +} + +// Retrieved share request +func getShareRequest(w http.ResponseWriter, r *http.Request) { + shares, err := FetchShares() + if err != nil { + fmt.Printf("INTERNAL ERROR: %s", err.Error()) + http.Error(w, "{\"error\": \"internal server error\"}", http.StatusInternalServerError) + return + } + resultJson, _ := json.Marshal(shares) + w.WriteHeader(http.StatusOK) + _, _ = w.Write(resultJson) +} + +func deleteShareRequest(w http.ResponseWriter, r *http.Request) { + share := &Share{ + Uuid: path.Base(r.RequestURI), + } + err := share.Delete() + if err != nil { + log.Debug("Error during share request deletion") + log.Error(err) + http.Error(w, "{\"error\": \"internal server error\"}", http.StatusInternalServerError) + return + } + w.WriteHeader(http.StatusOK) +} + +func shareResponse(w http.ResponseWriter, r *http.Request) { + shareResponse := &ShareResponse{} + // Verify and decode Owner input data + decoder := json.NewDecoder(r.Body) + err := decoder.Decode(&shareResponse) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte("{\"error\": \"Payload must be struct of ShareRequest\"}")) + return + } + shareResponseJson, _ := json.Marshal(shareResponse) + + go func() { + // Approve the request locally to trust data when secret will arrive + if shareResponse.Approved { + peer.ApproveLocalRequest(shareResponse.Uuid) + } + // Dial to sender to confirm share + err := peer.Dial(shareResponse.Sender, peer.PidShareResponse, shareResponseJson) + if err != nil { + log.Error("Error during share response dial") + } + }() + w.WriteHeader(http.StatusOK) +} \ No newline at end of file diff --git a/src/business/exposure/model.go b/src/business/exposure/model.go index 85ee713..fa98907 100644 --- a/src/business/exposure/model.go +++ b/src/business/exposure/model.go @@ -1 +1,108 @@ package exposure + +import ( + "encoding/json" + "github.com/Power-LAB/PeerVault/database" + "go.etcd.io/bbolt" + "time" +) + +type Share struct { + Uuid string + Sender string + Receiver string + Expiration string + KeyPath string +} + +type ShareRequest struct { + Receiver string + KeyPath string + ExpirationDelay time.Duration // Hours during the sharing request will be valid +} + +type ShareResponse struct { + Uuid string + Sender string + Approved bool +} + +func (s *Share) Save() error { + db, err := database.GetConnection() + if err != nil { + return err + } + + // Share serialized to json. + buf, err := json.Marshal(&s) + if err != nil { + return err + } + + return db.Update(func(tx *bbolt.Tx) error { + var b *bbolt.Bucket + b = tx.Bucket([]byte("share")) + if b == nil { + log.Debug("bucket share is nil") + b2, err := tx.CreateBucket([]byte("share")) + if err != nil { + log.Debug("bucket share create error nil") + return err + } + b = b2 + } + return b.Put([]byte(s.Uuid), buf) + }) +} + +func (s *Share) Delete() error { + db, err := database.GetConnection() + if err != nil { + return err + } + + return db.Update(func(tx *bbolt.Tx) error { + var b *bbolt.Bucket + b = tx.Bucket([]byte("share")) + if b == nil { + return nil + } + log.Debugf("Delete the share uuid: %s", s.Uuid) + if err := b.Delete([]byte(s.Uuid)); err != nil { + return err + } + return nil + }) +} + +func FetchShares() ([]Share, error) { + db, err := database.GetConnection() + if err != nil { + return nil, err + } + var shares []Share + + err = db.View(func(tx *bbolt.Tx) error { + b := tx.Bucket([]byte("share")) + if b == nil { + return nil + } + c := b.Cursor() + + for k, v := c.First(); k != nil; k, v = c.Next() { + var share Share + err := json.Unmarshal(v, &share) + if err != nil { + return err + } + shares = append(shares, share) + } + + return nil + }) + if err != nil { + return nil, err + } + + return shares, nil +} \ No newline at end of file diff --git a/src/business/owner/model.go b/src/business/owner/model.go index d64b7c2..37032ac 100644 --- a/src/business/owner/model.go +++ b/src/business/owner/model.go @@ -2,7 +2,6 @@ package owner import ( "encoding/json" - "fmt" "github.com/Power-LAB/PeerVault/crypto" "github.com/Power-LAB/PeerVault/database" "github.com/Power-LAB/PeerVault/identity" @@ -26,11 +25,10 @@ type Owner struct { } func IsOwnerExist() (bool, error) { - db, err := database.Open() + db, err := database.GetConnection() if err != nil { return false, err } - defer db.Close() exist := false err = db.View(func(tx *bbolt.Tx) error { @@ -53,11 +51,10 @@ func (o *Owner) GetIdentity() (identity.PeerIdentity, error) { } func (o *Owner) FetchOwner() error { - db, err := database.Open() + db, err := database.GetConnection() if err != nil { return err } - defer db.Close() err = db.View(func(tx *bbolt.Tx) error { b := tx.Bucket([]byte("owner")) @@ -76,11 +73,10 @@ func (o *Owner) FetchOwner() error { } func (o *Owner) PutOwner() error { - db, err := database.Open() + db, err := database.GetConnection() if err != nil { return err } - defer db.Close() keychain := crypto.Keychain{} err = keychain.CreateOrOpen() @@ -98,14 +94,17 @@ func (o *Owner) PutOwner() error { return err } + code, _ := keychain.Get("UnlockCode", "OwnerCode") + log.Debugf("assert code %s", code) + return db.Update(func(tx *bbolt.Tx) error { var b *bbolt.Bucket b = tx.Bucket([]byte("owner")) if b == nil { - fmt.Println("Bucket owner is nil") + log.Debug("Bucket owner is nil") b2, err := tx.CreateBucket([]byte("owner")) if err != nil { - fmt.Println("Bucket Creation error") + log.Debug("Bucket Creation error") return err } b = b2 @@ -129,13 +128,13 @@ func PasswordVerification(r *http.Request, exposure bool) bool { // Always authorized when password is disabled if o.AskPassword == PasswordPolicyNone { - fmt.Println("PasswordVerification PasswordPolicyNone") + log.Debug("PasswordVerification PasswordPolicyNone") return true } - // Authorized when we are not expose secret and password policy is exposure only + // Authorized when we are not exposing secret and password policy is exposure only if !exposure && o.AskPassword == PasswordPolicyOnlyWhenExposure { - fmt.Println("PasswordVerification PasswordPolicyOnlyWhenExposure") + log.Debug("PasswordVerification PasswordPolicyOnlyWhenExposure") return true } diff --git a/src/business/secret/control.go b/src/business/secret/control.go index 62a4a4a..9377b26 100644 --- a/src/business/secret/control.go +++ b/src/business/secret/control.go @@ -19,14 +19,14 @@ func Controller(w http.ResponseWriter, r *http.Request) { } switch r.Method { - case http.MethodGet: - getSecrets(w, r) - case http.MethodPost: - createSecret(w, r) - case http.MethodDelete: - deleteSecret(w, r) - default: - http.Error(w, "Invalid request method.", 405) + case http.MethodGet: + getSecrets(w, r) + case http.MethodPost: + createSecret(w, r) + case http.MethodDelete: + deleteSecret(w, r) + default: + http.Error(w, "Invalid request method.", 405) } } @@ -67,8 +67,8 @@ func createSecret(w http.ResponseWriter, r *http.Request) { } o := owner.Owner{} if o.FetchOwner() != nil { - log.Error(err) - http.Error(w, "{\"error\": \"Owner not found\"}", http.StatusBadRequest) + log.Notice(err) + http.Error(w, "{\"error\": \"Owner not found\"}", http.StatusNotFound) } // Encrypt the secret before saving into bbolt @@ -76,7 +76,7 @@ func createSecret(w http.ResponseWriter, r *http.Request) { if err != nil { log.Debug("Cannot find Identity of current owner") log.Error(err) - http.Error(w, "{\"error\": \"Owner not found\"}", http.StatusInternalServerError) + http.Error(w, "{\"error\": \"Cannot find Identity of current owner\"}", http.StatusInternalServerError) } cipherSecretValue, err := crypto.EncryptAes(identity.GetChildKeyAsByte(), []byte(secret.Value)) if err != nil { diff --git a/src/business/secret/model.go b/src/business/secret/model.go index b38ffe9..119470f 100644 --- a/src/business/secret/model.go +++ b/src/business/secret/model.go @@ -7,21 +7,35 @@ package secret import ( "encoding/json" + "fmt" "github.com/Power-LAB/PeerVault/database" "github.com/op/go-logging" "go.etcd.io/bbolt" "regexp" ) +type Error int + +func (k Error) Error() (msg string) { + switch k { + case ErrorSecretNotFound: + msg = "The Secret key path, namespace and key name was not found" + } + return fmt.Sprintf("%s (%d)", msg, k) +} + const ( SecretTypePassword = iota // Secret ASCII Password SecretTypeRsa = iota // Secret RSA key + + ErrorSecretNotFound = Error(1) ) var ( log = logging.MustGetLogger("peerVaultLogger") ) + type Secret struct { // Public Namespace string @@ -39,11 +53,10 @@ func (secret *Secret) assertSecretStruct() bool { } func FetchSecrets() ([]Secret, error) { - db, err := database.Open() + db, err := database.GetConnection() if err != nil { return nil, err } - defer db.Close() var secrets []Secret err = db.View(func(tx *bbolt.Tx) error { @@ -74,37 +87,36 @@ func FetchSecrets() ([]Secret, error) { return secrets, nil } -// @deprecated -func (secret *Secret) FetchSecret() error { - db, err := database.Open() +func FetchSecret(keyPath []byte) (Secret, error) { + secret := &Secret{} + db, err := database.GetConnection() if err != nil { - return err + return *secret, err } - defer db.Close() err = db.View(func(tx *bbolt.Tx) error { b := tx.Bucket([]byte("secret")) - buf := b.Get([]byte("buf")) - err := json.Unmarshal(buf, secret) - if err != nil { - return err + if b == nil { + return ErrorSecretNotFound } - - return nil + buf := b.Get(keyPath) + if buf == nil { + return ErrorSecretNotFound + } + return json.Unmarshal(buf, secret) }) if err != nil { - return err + return *secret, err } - return nil + return *secret, nil } func (secret *Secret) CreateSecret() error { - db, err := database.Open() + db, err := database.GetConnection() if err != nil { return err } - defer db.Close() // Secret serialized to json. buf, err := json.Marshal(&secret) @@ -130,11 +142,10 @@ func (secret *Secret) CreateSecret() error { // keyPath are the fullpath of the key, namespace concat with key, spaced by dot func DeleteSecret(keyPath string) error { - db, err := database.Open() + db, err := database.GetConnection() if err != nil { return err } - defer db.Close() return db.Update(func(tx *bbolt.Tx) error { var b *bbolt.Bucket diff --git a/src/communication/control/control.go b/src/communication/control/control.go index ce099fe..5b8eb74 100644 --- a/src/communication/control/control.go +++ b/src/communication/control/control.go @@ -36,6 +36,8 @@ func Listen(address* string) { http.HandleFunc("/secret/", secret.Controller) http.HandleFunc("/expose/", exposure.Controller) + http.HandleFunc("/expose/request", exposure.ControllerRequest) + http.HandleFunc("/expose/request/", exposure.ControllerRequest) s := &http.Server{ Addr: *address, diff --git a/src/communication/event/event.go b/src/communication/event/event.go index c1c9d9a..7f1349f 100644 --- a/src/communication/event/event.go +++ b/src/communication/event/event.go @@ -22,7 +22,7 @@ var ( type Message struct { Type string `json:"type"` - Data interface{} `json:"data"` + Data map[string]string `json:"data"` } // Write response to all websocket client connected @@ -67,6 +67,7 @@ func process(w http.ResponseWriter, r *http.Request) { func Listen(address* string) { log.Info("listen from event") - http.HandleFunc("/process", process) - log.Fatal(http.ListenAndServe(*address, nil)) + http.HandleFunc("/events", process) + _ = http.ListenAndServe(*address, nil) + select {} } diff --git a/src/communication/peer/peer.go b/src/communication/peer/peer.go index a7f9969..0de6d15 100644 --- a/src/communication/peer/peer.go +++ b/src/communication/peer/peer.go @@ -8,72 +8,150 @@ package peer import ( + "bufio" "context" "fmt" + "github.com/Power-LAB/PeerVault/identity" + "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p-core/protocol" + "github.com/op/go-logging" "github.com/Power-LAB/PeerVault/business/owner" "github.com/libp2p/go-libp2p" + circuit "github.com/libp2p/go-libp2p-circuit" "github.com/libp2p/go-libp2p-core/peer" ma "github.com/multiformats/go-multiaddr" ) -func Listen(relayHost *string) { - exist, err := owner.IsOwnerExist() - if err != nil { - fmt.Printf("PEER INTERNAL ERROR: %s", err.Error()) - return - } - if exist == false { - fmt.Printf("Owner does not exist, Peer cannot start") +const ( + PidShareRequest protocol.ID = "/secret/share/request" + PidShareResponse protocol.ID = "/secret/share/response" + PidShareSecret protocol.ID = "/secret/share" +) + +var ( + log = logging.MustGetLogger("peerVaultLogger") + relayHost string + node host.Host +) + +func SetRelayHost(relay string) { + relayHost = relay +} + +func Listen() { + if exist, _ := owner.IsOwnerExist(); exist == false { + log.Warning("OWNER NOT SETUP, PEER P2P CANT CONNECT") return } - o := &owner.Owner{} - err = o.FetchOwner() + + log.Debug("Listen") + peerIdentity, err := getPeerIdentity() if err != nil { - fmt.Printf("PEER INTERNAL ERROR: %s", err.Error()) - return + log.Fatal(err) } // The context governs the lifetime of the libp2p node ctx, cancel := context.WithCancel(context.Background()) defer cancel() - peerIdentity, err := o.GetIdentity() - if err != nil { - panic(err) - } pvt, err := peerIdentity.GetCryptoPrivateKey() if err != nil { - panic(err) + log.Fatal(err) } // Zero out the listen addresses for the host, so it can only communicate via p2p-circuit - node, err := libp2p.New( + node, err = libp2p.New( ctx, libp2p.Identity(pvt), libp2p.ListenAddrs(), - libp2p.EnableRelay(), + libp2p.EnableRelay(circuit.OptDiscovery), ) if err != nil { - panic(err) + log.Fatal(err) } // Creates relay peer.AddrInfo - relayAddrInfo, err := p2pAddrInfo(*relayHost) + relayAddrInfo, err := p2pAddrInfo(relayHost) if err != nil { - panic(err) + log.Fatal(err) } if err := node.Connect(context.Background(), *relayAddrInfo); err != nil { - panic(err) + log.Fatal(err) } - // Now, to test things, let's set up a protocol handler on node - node.SetStreamHandler("/secret", secretProtocol) + // Define handle for sharing request protocol + node.SetStreamHandler(PidShareRequest, secretShareRequestProtocol) + node.SetStreamHandler(PidShareResponse, secretShareResponseProtocol) + node.SetStreamHandler(PidShareSecret, secretProtocol) + + log.Info("listen from peer") + log.Info(node.ID().Pretty()) + log.Info(node.Addrs()) select {} } +func Dial(recipient string, pid protocol.ID, data []byte) error { + recipientPeerId, err := peer.IDB58Decode(recipient) + if err != nil { + return err + } + log.Debugf("recipientPeerId %s", relayHost + "/p2p-circuit/p2p/" + recipientPeerId.Pretty()) + ma.SwapToP2pMultiaddrs() + relayAddr, err := ma.NewMultiaddr(relayHost + "/p2p-circuit/p2p/" + recipientPeerId.Pretty()) + if err != nil { + log.Fatal(err) + } + + recipientRelayInfo := peer.AddrInfo{ + ID: recipientPeerId, + Addrs: []ma.Multiaddr{relayAddr}, + } + + // Connect node to recipient + if err := node.Connect(context.Background(), recipientRelayInfo); err != nil { + log.Error("fail connect to recipient using relay") + return err + } + + // we're connected! + stream, err := node.NewStream(context.Background(), recipientPeerId, pid) + if err != nil { + log.Fatal("Fail opening protocol with other peer", err) + return err + } + rw := bufio.NewReadWriter(bufio.NewReader(stream), bufio.NewWriter(stream)) + _, err = rw.WriteString(fmt.Sprintf("%s\n", data)) + if err != nil { + return err + } + err = rw.Flush() + + return err +} + +func getPeerIdentity() (identity.PeerIdentity, error) { + emptyIdentity := identity.PeerIdentity{} + exist, err := owner.IsOwnerExist() + if err != nil { + log.Error("PEER INTERNAL ERROR: %s", err.Error()) + return emptyIdentity, err + } + if exist == false { + log.Error("Owner does not exist, Peer cannot start") + return emptyIdentity, err + } + o := &owner.Owner{} + err = o.FetchOwner() + if err != nil { + log.Error("PEER INTERNAL ERROR: %s", err.Error()) + return emptyIdentity, err + } + return o.GetIdentity() +} + // create peer addr info func p2pAddrInfo(addrStr string) (*peer.AddrInfo, error) { addr, err := ma.NewMultiaddr(addrStr) diff --git a/src/communication/peer/secret.go b/src/communication/peer/secret.go index 3b71140..c97b0bc 100644 --- a/src/communication/peer/secret.go +++ b/src/communication/peer/secret.go @@ -10,19 +10,263 @@ package peer import ( + "bufio" + "encoding/json" "fmt" + "github.com/Power-LAB/PeerVault/business/owner" + "github.com/Power-LAB/PeerVault/business/secret" "github.com/Power-LAB/PeerVault/communication/event" + "github.com/Power-LAB/PeerVault/crypto" + "github.com/Power-LAB/PeerVault/database" "github.com/libp2p/go-libp2p-core/network" + "go.etcd.io/bbolt" + "strings" ) +type ShareRequest struct { + Uuid string + Sender string + Receiver string + Expiration string + KeyPath string + Approved bool +} + +type Share struct { + Uuid string + Sender string + Receiver string + Expiration string + KeyPath string +} + +type ShareResponse struct { + Uuid string + Sender string + Approved bool +} + +type ShareResponseData struct { + Uuid string + Sender string + Secret secret.Secret +} + +type Error int + +func (k Error) Error() (msg string) { + switch k { + case ErrorShareNotFound: + msg = "The Secret key path, namespace and key name was not found" + } + return fmt.Sprintf("%s (%d)", msg, k) +} + +const ( + ErrorShareNotFound = Error(1) +) + +var ( + requests = make(map[string]ShareRequest) +) + +func ApproveLocalRequest(uuid string) { + if shareRequest, ok := requests[uuid]; ok { + shareRequest.Approved = true + requests[uuid] = shareRequest + } +} + +// Receive new request for sharing password +func secretShareRequestProtocol(s network.Stream) { + log.Debug("Peer secretShareRequestProtocol") + log.Debugf("remote are: %s\n", s.Conn().RemotePeer()) + rw := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s)) + buf, err := rw.ReadString('\n') + if err != nil { + log.Error(err) + return + } + shareRequest := &ShareRequest{} + // Verify and decode share request data + decoder := json.NewDecoder(strings.NewReader(buf)) + err = decoder.Decode(&shareRequest) + if err != nil { + log.Error(err) + return + } + + if shareRequest.Sender != s.Conn().RemotePeer().Pretty() { + log.Error("Share request corrupted, sender and remote peer are different") + return + } + requests[shareRequest.Uuid] = *shareRequest + + _ = event.Write(event.Message{ + Type: "secret.share.request", + Data: map[string]string { + "Sender": s.Conn().RemotePeer().Pretty(), + "Uuid": shareRequest.Uuid, + "SecretPath": shareRequest.KeyPath, + "Expiration": shareRequest.Expiration, + }, + }) + err = s.Close() + if err != nil { + log.Error(err) + } +} + +// Receive response confirmation for password sharing +func secretShareResponseProtocol(s network.Stream) { + log.Debug("Peer secretShareResponseProtocol") + log.Debugf("remote are: %s\n", s.Conn().RemotePeer()) + rw := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s)) + buf, err := rw.ReadString('\n') + if err != nil { + log.Error(err) + return + } + shareResponse := &ShareResponse{} + // Verify and decode share request data + decoder := json.NewDecoder(strings.NewReader(buf)) + err = decoder.Decode(&shareResponse) + if err != nil { + log.Error(err) + return + } + _ = s.Close() + + share, err := getDbShareRequest([]byte(shareResponse.Uuid)) + if err != nil { + if err == ErrorShareNotFound { + log.Errorf("share request not found with uuid %s", shareResponse.Uuid) + } else { + log.Error(err) + } + return + } + if shareResponse.Approved == false { + _ = event.Write(event.Message{ + Type: "secret.share.declined", + Data: map[string]string { + "Receiver": share.Receiver, + "SecretPath": share.KeyPath, + "Expiration": share.Expiration, + }, + }) + return + } + secretData, err := secret.FetchSecret([]byte(share.KeyPath)) + o := owner.Owner{} + if o.FetchOwner() != nil { + log.Error(err) + return + } + id, err := o.GetIdentity() + if err != nil { + log.Error(err) + return + } + plainText, err := crypto.DecryptAes(id.GetChildKeyAsByte(), []byte(secretData.Value)) + secretData.Value = string(plainText) + if err != nil { + log.Error(err) + return + } + responseData := &ShareResponseData{ + Uuid: share.Uuid, + Sender: share.Sender, + Secret: secretData, + } + secretJson, _ := json.Marshal(responseData) + err = Dial(share.Receiver, PidShareSecret, secretJson) + if err != nil { + log.Error(err) + return + } +} + +// Receive the password after share request exchange has been done func secretProtocol(s network.Stream) { - fmt.Printf("Meow! It worked! remote are: %s\n", s.Conn().RemotePeer()) - err := event.Write(event.Message{ - Type: "message", - Data: nil, + log.Debug("Peer secretProtocol") + log.Debugf("remote are: %s\n", s.Conn().RemotePeer()) + + rw := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s)) + buf, err := rw.ReadString('\n') + if err != nil { + log.Error(err) + return + } + shareResponseData := &ShareResponseData{} + // Verify and decode share request data + decoder := json.NewDecoder(strings.NewReader(buf)) + err = decoder.Decode(&shareResponseData) + if err != nil { + log.Error(err) + return + } + _ = s.Close() + shareRequest, ok := requests[shareResponseData.Uuid] + if !ok { + log.Errorf("share request not found with uuid %s", shareResponseData.Uuid) + } + if shareRequest.Approved == false { + log.Error("This should not append, share request has not been approved") + return + } + if shareRequest.Approved == true { + o := owner.Owner{} + if o.FetchOwner() != nil { + log.Error(err) + return + } + id, err := o.GetIdentity() + if err != nil { + log.Error(err) + return + } + cipherSecretValue, err := crypto.EncryptAes(id.GetChildKeyAsByte(), []byte(shareResponseData.Secret.Value)) + shareResponseData.Secret.Value = string(cipherSecretValue) + log.Debug(shareResponseData.Secret) + err = shareResponseData.Secret.CreateSecret() + if err != nil { + log.Error(err) + return + } + _ = event.Write(event.Message{ + Type: "secret.share.created", + Data: map[string]string { + "Sender": shareRequest.Sender, + "SecretPath": shareRequest.KeyPath, + "Type": string(shareResponseData.Secret.Type), + "Description": shareResponseData.Secret.Description, + }, + }) + } +} + +func getDbShareRequest(uuid []byte) (Share, error) { + share := &Share{} + db, err := database.GetConnection() + if err != nil { + return *share, err + } + + err = db.View(func(tx *bbolt.Tx) error { + b := tx.Bucket([]byte("share")) + if b == nil { + return ErrorShareNotFound + } + buf := b.Get(uuid) + if buf == nil { + return ErrorShareNotFound + } + return json.Unmarshal(buf, share) }) if err != nil { - fmt.Printf("SecretProtocol ERROR: %s", err.Error()) + return *share, err } - s.Close() + + return *share, nil } \ No newline at end of file diff --git a/src/crypto/crypto.go b/src/crypto/crypto.go index 0b2a4c3..6f9c939 100644 --- a/src/crypto/crypto.go +++ b/src/crypto/crypto.go @@ -15,6 +15,7 @@ import ( "crypto/cipher" "encoding/base64" "errors" + "fmt" "io" crand "crypto/rand" @@ -29,10 +30,24 @@ import ( "golang.org/x/crypto/ripemd160" ) +type Error int + var ( log = logging.MustGetLogger("peerVaultLogger") + ErrorKeychainValueAlreadyExists = Error(1) + ErrorKeychainKeyNotFound = Error(2) ) +func (k Error) Error() (msg string) { + switch k { + case ErrorKeychainValueAlreadyExists: + msg = "Value already exist on PeerVault keychain." + case ErrorKeychainKeyNotFound: + msg = "Key not found in PeerVault keychain" + } + return fmt.Sprintf("%s (%d)", msg, k) +} + // // Seed Integration // diff --git a/src/crypto/keychain.go b/src/crypto/keychain.go index 28bb813..2cd7616 100644 --- a/src/crypto/keychain.go +++ b/src/crypto/keychain.go @@ -1,3 +1,5 @@ +// +build darwin + // Copyright (c) 2019, Pierre Tomasina // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. @@ -8,7 +10,6 @@ package crypto import ( "errors" - "fmt" "github.com/keybase/go-keychain" ) @@ -16,12 +17,8 @@ const ( service = "PeerVault" ) -type Error int - var ( path = "peervault.keychain" - ErrorKeychainValueAlreadyExists = Error(1) - ErrorKeychainKeyNotFound = Error(2) ) func EnableDevMode() { @@ -29,16 +26,6 @@ func EnableDevMode() { path = "peervault-dev.keychain" } -func (k Error) Error() (msg string) { - switch k { - case ErrorKeychainValueAlreadyExists: - msg = "Value already exist on PeerVault keychain." - case ErrorKeychainKeyNotFound: - msg = "Key not found in PeerVault keychain" - } - return fmt.Sprintf("%s (%d)", msg, k) -} - type Keychain struct { keychain keychain.Keychain } diff --git a/src/crypto/linuxchain.go b/src/crypto/linuxchain.go new file mode 100644 index 0000000..1b67010 --- /dev/null +++ b/src/crypto/linuxchain.go @@ -0,0 +1,100 @@ +// +build linux + +// Copyright (c) 2019, Pierre Tomasina +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// Crypto package will manage the cryptography of the Vault +// - Store and retrieve private key from OSX Keychain +package crypto + +import ( + "fmt" + "github.com/Power-LAB/PeerVault/database" + "github.com/op/go-logging" + "go.etcd.io/bbolt" +) + +func EnableDevMode() { + log.Debug("Developer mode enabled using linux secret service 'peervault-dev'") +} + +type Keychain struct { + keychain string +} + +func (k *Keychain) CreateOrOpen() error { + fmt.Printf( + "\033[%dm%s\033[0m", + int(logging.ColorRed), + "!!! ATTENTION !!!\nTHE LINUX VERSION DOES NOT STORE SECURELY THE IDENTITY KEYS AS MACOS USING KEYCHAIN.\n" + + "THE LINUX VERSION IS NOT PRODUCTION READY\n\n") + return nil +} + +func (k *Keychain) Put(key string, value []byte, label string, forceUpdate bool) error { + db, err := database.GetConnection() + if err != nil { + return err + } + + keyPath := []byte(key + "." + label) + + return db.Update(func(tx *bbolt.Tx) error { + var b *bbolt.Bucket + b = tx.Bucket([]byte("keychain")) + if b == nil { + log.Debug("keychain bucket is nil") + b2, err := tx.CreateBucket([]byte("keychain")) + if err != nil { + log.Debug("bucket keychain create error nil") + return err + } + b = b2 + } + return b.Put(keyPath, value) + }) +} + +func (k *Keychain) Get(key string, label string) ([]byte, error) { + db, err := database.GetConnection() + if err != nil { + return nil, err + } + + keyPath := []byte(key + "." + label) + var value []byte + err = db.View(func(tx *bbolt.Tx) error { + b := tx.Bucket([]byte("keychain")) + if b == nil { + return ErrorKeychainKeyNotFound + } + // Strange BUG when using direct byte result signal SIGSEGV: segmentation violation + value = []byte(string(b.Get(keyPath))) + if value == nil { + return ErrorKeychainKeyNotFound + } + return nil + }) + return value, err +} + +func (k *Keychain) Delete(keyPath string) error { + db, err := database.GetConnection() + if err != nil { + return err + } + + return db.Update(func(tx *bbolt.Tx) error { + var b *bbolt.Bucket + b = tx.Bucket([]byte("keychain")) + if b == nil { + return nil + } + log.Debugf("Delete the key path: %s", keyPath) + if err := b.Delete([]byte(keyPath)); err != nil { + return err + } + return nil + }) +} \ No newline at end of file diff --git a/src/database/bbolt.go b/src/database/bbolt.go index 2eba08a..2d4900f 100644 --- a/src/database/bbolt.go +++ b/src/database/bbolt.go @@ -8,7 +8,7 @@ package database import ( "bytes" "encoding/binary" - "fmt" + "github.com/op/go-logging" "go.etcd.io/bbolt" "os" "path/filepath" @@ -17,6 +17,8 @@ import ( var ( dbFilePath string + dbConnectionRw *bbolt.DB + log = logging.MustGetLogger("peerVaultLogger") ) func SetDbPath(path string) { @@ -28,14 +30,28 @@ func GetDbPath() string { } // Open PeerVault database -func Open() (*bbolt.DB, error) { +func Open() error { + var err error _ = os.MkdirAll(filepath.Dir(dbFilePath), 0700) - db, err := bbolt.Open(dbFilePath, 0600, &bbolt.Options{Timeout: 1 * time.Second}) + dbConnectionRw, err = bbolt.Open(dbFilePath, 0600, &bbolt.Options{Timeout: 1 * time.Second}) if err != nil { - fmt.Println("DB Not accessible, must retry later") - return nil, err + log.Error("DB Not accessible, must retry later") + return err + } + return nil +} + +func Close() { + _ = dbConnectionRw.Close() +} + +func GetConnection() (*bbolt.DB, error) { + if dbConnectionRw == nil { + if err := Open(); err != nil { + return nil, err + } } - return db, nil + return dbConnectionRw, nil } // Convert integer into byte diff --git a/src/go.mod b/src/go.mod index aed4ffb..0b98be6 100644 --- a/src/go.mod +++ b/src/go.mod @@ -6,14 +6,18 @@ require ( github.com/FactomProject/basen v0.0.0-20150613233007-fe3947df716e // indirect github.com/FactomProject/btcutilecc v0.0.0-20130527213604-d3a63a5752ec // indirect github.com/cmars/basen v0.0.0-20150613233007-fe3947df716e // indirect + github.com/google/uuid v1.1.1 github.com/gorilla/websocket v1.4.1 github.com/keybase/go-keychain v0.0.0-20191220220820-f65a47cbe0b1 github.com/libp2p/go-libp2p v0.4.0 + github.com/libp2p/go-libp2p-circuit v0.1.3 github.com/libp2p/go-libp2p-core v0.2.3 github.com/multiformats/go-multiaddr v0.1.1 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/tyler-smith/go-bip32 v0.0.0-20170922074101-2c9cfd177564 github.com/tyler-smith/go-bip39 v1.0.2 go.etcd.io/bbolt v1.3.3 - golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392 + golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 + golang.org/x/lint v0.0.0-20200130185559-910be7a94367 // indirect + golang.org/x/tools v0.0.0-20200210192313-1ace956b0e17 // indirect ) diff --git a/src/go.sum b/src/go.sum index 92e2cd5..59c1746 100644 --- a/src/go.sum +++ b/src/go.sum @@ -330,10 +330,16 @@ golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443 h1:IcSOAf4PyMp3U3XbIEj1/x golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392 h1:ACG4HJsFiNMf47Y4PeRoebLNy/2lXT9EtprMuTFWt1M= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 h1:XQyxROzUlZH+WIQwySDgnISgOivlhjIEwaQaJEJrrN0= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367 h1:0IiAsCRByjO2QjX7ZPkw5oU9x+n1YqRL802rjC0c3Aw= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -348,6 +354,7 @@ golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAG golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/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-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/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-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -371,7 +378,12 @@ golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20181130052023-1c3d964395ce/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd h1:/e+gpKk9r3dJobndpTytxS2gOy6m5uvpg+ISQoEcusQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200210192313-1ace956b0e17 h1:a/Fd23DJvg1CaeDH0dYHahE+hCI0v9rFgxSNIThoUcM= +golang.org/x/tools v0.0.0-20200210192313-1ace956b0e17/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= diff --git a/src/identity/identity.go b/src/identity/identity.go index da1aa78..a762ede 100644 --- a/src/identity/identity.go +++ b/src/identity/identity.go @@ -40,7 +40,6 @@ func GetIdentity(QmPeerId string) (PeerIdentity, error) { log.Debugf("The private key of QmPeerId %s has not been found on KeyChain", QmPeerId) return *peerIdentity, err } - log.Debugf("%s", idJson) err = json.Unmarshal(idJson, peerIdentity) return *peerIdentity, err diff --git a/src/main.go b/src/main.go index 4e8639f..4a155c5 100644 --- a/src/main.go +++ b/src/main.go @@ -56,6 +56,9 @@ func main() { } database.SetDbPath(*dbFilePath) + if err := database.Open(); err != nil { + log.Fatal("Error during opening bbolt database") + } run(wsAddress, apiAddress, relayHost) } @@ -93,7 +96,7 @@ func configureLogger(logFilePath string, logLevel int) { fmt.Printf( "\033[%dm%s\033[0m", int(logging.ColorRed), - "!!! ATTENTION !!!\nDEBUG LOGGING MAY CONTAIN SENSIBLE INFORMATION SUCH AS CLEAR PRIVATE " + + "!!! ATTENTION !!!\nDEBUG LOGGING MAY CONTAIN SENSIBLE INFORMATION SUCH AS CLEAR PRIVATE "+ "KEY OR ANY DATA.\nIT SHOULD ONLY BE USED IN DEVELOPPER MODE\n\n") } logging.SetBackend(backendLogLeveled) @@ -107,7 +110,8 @@ func run(wsAddress *string, apiAddress *string, relayHost *string) { go control.Listen(apiAddress) // Start peer - go peer.Listen(relayHost) + peer.SetRelayHost(*relayHost) + go peer.Listen() select {} -} \ No newline at end of file +}