From 418356946508fa55ac867b54dea4d5c3c3fe751a Mon Sep 17 00:00:00 2001 From: Alessandro Ghedini Date: Mon, 15 Jun 2015 21:12:10 +0200 Subject: [PATCH] Add support for listing required delegations for an encrypted secret This patch adds the /owners API endpoint that returns the list of users that "own" the given secret. These are the users that can delegate their passwords for decrypting the secret. It also adds the "Get Owners" form in the web UI that uses the new API. Fixes #62 --- README.md | 12 ++++++++++ core/core.go | 27 +++++++++++++++++++++++ core/core_test.go | 55 ++++++++++++++++++++++++++++++++++++++++++++++ cryptor/cryptor.go | 52 +++++++++++++++++++++++++++++++++++++++++++ redoctober.go | 32 +++++++++++++++++++++++++++ 5 files changed, 178 insertions(+) diff --git a/README.md b/README.md index da0e27d..783092b 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,7 @@ format is POSTed and JSON is returned. - `/modify`: Modify permissions - `/encrypt`: Encrypt - `/decrypt`: Decrypt + - `/owners`: List owners of an encrypted secret. - `/summary`: Display summary of the delegates - `/password`: Change password - `/index`: Optionally, the server can host a static HTML file. @@ -178,6 +179,17 @@ If there aren't enough keys delegated you'll see: {"Status":"Need more delegated keys"} +### Owners + +Owners allows users to determine which delegations are needed to decrypt +a piece of data. + +Example query: + + $ curl --cacert cert/server.crt https://localhost:8080/owners \ + -d '{"Data":"eyJWZXJzaW9uIj...NSSllzPSJ9"}' + {"Status":"ok","Owners":["Alice","Bill","Cat","Dodo"]} + ### Password Password allows a user to change their password. This password change diff --git a/core/core.go b/core/core.go index f849ef9..49b7e88 100644 --- a/core/core.go +++ b/core/core.go @@ -73,6 +73,10 @@ type DecryptRequest struct { Data []byte } +type OwnersRequest struct { + Data []byte +} + type ModifyRequest struct { Name string Password string @@ -100,6 +104,11 @@ type DecryptWithDelegates struct { Delegates []string } +type OwnersData struct { + Status string + Owners []string +} + // Helper functions that create JSON responses sent by core func jsonStatusOk() ([]byte, error) { @@ -366,3 +375,21 @@ func Modify(jsonIn []byte) ([]byte, error) { return jsonStatusOk() } } + +// Owners processes a owners request. +func Owners(jsonIn []byte) ([]byte, error) { + var s OwnersRequest + err := json.Unmarshal(jsonIn, &s) + if err != nil { + log.Println("Error unmarshaling input:", err) + return jsonStatusError(err) + } + + names, err := crypt.GetOwners(s.Data) + if err != nil { + log.Println("Error listing owners:", err) + return jsonStatusError(err) + } + + return json.Marshal(OwnersData{Status: "ok", Owners: names}) +} diff --git a/core/core_test.go b/core/core_test.go index 688c8b6..fb5f7ec 100644 --- a/core/core_test.go +++ b/core/core_test.go @@ -7,7 +7,9 @@ package core import ( "bytes" "encoding/json" + "reflect" "os" + "sort" "testing" "github.com/cloudflare/redoctober/passvault" @@ -453,6 +455,59 @@ func TestEncryptDecrypt(t *testing.T) { } } +func TestOwners(t *testing.T) { + delegateJson := []byte("{\"Name\":\"Alice\",\"Password\":\"Hello\",\"Time\":\"0s\",\"Uses\":0}") + delegateJson2 := []byte("{\"Name\":\"Bob\",\"Password\":\"Hello\",\"Time\":\"0s\",\"Uses\":0}") + delegateJson3 := []byte("{\"Name\":\"Carol\",\"Password\":\"Hello\",\"Time\":\"0s\",\"Uses\":0}") + encryptJson := []byte("{\"Name\":\"Carol\",\"Password\":\"Hello\",\"Minumum\":2,\"Owners\":[\"Alice\",\"Bob\",\"Carol\"],\"Data\":\"SGVsbG8gSmVsbG8=\"}") + + var s ResponseData + var l OwnersData + + Init("memory") + + Create(delegateJson) + Delegate(delegateJson2) + Delegate(delegateJson3) + + // Encrypt with non-admin + respJson, err := Encrypt(encryptJson) + if err != nil { + t.Fatalf("Error in encrypt, %v", err) + } + err = json.Unmarshal(respJson, &s) + if err != nil { + t.Fatalf("Error in encrypt, %v", err) + } + if s.Status != "ok" { + t.Fatalf("Error in encrypt, %v", s.Status) + } + + // Get owners list + ownersJson, err := json.Marshal(OwnersRequest{Data: s.Response}) + if err != nil { + t.Fatalf("Error in owners, %v", err) + } + respJson, err = Owners(ownersJson) + if err != nil { + t.Fatalf("Error in owners, %v", err) + } + err = json.Unmarshal(respJson, &l) + if err != nil { + t.Fatalf("Error in owners, %v", err) + } + if l.Status != "ok" { + t.Fatalf("Error in owners, %v", l.Status) + } + + sort.Strings(l.Owners) + + expectedOwners := []string{"Alice", "Bob", "Carol"} + if !reflect.DeepEqual(l.Owners, expectedOwners) { + t.Fatalf("Owners list mismatch, %v", l.Owners) + } +} + func TestModify(t *testing.T) { summaryJson := []byte("{\"Name\":\"Alice\",\"Password\":\"Hello\"}") summaryJson2 := []byte("{\"Name\":\"Carol\",\"Password\":\"Hello\"}") diff --git a/cryptor/cryptor.go b/cryptor/cryptor.go index 7d92902..830b188 100644 --- a/cryptor/cryptor.go +++ b/cryptor/cryptor.go @@ -471,3 +471,55 @@ func (c *Cryptor) Decrypt(in []byte, user string) (resp []byte, names []string, resp, err = padding.RemovePadding(clearData) return } + +// GetOwners returns the list of users that can delegate their passwords +// to decrypt the given encrypted secret. +func (c *Cryptor) GetOwners(in []byte) (names []string, err error) { + // unwrap encrypted file + var encrypted EncryptedData + if err = json.Unmarshal(in, &encrypted); err != nil { + return + } + if encrypted.Version != DEFAULT_VERSION && encrypted.Version != -1 { + err = errors.New("Unknown version") + return + } + + hmacKey, err := c.records.GetHMACKey() + if err != nil { + return + } + + if err = encrypted.unlock(hmacKey); err != nil { + return + } + + // make sure file was encrypted with the active vault + vaultId, err := c.records.GetVaultID() + if err != nil { + return + } + if encrypted.VaultId != vaultId { + err = errors.New("Wrong vault") + return + } + + // compute HMAC + expectedMAC := encrypted.computeHmac(hmacKey) + if !hmac.Equal(encrypted.Signature, expectedMAC) { + err = errors.New("Signature mismatch") + return + } + + addedNames := make(map[string]bool) + for _, mwKey := range encrypted.KeySet { + for _, mwName := range mwKey.Name { + if !addedNames[mwName] { + names = append(names, mwName) + addedNames[mwName] = true + } + } + } + + return +} diff --git a/redoctober.go b/redoctober.go index 10768b5..459d55e 100644 --- a/redoctober.go +++ b/redoctober.go @@ -33,6 +33,7 @@ var functions = map[string]func([]byte) ([]byte, error){ "/password": core.Password, "/encrypt": core.Encrypt, "/decrypt": core.Decrypt, + "/owners": core.Owners, "/modify": core.Modify, } @@ -273,6 +274,7 @@ var indexHtml = []byte(`
  • Admin
  • Encrypt
  • Decrypt
  • +
  • Owners
  • @@ -528,6 +530,22 @@ var indexHtml = []byte(` +
    +
    +
    +

    Get Owners

    + +
    + + +
    + + +
    + +
    +
    +