mirror of
https://github.com/cloudflare/redoctober.git
synced 2026-05-28 10:40:49 +00:00
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
This commit is contained in:
12
README.md
12
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
|
||||
|
||||
27
core/core.go
27
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})
|
||||
}
|
||||
|
||||
@@ -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