From 95940ed3fa6c18437900ee5070c508d0a92193ca Mon Sep 17 00:00:00 2001 From: e Date: Tue, 4 Aug 2015 14:01:04 -0700 Subject: [PATCH] Add hipchat and ordering support to redoctober. Also fix XSS in RO Supports MSP and requires several arguments to add hipchat integration to red october. RedOctober will then alert on creation of an order, any new delegation, or several other states. --- core/core.go | 242 ++++++++++++++++++++++++++++-- core/core_test.go | 18 +-- cryptor/cryptor.go | 8 +- cryptor/cryptor_test.go | 3 +- hipchat/hipchat.go | 67 +++++++++ index.html | 272 ++++++++++++++++++++++++++++++++-- keycache/keycache.go | 37 ++++- order/order.go | 119 +++++++++++++++ passvault/passvault.go | 43 +++++- passvault/passvault_test.go | 6 +- redoctober.go | 284 ++++++++++++++++++++++++++++++++++-- 11 files changed, 1042 insertions(+), 57 deletions(-) create mode 100644 hipchat/hipchat.go create mode 100644 order/order.go diff --git a/core/core.go b/core/core.go index 51608ac..5bf5ec9 100644 --- a/core/core.go +++ b/core/core.go @@ -9,9 +9,13 @@ import ( "errors" "fmt" "log" + "strconv" + "time" "github.com/cloudflare/redoctober/cryptor" + "github.com/cloudflare/redoctober/hipchat" "github.com/cloudflare/redoctober/keycache" + "github.com/cloudflare/redoctober/order" "github.com/cloudflare/redoctober/passvault" ) @@ -19,6 +23,7 @@ var ( crypt cryptor.Cryptor records passvault.Records cache keycache.Cache + orders order.Orderer ) // Each of these structures corresponds to the JSON expected on the @@ -63,6 +68,7 @@ type PasswordRequest struct { Password string NewPassword string + HipchatName string } type EncryptRequest struct { @@ -106,6 +112,33 @@ type ExportRequest struct { Password string } +type OrderRequest struct { + Name string + Password string + Duration string + Uses string + Data []byte + Label string +} + +type OrderInfoRequest struct { + Name string + Password string + + OrderNum string +} +type OrderOutstandingRequest struct { + Name string + Password string +} + +type OrderCancelRequest struct { + Name string + Password string + + OrderNum string +} + // These structures map the JSON responses that will be sent from the API type ResponseData struct { @@ -169,8 +202,7 @@ func validateUser(name, password string, admin bool) error { return nil } -// validateName checks that the username and password pass the minimal -// validation check +// validateName checks that the username and password pass a validation test. func validateName(name, password string) error { if name == "" { return errors.New("User name must not be blank") @@ -183,21 +215,35 @@ func validateName(name, password string) error { } // Init reads the records from disk from a given path -func Init(path string) error { +func Init(ca, hcKey, hcRoom, hcHost, roHost string) error { var err error defer func() { if err != nil { log.Printf("core.init failed: %v", err) } else { - log.Printf("core.init success: path=%s", path) + log.Printf("core.init success: ca=%s", ca) } }() - if records, err = passvault.InitFrom(path); err != nil { - err = fmt.Errorf("failed to load password vault %s: %s", path, err) + if records, err = passvault.InitFrom(ca); err != nil { + err = fmt.Errorf("failed to load password vault %s: %s", ca, err) } + var hipchatClient hipchat.HipchatClient + if hcKey != "" && hcRoom != "" && hcHost != "" { + roomId, err := strconv.Atoi(hcRoom) + if err != nil { + return errors.New("core.init unable to use hipchat roomId provided") + } + hipchatClient = hipchat.HipchatClient{ + ApiKey: hcKey, + RoomId: roomId, + HcHost: hcHost, + RoHost: roHost, + } + } + orders = order.NewOrderer(hipchatClient) cache = keycache.Cache{UserKeys: make(map[keycache.DelegateIndex]keycache.ActiveUser)} crypt = cryptor.New(&records, &cache) @@ -352,6 +398,22 @@ func Delegate(jsonIn []byte) ([]byte, error) { return jsonStatusError(err) } + // Make sure we capture the number who have already delegated. + for _, delegatedUser := range s.Users { + for _, delegatedLabel := range s.Labels { + if orderKey, found := orders.FindOrder(delegatedUser, delegatedLabel); found { + order := orders.Orders[orderKey] + order.AdminsDelegated = append(order.AdminsDelegated, s.Name) + order.Delegated++ + orders.Orders[orderKey] = order + + // Notify the hipchat room that there was a new delegator + orders.NotifyDelegation(s.Name, delegatedLabel, delegatedUser, orderKey, s.Time) + + } + } + } + return jsonStatusOk() } @@ -423,7 +485,7 @@ func Password(jsonIn []byte) ([]byte, error) { } // add signed-in record to active set - err = records.ChangePassword(s.Name, s.Password, s.NewPassword) + err = records.ChangePassword(s.Name, s.Password, s.NewPassword, s.HipchatName) if err != nil { return jsonStatusError(err) } @@ -490,7 +552,7 @@ func ReEncrypt(jsonIn []byte) ([]byte, error) { return jsonStatusError(err) } - data, _, secure, err := crypt.Decrypt(s.Data, s.Name) + data, _, _, secure, err := crypt.Decrypt(s.Data, s.Name) if err != nil { return jsonStatusError(err) } @@ -535,7 +597,7 @@ func Decrypt(jsonIn []byte) ([]byte, error) { return jsonStatusError(err) } - data, names, secure, err := crypt.Decrypt(s.Data, s.Name) + data, allLabels, names, secure, err := crypt.Decrypt(s.Data, s.Name) if err != nil { return jsonStatusError(err) } @@ -551,6 +613,13 @@ func Decrypt(jsonIn []byte) ([]byte, error) { return jsonStatusError(err) } + // Cleanup any orders that have been fulfilled and notify the room. + for _, label := range allLabels { + if orderKey, found := orders.FindOrder(s.Name, label); found { + delete(orders.Orders, orderKey) + orders.NotifyOrderFulfilled(s.Name, orderKey) + } + } return jsonResponse(out) } @@ -661,3 +730,158 @@ func Export(jsonIn []byte) ([]byte, error) { return jsonResponse(out) } + +// Order will request delegations from other users. +func Order(jsonIn []byte) (out []byte, err error) { + var o OrderRequest + + defer func() { + if err != nil { + log.Printf("core.order failed: user=%s %v", o.Name, err) + } else { + log.Printf("core.order success: user=%s", o.Name) + } + }() + + if err = json.Unmarshal(jsonIn, &o); err != nil { + return jsonStatusError(err) + } + + if err := validateUser(o.Name, o.Password, false); err != nil { + return jsonStatusError(err) + } + + // Get the owners of the ciphertext. + owners, _, err := crypt.GetOwners(o.Data) + if err != nil { + jsonStatusError(err) + } + if o.Duration == "" { + err = errors.New("Duration required when placing an order.") + jsonStatusError(err) + } + if o.Uses == "" || o.Uses == "0" { + err = errors.New("Number of required uses necessary when placing an order.") + jsonStatusError(err) + } + cache.Refresh() + orderNum := order.GenerateNum() + + adminsDelegated, numDelegated := cache.DelegateStatus(o.Name, o.Label, owners) + duration, err := time.ParseDuration(o.Duration) + if err != nil { + jsonStatusError(err) + } + currentTime := time.Now() + expiryTime := currentTime.Add(duration) + ord := order.CreateOrder(o.Name, + o.Label, + orderNum, + currentTime, + expiryTime, + duration, + adminsDelegated, + owners, + numDelegated) + orders.Orders[orderNum] = ord + out, err = json.Marshal(ord) + + // Get a map to any alternative name we want to notify + altOwners := records.GetAltNamesFromName(orders.AlternateName, owners) + + // Let everyone on hipchat know there is a new order. + orders.NotifyNewOrder(o.Name, o.Duration, o.Label, o.Uses, orderNum, altOwners) + if err != nil { + return jsonStatusError(err) + } + return jsonResponse(out) +} + +// OrdersOutstanding will return a list of currently outstanding orders. +func OrdersOutstanding(jsonIn []byte) (out []byte, err error) { + var o OrderOutstandingRequest + + defer func() { + if err != nil { + log.Printf("core.ordersout failed: user=%s %v", o.Name, err) + } else { + log.Printf("core.ordersout success: user=%s", o.Name) + } + }() + + if err = json.Unmarshal(jsonIn, &o); err != nil { + return jsonStatusError(err) + } + + if err := validateUser(o.Name, o.Password, false); err != nil { + return jsonStatusError(err) + } + + out, err = json.Marshal(orders.Orders) + if err != nil { + return jsonStatusError(err) + } + return jsonResponse(out) +} + +// OrderInfo will return a list of currently outstanding order numbers. +func OrderInfo(jsonIn []byte) (out []byte, err error) { + var o OrderInfoRequest + + defer func() { + if err != nil { + log.Printf("core.order failed: user=%s %v", o.Name, err) + } else { + log.Printf("core.order success: user=%s", o.Name) + } + }() + + if err = json.Unmarshal(jsonIn, &o); err != nil { + return jsonStatusError(err) + } + if err := validateUser(o.Name, o.Password, false); err != nil { + return jsonStatusError(err) + } + + if ord, ok := orders.Orders[o.OrderNum]; ok { + if out, err = json.Marshal(ord); err != nil { + return jsonStatusError(err) + } else if len(out) == 0 { + return jsonStatusError(errors.New("No order with that number")) + } + + return jsonResponse(out) + } + return +} + +// OrderCancel will cancel an order given an order num +func OrderCancel(jsonIn []byte) (out []byte, err error) { + var o OrderCancelRequest + + defer func() { + if err != nil { + log.Printf("core.order failed: user=%s %v", o.Name, err) + } else { + log.Printf("core.order success: user=%s", o.Name) + } + }() + + if err = json.Unmarshal(jsonIn, &o); err != nil { + return jsonStatusError(err) + } + + if err := validateUser(o.Name, o.Password, false); err != nil { + return jsonStatusError(err) + } + + if ord, ok := orders.Orders[o.OrderNum]; ok { + if o.Name == ord.Name { + delete(orders.Orders, o.OrderNum) + out = []byte("Successfully removed order") + return jsonResponse(out) + } + } + err = errors.New("Invalid Order Number") + return jsonStatusError(err) +} diff --git a/core/core_test.go b/core/core_test.go index 9951f74..6b00f05 100644 --- a/core/core_test.go +++ b/core/core_test.go @@ -18,7 +18,7 @@ import ( func TestCreate(t *testing.T) { createJson := []byte("{\"Name\":\"Alice\",\"Password\":\"Hello\"}") - Init("memory") + Init("memory", "", "", "", "") respJson, err := Create(createJson) if err != nil { @@ -66,7 +66,7 @@ func TestSummary(t *testing.T) { t.Fatalf("Error in summary of account with no vault, %v", s.Status) } - Init("memory") + Init("memory", "", "", "", "") // check for summary of initialized vault respJson, err = Create(createJson) @@ -220,7 +220,7 @@ func TestCreateUser(t *testing.T) { createUserECCJson := []byte("{\"Name\":\"Cat\",\"Password\":\"Cheshire\",\"UserType\":\"ECC\"}") createVaultJson := []byte("{\"Name\":\"Alice\",\"Password\":\"Hello\"}") - Init("memory") + Init("memory", "", "", "", "") // Check that users cannot be created before a vault is respJson, err := CreateUser(createUserJson) @@ -292,7 +292,7 @@ func TestPassword(t *testing.T) { delegateJson2 := []byte("{\"Name\":\"Alice\",\"Password\":\"Olleh\",\"Time\":\"2h\",\"Uses\":1}") passwordJson2 := []byte("{\"Name\":\"Alice\",\"Password\":\"Olleh\",\"NewPassword\":\"Hello\"}") - Init("memory") + Init("memory", "", "", "", "") // check for summary of initialized vault with new member var s ResponseData @@ -405,7 +405,7 @@ func TestEncryptDecrypt(t *testing.T) { encryptJson2 := []byte("{\"Name\":\"Alice\",\"Password\":\"Hello\",\"Minimum\":2,\"Owners\":[\"Alice\",\"Bob\",\"Carol\"],\"Data\":\"SGVsbG8gSmVsbG8=\",\"Labels\":[\"blue\",\"red\"]}") encryptJson3 := []byte("{\"Name\":\"Alice\",\"Password\":\"Hello\",\"Minimum\":1,\"Owners\":[\"Alice\"],\"Data\":\"SGVsbG8gSmVsbG8=\"}") - Init("memory") + Init("memory", "", "", "", "") // check for summary of initialized vault with new member var s ResponseData @@ -632,7 +632,7 @@ func TestReEncrypt(t *testing.T) { delegateJson7 := []byte(`{"Name":"Carol","Password":"Hello","Time":"10s","Uses":2,"Users":["Alice"],"Labels":["red"]}`) encryptJson := []byte(`{"Name":"Carol","Password":"Hello","Minimum":2,"Owners":["Alice","Bob","Carol"],"Data":"SGVsbG8gSmVsbG8=","Labels":["blue"]}`) - Init("memory") + Init("memory", "", "", "", "") // check for summary of initialized vault with new member var s ResponseData @@ -812,7 +812,7 @@ func TestOwners(t *testing.T) { var s ResponseData var l OwnersData - Init("memory") + Init("memory", "", "", "", "") Create(delegateJson) Delegate(delegateJson2) @@ -868,7 +868,7 @@ func TestModify(t *testing.T) { modifyJson4 := []byte("{\"Name\":\"Carol\",\"Password\":\"Hello\",\"ToModify\":\"Alice\",\"Command\":\"revoke\"}") modifyJson5 := []byte("{\"Name\":\"Carol\",\"Password\":\"Hello\",\"ToModify\":\"Alice\",\"Command\":\"delete\"}") - Init("memory") + Init("memory", "", "", "", "") // check for summary of initialized vault with new member var s ResponseData @@ -1059,7 +1059,7 @@ func TestStatic(t *testing.T) { t.Fatalf("Error closing file, %v", err) } - Init("/tmp/db1.json") + Init("/tmp/db1.json", "", "", "", "") // check for summary of initialized vault with new member var s ResponseData diff --git a/cryptor/cryptor.go b/cryptor/cryptor.go index 2944fa6..c62a0cf 100644 --- a/cryptor/cryptor.go +++ b/cryptor/cryptor.go @@ -524,14 +524,14 @@ func (c *Cryptor) Encrypt(in []byte, labels []string, access AccessStructure) (r } // Decrypt decrypts a file using the keys in the key cache. -func (c *Cryptor) Decrypt(in []byte, user string) (resp []byte, names []string, secure bool, err error) { +func (c *Cryptor) Decrypt(in []byte, user string) (resp []byte, labels, names []string, secure bool, 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 { - return nil, nil, secure, errors.New("Unknown version") + return nil, nil, nil, secure, errors.New("Unknown version") } secure = encrypted.Version == -1 @@ -551,7 +551,7 @@ func (c *Cryptor) Decrypt(in []byte, user string) (resp []byte, names []string, return } if encrypted.VaultId != vaultId { - return nil, nil, secure, errors.New("Wrong vault") + return nil, nil, nil, secure, errors.New("Wrong vault") } // compute HMAC @@ -579,6 +579,7 @@ func (c *Cryptor) Decrypt(in []byte, user string) (resp []byte, names []string, aesCBC.CryptBlocks(clearData, encrypted.Data) resp, err = padding.RemovePadding(clearData) + labels = encrypted.Labels return } @@ -637,7 +638,6 @@ func (c *Cryptor) GetOwners(in []byte) (names []string, predicate string, err er addedNames[name] = true } } - predicate = encrypted.Predicate return diff --git a/cryptor/cryptor_test.go b/cryptor/cryptor_test.go index 4b744ac..fa4727c 100644 --- a/cryptor/cryptor_test.go +++ b/cryptor/cryptor_test.go @@ -83,7 +83,6 @@ func TestDuplicates(t *testing.T) { if err != nil { t.Fatalf("%v", err) } - c := Cryptor{&records, &cache} for _, name := range names { @@ -113,7 +112,7 @@ func TestDuplicates(t *testing.T) { t.Fatalf("%v", err) } - _, _, _, err := c.Decrypt(resp, name) + _, _, _, _, err := c.Decrypt(resp, name) if err == nil { t.Fatalf("That shouldn't have worked!") } diff --git a/hipchat/hipchat.go b/hipchat/hipchat.go new file mode 100644 index 0000000..f6451f4 --- /dev/null +++ b/hipchat/hipchat.go @@ -0,0 +1,67 @@ +package hipchat + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "log" + "net/http" + "net/url" + "strconv" +) + +const ( + RedBackground = "red" + YellowBackground = "yellow" + GreenBackground = "green" + GrayBackground = "gray" + RandomBackground = "random" + PurpleBackground = "purple" +) + +type HipchatClient struct { + RoomId int + ApiKey string + HcHost string + RoHost string +} + +func NewClient() *HipchatClient { + return &HipchatClient{} +} +func (h *HipchatClient) Notify(msg, color string) error { + if h.ApiKey == "" { + return errors.New("ApiKey unset") + } + msgBody := map[string]interface{}{ + "message": msg, + "notify": "true", + "color": color, + } + + body, err := json.Marshal(msgBody) + if err != nil { + return err + } + roomId := url.QueryEscape(strconv.Itoa(h.RoomId)) + hipchatUrl := fmt.Sprintf("https://%s/v2/room/%s/notification?auth_token=%s", h.HcHost, roomId, h.ApiKey) + req, err := http.NewRequest("POST", hipchatUrl, bytes.NewReader(body)) + + req.Header.Add("Content-Type", "application/json") + + Client := http.Client{} + resp, err := Client.Do(req) + if err != nil { + log.Printf("Could not post to hipchat for the reason %s", err.Error()) + return err + } + + _, err = ioutil.ReadAll(resp.Body) + if err != nil { + log.Printf("Could not post to hipchat for the reason %s", err.Error()) + return err + } + return nil +} diff --git a/index.html b/index.html index ea9ef7e..0d28049 100644 --- a/index.html +++ b/index.html @@ -27,6 +27,7 @@
  • Encrypt
  • Decrypt
  • Owners
  • +
  • Order
  • @@ -164,7 +165,7 @@
    -

    Change password

    +

    Change account

    @@ -172,16 +173,18 @@
    - +
    - +
    - - + + + +
    @@ -309,6 +312,120 @@
    +
    +
    +
    +

    Create Order

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

    Order Info

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

    Outstanding Orders

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

    Order Cancel

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