Merge pull request #65 from ghedo/owners

Add support for listing required delegations for an encrypted secret.
This commit is contained in:
Kyle Isom
2015-06-17 18:10:58 -07:00
5 changed files with 178 additions and 0 deletions
+12
View File
@@ -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
+27
View File
@@ -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})
}
+55
View File
@@ -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\"}")
+52
View File
@@ -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
}
+32
View File
@@ -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(`<!DOCTYPE html>
<li><a href="#admin">Admin</a></li>
<li><a href="#encrypt">Encrypt</a></li>
<li><a href="#decrypt">Decrypt</a></li>
<li><a href="#owners">Owners</a></li>
</ul>
</div>
</div>
@@ -528,6 +530,22 @@ var indexHtml = []byte(`<!DOCTYPE html>
</form>
</div>
</section>
<hr />
<section class="row">
<div id="owners" class="col-md-6">
<h3>Get Owners</h3>
<form id="owners" class="ro-user-owners" role="form" action="/owners" method="post">
<div class="feedback owners-feedback"></div>
<div class="form-group">
<label for="owners-data">Data</label>
<textarea name="Data" class="form-control" id="owners-data" rows="5" required></textarea>
</div>
<button type="submit" class="btn btn-primary">Get Owners</button>
</form>
</div>
</section>
</div>
<footer id="footer" class="footer">
@@ -737,6 +755,20 @@ var indexHtml = []byte(`<!DOCTYPE html>
}
});
});
// Get owners
$('body').on('submit', 'form#owners', function(evt){
evt.preventDefault();
var $form = $(evt.currentTarget),
data = serialize($form);
submit( $form, {
data : data,
success : function(d){
$form.find('.feedback').empty().append( makeAlert({ type: 'success', message: '<p>Owners: '+d.Owners.sort().join(', ')+'</p>' }) );
}
});
});
});
</script>
</body>