Skip to content

Commit

Permalink
Expose password with API (#10)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Pierozi authored May 31, 2020
1 parent 7e71d88 commit 23e4989
Show file tree
Hide file tree
Showing 19 changed files with 922 additions and 104 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@
.idea
bin
peervault.db
.peervault-linux.db
.DS_Store
37 changes: 35 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -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
205 changes: 205 additions & 0 deletions src/business/exposure/control.go
Original file line number Diff line number Diff line change
@@ -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)
}
107 changes: 107 additions & 0 deletions src/business/exposure/model.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading

0 comments on commit 23e4989

Please sign in to comment.