mirror of
https://github.com/cloudflare/redoctober.git
synced 2026-09-19 22:24:12 +00:00
Merge pull request #65 from ghedo/owners
Add support for listing required delegations for an encrypted secret.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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})
|
||||
}
|
||||
|
||||
@@ -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\"}")
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user