First time a second set of eyes has looked at the code

The major work here has been:

1. Clean up some go idioms to make the code more idiomatic and simplify
   functions where possible to minimize number of lines where there was
   unnecessary code.

2. Document as many of the functions and packages as possible.

3. Add a test suite for the padding package.

4. Force 'go fmt' when 'make' is performed.

5. Better handling of errors throughout the code. There were quite a few
   that were ignored, now they are handled and reported on (e.g. if the
   JSON in the password vault is broken it will no longer be ignored, etc.)

6. Changed the names of a number of functions to make them clearer.

The code could now do with someone who knows it well to go through and
document everything clearly for release.
This commit is contained in:
John Graham-Cumming
2013-11-19 19:27:19 -08:00
parent d172058d5d
commit ab19d6a5cb
10 changed files with 704 additions and 517 deletions

View File

@@ -3,17 +3,16 @@ VERSION := 0.1
ITERATION := $(shell date +%s)
REVISION := $(shell git log -n1 --pretty=format:%h)
GOPATH := '$(CURDIR)'
export GOPATH := $(PWD)
BUILD_DEPS := go
.PHONY: all
all: $(NAME)
.PHONY: test
test:
GOPATH=$(GOPATH) go test $(NAME)/...
@go test $(NAME)/...
.PHONY: print-builddeps
print-builddeps:
@@ -24,9 +23,8 @@ $(NAME): bin/$(NAME)
SRC := $(shell find src/$(NAME) -type f)
bin/$(NAME): $(SRC)
GOPATH=$(GOPATH) go install -tags "$(TAGS)" -ldflags "$(LDFLAGS)" $(NAME)
@go fmt $(NAME)
@go install -tags "$(TAGS)" -ldflags "$(LDFLAGS)" $(NAME)
BUILD_PATH := build
INSTALL_PREFIX := usr/local
@@ -59,5 +57,7 @@ clean-package:
.PHONY: clean
clean: clean-package
GOPATH=$(GOPATH) go clean -i $(NAME)/...
$(RM) -r pkg
@go clean -i $(NAME)/...
@$(RM) -r pkg
print-%: ; @echo $*=$($*)

View File

@@ -7,56 +7,69 @@ package core
import (
"encoding/json"
"errors"
"fmt"
"log"
"redoctober/cryptor"
"redoctober/keycache"
"redoctober/passvault"
)
type credential struct {
// Each of these structures corresponds to the JSON expected on the
// correspondingly named URI (e.g. the delegate structure maps to the
// JSON that should be sent on the /delegate URI and it is handled by
// the Delegate function below).
type create struct {
Name string
Password string
}
// format of incoming sign-in request
type create struct {
credential
}
type summary struct {
credential
Name string
Password string
}
type delegate struct {
credential
Name string
Password string
Uses int
Time string
}
type password struct {
credential
Name string
Password string
NewPassword string
}
type encrypt struct {
credential
Name string
Password string
Minimum int
Owners []string
Data []byte
}
type decrypt struct {
credential
Name string
Password string
Data []byte
}
type modify struct {
credential
Name string
Password string
ToModify string
Command string
}
// response JSON format
// These structures map the JSON responses that will be sent from the API
type status struct {
Status string
}
@@ -72,250 +85,226 @@ type summaryData struct {
All map[string]passvault.Summary
}
func errToJson(err error) (ret []byte) {
if err == nil {
ret, _ = json.Marshal(status{Status: "ok"})
} else {
ret, _ = json.Marshal(status{Status: err.Error()})
}
return
// Helper functions that create JSON responses sent by core
func jsonStatusOk() ([]byte, error) {
return json.Marshal(status{Status: "ok"})
}
func jsonStatusError(err error) ([]byte, error) {
return json.Marshal(status{Status: err.Error()})
}
func jsonSummary() ([]byte, error) {
return json.Marshal(summaryData{Status: "ok",Live: keycache.GetSummary(), All: passvault.GetSummary()})
}
func jsonResponse(resp []byte) ([]byte, error) {
return json.Marshal(responseData{Status: "ok", Response: resp})
}
func summaryToJson(err error) (ret []byte) {
if err == nil {
ret, _ = json.Marshal(summaryData{Status: "ok", Live: keycache.GetSummary(), All: passvault.GetSummary()})
} else {
ret, _ = json.Marshal(status{Status: err.Error()})
}
return
}
func responseToJson(resp []byte, err error) (ret []byte) {
if err == nil {
ret, _ = json.Marshal(responseData{Status: "ok", Response: resp})
} else {
ret, _ = json.Marshal(status{Status: err.Error()})
}
return
}
func validateAdmin(name string, password string) (err error) {
// validateAdmin checks that the username and password passed in are
// correct and that the user is an admin
func validateAdmin(name, password string) error {
if passvault.NumRecords() == 0 {
return errors.New("Vault is not created yet")
}
// find record
passwordRec, ok := passvault.GetRecord(name)
pr, ok := passvault.GetRecord(name)
if !ok {
return errors.New("User not present")
}
err = passwordRec.ValidatePassword(password)
if err != nil {
return
if err := pr.ValidatePassword(password); err != nil {
return err
}
if !passwordRec.IsAdmin() {
if !pr.IsAdmin() {
return errors.New("Admin required")
}
return nil
}
// Init reads the records from disk from a given path
func Init(path string) (err error) {
if err = passvault.InitFromDisk(path); err != nil {
err = fmt.Errorf("Failed to load password vault %s: %s", path, err)
}
return
}
// Init reads the records from disk from a given path.
func Init(path string) {
passvault.InitFromDisk(path)
}
// Create processes a create request.
func Create(jsonIn []byte) []byte {
func Create(jsonIn []byte) ([]byte, error) {
var s create
err := json.Unmarshal(jsonIn, &s)
if err != nil {
return errToJson(err)
if err := json.Unmarshal(jsonIn, &s); err != nil {
return jsonStatusError(err)
}
if passvault.NumRecords() != 0 {
return errToJson(errors.New("Vault is already created"))
return jsonStatusError(errors.New("Vault is already created"))
}
if _, err := passvault.AddNewRecord(s.Name, s.Password, true); err != nil {
log.Printf("Error adding record for %s: %s\n", s.Name, err)
return jsonStatusError(err)
}
_, err = passvault.AddNewRecord(s.Name, s.Password, true)
if err != nil {
log.Println("Error adding record:", err)
return errToJson(err)
}
return errToJson(err)
return jsonStatusOk()
}
// Summary processes a summary request.
func Summary(jsonIn []byte) []byte {
func Summary(jsonIn []byte) ([]byte, error) {
var s summary
keycache.Refresh()
err := json.Unmarshal(jsonIn, &s)
if err != nil {
return errToJson(err)
if err := json.Unmarshal(jsonIn, &s); err != nil {
return jsonStatusError(err)
}
if passvault.NumRecords() == 0 {
return errToJson(errors.New("Vault is not created yet"))
return jsonStatusError(errors.New("Vault is not created yet"))
}
// validate admin
err = validateAdmin(s.Name, s.Password)
if err != nil {
log.Println("Error validating admin status", err)
return errToJson(err)
if err := validateAdmin(s.Name, s.Password); err != nil {
log.Printf("Error validating admin status of %s: %s", s.Name, err)
return jsonStatusError(err)
}
// populate
return summaryToJson(err)
return jsonSummary()
}
// Delegate processes a delegation request.
func Delegate(jsonIn []byte) []byte {
func Delegate(jsonIn []byte) ([]byte, error) {
var s delegate
err := json.Unmarshal(jsonIn, &s)
if err != nil {
return errToJson(err)
if err := json.Unmarshal(jsonIn, &s); err != nil {
return jsonStatusError(err)
}
if passvault.NumRecords() == 0 {
return errToJson(errors.New("Vault is not created yet"))
return jsonStatusError(errors.New("Vault is not created yet"))
}
// find record
passwordRec, ok := passvault.GetRecord(s.Name)
if ok {
err = passwordRec.ValidatePassword(s.Password)
if err != nil {
return errToJson(err)
// Find password record for user and verify that their password
// matches. If not found then add a new entry for this user.
pr, found := passvault.GetRecord(s.Name)
if found {
if err := pr.ValidatePassword(s.Password); err != nil {
return jsonStatusError(err)
}
} else {
passwordRec, err = passvault.AddNewRecord(s.Name, s.Password, false)
if err != nil {
log.Println("Error adding record:", err)
return errToJson(err)
var err error
if pr, err = passvault.AddNewRecord(s.Name, s.Password, false); err != nil {
log.Printf("Error adding record for %s: %s\n", s.Name, err)
return jsonStatusError(err)
}
}
// add signed-in record to active set
err = keycache.AddKeyFromRecord(passwordRec, s.Name, s.Password, s.Uses, s.Time)
if err != nil {
log.Println("Error adding key to cache:", err)
return errToJson(err)
if err := keycache.AddKeyFromRecord(pr, s.Name, s.Password, s.Uses, s.Time); err != nil {
log.Printf("Error adding key to cache for %s: %s\n", s.Name, err)
return jsonStatusError(err)
}
return errToJson(err)
return jsonStatusOk()
}
// Password processes a password change request.
func Password(jsonIn []byte) []byte {
func Password(jsonIn []byte) ([]byte, error) {
var s password
err := json.Unmarshal(jsonIn, &s)
if err != nil {
return errToJson(err)
if err := json.Unmarshal(jsonIn, &s); err != nil {
return jsonStatusError(err)
}
if passvault.NumRecords() == 0 {
return errToJson(errors.New("Vault is not created yet"))
return jsonStatusError(errors.New("Vault is not created yet"))
}
// add signed-in record to active set
err = passvault.ChangePassword(s.Name, s.Password, s.NewPassword)
if err != nil {
if err := passvault.ChangePassword(s.Name, s.Password, s.NewPassword); err != nil {
log.Println("Error changing password:", err)
return errToJson(err)
return jsonStatusError(err)
}
return errToJson(err)
return jsonStatusOk()
}
// Encrypt processes an encrypt request.
func Encrypt(jsonIn []byte) (ret []byte) {
func Encrypt(jsonIn []byte) ([]byte, error) {
var s encrypt
err := json.Unmarshal(jsonIn, &s)
if err != nil {
return errToJson(err)
if err := json.Unmarshal(jsonIn, &s); err != nil {
return jsonStatusError(err)
}
err = validateAdmin(s.Name, s.Password)
if err != nil {
if err := validateAdmin(s.Name, s.Password); err != nil {
log.Println("Error validating admin status", err)
return errToJson(err)
return jsonStatusError(err)
}
// Encrypt file with list of owners
resp, err := cryptor.Encrypt(s.Data, s.Owners, s.Minimum)
if err != nil {
if resp, err := cryptor.Encrypt(s.Data, s.Owners, s.Minimum); err != nil {
log.Println("Error encrypting:", err)
return errToJson(err)
return jsonStatusError(err)
} else {
return jsonResponse(resp)
}
return responseToJson(resp, err)
}
// Decrypt processes a decrypt request.
func Decrypt(jsonIn []byte) (ret []byte) {
func Decrypt(jsonIn []byte) ([]byte, error) {
var s decrypt
err := json.Unmarshal(jsonIn, &s)
if err != nil {
return errToJson(err)
return jsonStatusError(err)
}
err = validateAdmin(s.Name, s.Password)
if err != nil {
log.Println("Error validating admin status", err)
return errToJson(err)
return jsonStatusError(err)
}
resp, err := cryptor.Decrypt(s.Data)
if err != nil {
log.Println("Error decrypting:", err)
return errToJson(err)
return jsonStatusError(err)
}
return responseToJson(resp, err)
return jsonResponse(resp)
}
// Modify processes a modify request.
func Modify(jsonIn []byte) []byte {
func Modify(jsonIn []byte) ([]byte, error) {
var s modify
err := json.Unmarshal(jsonIn, &s)
if err != nil {
return errToJson(err)
if err := json.Unmarshal(jsonIn, &s); err != nil {
return jsonStatusError(err)
}
err = validateAdmin(s.Name, s.Password)
if err != nil {
log.Println("Error validating admin status", err)
return errToJson(err)
if err := validateAdmin(s.Name, s.Password); err != nil {
log.Printf("Error validating admin status of %s: %s", s.Name, err)
return jsonStatusError(err)
}
if _, ok := passvault.GetRecord(s.ToModify); !ok {
return errToJson(errors.New("Record to modify missing"))
return jsonStatusError(errors.New("Record to modify missing"))
}
if s.Name == s.ToModify {
return errToJson(errors.New("Cannot modify own record"))
return jsonStatusError(errors.New("Cannot modify own record"))
}
var err error
switch s.Command {
case "delete":
{
err = passvault.DeleteRecord(s.ToModify)
}
err = passvault.DeleteRecord(s.ToModify)
case "revoke":
{
err = passvault.RevokeRecord(s.ToModify)
}
err = passvault.RevokeRecord(s.ToModify)
case "admin":
{
err = passvault.MakeAdmin(s.ToModify)
}
err = passvault.MakeAdmin(s.ToModify)
default:
{
return errToJson(errors.New("Unknown command"))
}
return jsonStatusError(errors.New("Unknown command"))
}
if err != nil {
return jsonStatusError(err)
} else {
return jsonStatusOk()
}
return errToJson(err)
}

View File

@@ -1,6 +1,7 @@
// core_test.go: tests for core.go
//
// Copyright (c) 2013 CloudFlare, Inc.
package core
import (
@@ -8,7 +9,7 @@ import (
"os"
"redoctober/keycache"
"redoctober/passvault"
"redoctober/testing"
"testing"
)
func TestCreate(t *testing.T) {
@@ -17,10 +18,13 @@ func TestCreate(t *testing.T) {
os.Remove("/tmp/db1.json")
Init("/tmp/db1.json")
respJson := Create(createJson)
respJson, err := Create(createJson)
if err != nil {
t.Fatalf("Error in creating account, ", err)
}
var s responseData
err := json.Unmarshal(respJson, &s)
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in creating account, ", err)
}
@@ -29,7 +33,10 @@ func TestCreate(t *testing.T) {
}
// check to see if creation can happen twice
respJson = Create(createJson)
respJson, err = Create(createJson)
if err != nil {
t.Fatalf("Error in creating account when one exists, ", err)
}
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in creating account when one exists, ", err)
@@ -47,9 +54,12 @@ func TestSummary(t *testing.T) {
os.Remove("/tmp/db1.json")
// check for summary of uninitialized vault
respJson := Summary(createJson)
respJson, err := Summary(createJson)
if err != nil {
t.Fatalf("Error in summary of account with no vault,", err)
}
var s summaryData
err := json.Unmarshal(respJson, &s)
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in summary of account with no vault,", err)
}
@@ -60,7 +70,10 @@ func TestSummary(t *testing.T) {
Init("/tmp/db1.json")
// check for summary of initialized vault
respJson = Create(createJson)
respJson, err = Create(createJson)
if err != nil {
t.Fatalf("Error in creating account, ", err)
}
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in creating account, ", err)
@@ -69,7 +82,10 @@ func TestSummary(t *testing.T) {
t.Fatalf("Error in creating account, ", s.Status)
}
respJson = Summary(createJson)
respJson, err = Summary(createJson)
if err != nil {
t.Fatalf("Error in summary of account,", err)
}
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in summary of account,", err)
@@ -90,7 +106,10 @@ func TestSummary(t *testing.T) {
}
// check for summary of initialized vault with new member
respJson = Delegate(delegateJson)
respJson, err = Delegate(delegateJson)
if err != nil {
t.Fatalf("Error in delegating account,", err)
}
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in delegating account,", err)
@@ -99,7 +118,10 @@ func TestSummary(t *testing.T) {
t.Fatalf("Error in delegating account, ", s.Status)
}
respJson = Summary(createJson)
respJson, err = Summary(createJson)
if err != nil {
t.Fatalf("Error in summary of account with no vault,", err)
}
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in summary of account with no vault,", err)
@@ -141,7 +163,6 @@ func TestSummary(t *testing.T) {
t.Fatalf("Error in summary of account, record missing ")
}
//
keycache.FlushCache()
os.Remove("/tmp/db1.json")
@@ -159,8 +180,11 @@ func TestPassword(t *testing.T) {
// check for summary of initialized vault with new member
var s responseData
respJson := Create(createJson)
err := json.Unmarshal(respJson, &s)
respJson, err := Create(createJson)
if err != nil {
t.Fatalf("Error in creating account, ", err)
}
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in creating account, ", err)
}
@@ -168,7 +192,10 @@ func TestPassword(t *testing.T) {
t.Fatalf("Error in creating account, ", s.Status)
}
respJson = Delegate(delegateJson)
respJson, err = Delegate(delegateJson)
if err != nil {
t.Fatalf("Error in delegating account,", err)
}
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in delegating account,", err)
@@ -177,7 +204,10 @@ func TestPassword(t *testing.T) {
t.Fatalf("Error in delegating account, ", s.Status)
}
respJson = Password(passwordJson2)
respJson, err = Password(passwordJson2)
if err != nil {
t.Fatalf("Error in password", err)
}
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in password", err)
@@ -186,7 +216,10 @@ func TestPassword(t *testing.T) {
t.Fatalf("Error in password, ", s.Status)
}
respJson = Password(passwordJson)
respJson, err = Password(passwordJson)
if err != nil {
t.Fatalf("Error in password", err)
}
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in password", err)
@@ -195,7 +228,10 @@ func TestPassword(t *testing.T) {
t.Fatalf("Error in password, ", s.Status)
}
respJson = Delegate(delegateJson)
respJson, err = Delegate(delegateJson)
if err != nil {
t.Fatalf("Error in delegating account,", err)
}
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in delegating account,", err)
@@ -204,7 +240,10 @@ func TestPassword(t *testing.T) {
t.Fatalf("Error in delegating account, ", s.Status)
}
respJson = Delegate(delegateJson2)
respJson, err = Delegate(delegateJson2)
if err != nil {
t.Fatalf("Error in delegating account,", err)
}
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in delegating account,", err)
@@ -213,7 +252,10 @@ func TestPassword(t *testing.T) {
t.Fatalf("Error in delegating account, ", s.Status)
}
respJson = Password(passwordJson2)
respJson, err = Password(passwordJson2)
if err != nil {
t.Fatalf("Error in password", err)
}
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in password", err)
@@ -222,7 +264,10 @@ func TestPassword(t *testing.T) {
t.Fatalf("Error in password, ", s.Status)
}
respJson = Delegate(delegateJson)
respJson, err = Delegate(delegateJson)
if err != nil {
t.Fatalf("Error in delegating account,", err)
}
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in delegating account,", err)
@@ -251,16 +296,22 @@ func TestEncryptDecrypt(t *testing.T) {
// check for summary of initialized vault with new member
var s responseData
respJson := Create(delegateJson)
err := json.Unmarshal(respJson, &s)
respJson, err := Create(delegateJson)
if err != nil {
t.Fatalf("Error in creating account,", err)
}
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in creating account,", err)
}
if s.Status != "ok" {
t.Fatalf("Error in creating account, ", s.Status)
}
respJson, err = Delegate(delegateJson2)
if err != nil {
t.Fatalf("Error in delegating account,", err)
}
if s.Status != "ok" {
t.Fatalf("Error in delegating account, ", s.Status)
}
respJson = Delegate(delegateJson2)
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in delegating account,", err)
@@ -269,7 +320,10 @@ func TestEncryptDecrypt(t *testing.T) {
t.Fatalf("Error in delegating account, ", s.Status)
}
respJson = Delegate(delegateJson3)
respJson, err = Delegate(delegateJson3)
if err != nil {
t.Fatalf("Error in delegating account,", err)
}
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in delegating account,", err)
@@ -280,7 +334,10 @@ func TestEncryptDecrypt(t *testing.T) {
// check summary to see if none are delegated
keycache.Refresh()
respJson = Summary(summaryJson)
respJson, err = Summary(summaryJson)
if err != nil {
t.Fatalf("Error in summary,", err)
}
var sum summaryData
err = json.Unmarshal(respJson, &sum)
if err != nil {
@@ -294,7 +351,10 @@ func TestEncryptDecrypt(t *testing.T) {
}
// Encrypt with non-admin (fail)
respJson = Encrypt(encryptJson)
respJson, err = Encrypt(encryptJson)
if err != nil {
t.Fatalf("Error in encrypt,", err)
}
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in encrypt,", err)
@@ -304,7 +364,10 @@ func TestEncryptDecrypt(t *testing.T) {
}
// Encrypt
respJson = Encrypt(encryptJson2)
respJson, err = Encrypt(encryptJson2)
if err != nil {
t.Fatalf("Error in encrypt,", err)
}
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in encrypt,", err)
@@ -314,12 +377,15 @@ func TestEncryptDecrypt(t *testing.T) {
}
// decrypt file
decryptJson, err := json.Marshal(decrypt{Name: "Alice", Password: "Hello", In: s.Response})
decryptJson, err := json.Marshal(decrypt{Name: "Alice", Password: "Hello", Data: s.Response})
if err != nil {
t.Fatalf("Error in marshalling decryption,", err)
}
respJson2 := Decrypt(decryptJson)
respJson2, err := Decrypt(decryptJson)
if err != nil {
t.Fatalf("Error in decrypt,", err)
}
err = json.Unmarshal(respJson2, &s)
if err != nil {
t.Fatalf("Error in decrypt,", err)
@@ -329,7 +395,10 @@ func TestEncryptDecrypt(t *testing.T) {
}
// delegate two valid decryptors
respJson = Delegate(delegateJson4)
respJson, err = Delegate(delegateJson4)
if err != nil {
t.Fatalf("Error in delegating account,", err)
}
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in delegating account,", err)
@@ -338,7 +407,10 @@ func TestEncryptDecrypt(t *testing.T) {
t.Fatalf("Error in delegating account, ", s.Status)
}
respJson = Delegate(delegateJson5)
respJson, err = Delegate(delegateJson5)
if err != nil {
t.Fatalf("Error in delegating account,", err)
}
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in delegating account,", err)
@@ -350,7 +422,10 @@ func TestEncryptDecrypt(t *testing.T) {
// verify the presence of the two delgations
keycache.Refresh()
var sum2 summaryData
respJson = Summary(summaryJson)
respJson, err = Summary(summaryJson)
if err != nil {
t.Fatalf("Error in summary,", err)
}
err = json.Unmarshal(respJson, &sum2)
if err != nil {
t.Fatalf("Error in summary,", err)
@@ -362,7 +437,10 @@ func TestEncryptDecrypt(t *testing.T) {
t.Fatalf("Error in summary, ", sum2.Live)
}
respJson2 = Decrypt(decryptJson)
respJson2, err = Decrypt(decryptJson)
if err != nil {
t.Fatalf("Error in decrypt,", err)
}
err = json.Unmarshal(respJson2, &s)
if err != nil {
t.Fatalf("Error in decrypt,", err)
@@ -396,16 +474,22 @@ func TestModify(t *testing.T) {
// check for summary of initialized vault with new member
var s responseData
respJson := Create(delegateJson)
err := json.Unmarshal(respJson, &s)
respJson, err := Create(delegateJson)
if err != nil {
t.Fatalf("Error in creating account,", err)
}
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in creating account,", err)
}
if s.Status != "ok" {
t.Fatalf("Error in creating account, ", s.Status)
}
respJson, err = Delegate(delegateJson2)
if err != nil {
t.Fatalf("Error in delegating account,", err)
}
if s.Status != "ok" {
t.Fatalf("Error in delegating account, ", s.Status)
}
respJson = Delegate(delegateJson2)
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in delegating account,", err)
@@ -414,7 +498,10 @@ func TestModify(t *testing.T) {
t.Fatalf("Error in delegating account, ", s.Status)
}
respJson = Delegate(delegateJson3)
respJson, err = Delegate(delegateJson3)
if err != nil {
t.Fatalf("Error in delegating account,", err)
}
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in delegating account,", err)
@@ -425,7 +512,10 @@ func TestModify(t *testing.T) {
// check summary to see if none are delegated
keycache.Refresh()
respJson = Summary(summaryJson)
respJson, err = Summary(summaryJson)
if err != nil {
t.Fatalf("Error in summary,", err)
}
var sum summaryData
err = json.Unmarshal(respJson, &sum)
if err != nil {
@@ -439,7 +529,10 @@ func TestModify(t *testing.T) {
}
// Modify from non-admin (fail)
respJson = Modify(modifyJson)
respJson, err = Modify(modifyJson)
if err != nil {
t.Fatalf("Error in modify,", err)
}
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in modify,", err)
@@ -449,7 +542,10 @@ func TestModify(t *testing.T) {
}
// Modify self from admin (fail)
respJson = Modify(modifyJson2)
respJson, err = Modify(modifyJson2)
if err != nil {
t.Fatalf("Error in modify,", err)
}
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in modify,", err)
@@ -459,7 +555,10 @@ func TestModify(t *testing.T) {
}
// Modify admin from admin
respJson = Modify(modifyJson3)
respJson, err = Modify(modifyJson3)
if err != nil {
t.Fatalf("Error in modify,", err)
}
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in modify,", err)
@@ -468,7 +567,10 @@ func TestModify(t *testing.T) {
t.Fatalf("Error in modify, ", s.Status)
}
respJson = Summary(summaryJson)
respJson, err = Summary(summaryJson)
if err != nil {
t.Fatalf("Error in summary,", err)
}
err = json.Unmarshal(respJson, &sum)
if err != nil {
t.Fatalf("Error in summary,", err)
@@ -481,7 +583,10 @@ func TestModify(t *testing.T) {
}
// Revoke admin from admin
respJson = Modify(modifyJson4)
respJson, err = Modify(modifyJson4)
if err != nil {
t.Fatalf("Error in modify,", err)
}
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in modify,", err)
@@ -490,7 +595,10 @@ func TestModify(t *testing.T) {
t.Fatalf("Error in modify, ", s.Status)
}
respJson = Summary(summaryJson2)
respJson, err = Summary(summaryJson2)
if err != nil {
t.Fatalf("Error in summary,", err)
}
err = json.Unmarshal(respJson, &sum)
if err != nil {
t.Fatalf("Error in summary,", err)
@@ -503,7 +611,10 @@ func TestModify(t *testing.T) {
}
// Delete from admin
respJson = Modify(modifyJson5)
respJson, err = Modify(modifyJson5)
if err != nil {
t.Fatalf("Error in modify,", err)
}
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in modify,", err)
@@ -513,7 +624,10 @@ func TestModify(t *testing.T) {
}
var sum3 summaryData
respJson = Summary(summaryJson2)
respJson, err = Summary(summaryJson2)
if err != nil {
t.Fatalf("Error in summary,", err)
}
err = json.Unmarshal(respJson, &sum3)
if err != nil {
t.Fatalf("Error in summary,", err)

View File

@@ -22,23 +22,23 @@ const (
DEFAULT_VERSION = 1
)
// MultiWrappedKey is a structure containing
// a 16-byte key encrypted once for each of the keys corresponding to
// the names of the users in Name in order.
// MultiWrappedKey is a structure containing a 16-byte key encrypted
// once for each of the keys corresponding to the names of the users
// in Name in order.
type MultiWrappedKey struct {
Name []string
Key []byte
}
// SingleWrappedKey is a structure containing
// a 16-byte key encrypted by an RSA key.
// SingleWrappedKey is a structure containing a 16-byte key encrypted
// by an RSA key.
type SingleWrappedKey struct {
Key []byte
aesKey []byte
}
// EncryptedFile is the format for encrypted data containing all the keys necessary to
// decrypt it when delegated.
// EncryptedFile is the format for encrypted data containing all the
// keys necessary to decrypt it when delegated.
type EncryptedFile struct {
Version int
VaultId int
@@ -60,7 +60,8 @@ func makeRandom(length int) (bytes []byte, err error) {
return
}
// encrypt clearKey with the key associated with name inner, then name outer
// encrypt clearKey with the key associated with name inner, then name
// outer
func encryptKey(nameInner, nameOuter string, clearKey []byte, rsaKeys map[string]SingleWrappedKey) (out MultiWrappedKey, err error) {
out.Name = []string{nameOuter, nameInner}
@@ -109,12 +110,10 @@ func encryptKey(nameInner, nameOuter string, clearKey []byte, rsaKeys map[string
}
// double-wrap the keys
keyBytes, err = keycache.EncryptKey(clearKey, nameInner, overrideInner.aesKey)
if err != nil {
if keyBytes, err = keycache.EncryptKey(clearKey, nameInner, overrideInner.aesKey); err != nil {
return out, err
}
keyBytes, err = keycache.EncryptKey(keyBytes, nameOuter, overrideOuter.aesKey)
if err != nil {
if keyBytes, err = keycache.EncryptKey(keyBytes, nameOuter, overrideOuter.aesKey); err != nil {
return out, err
}
@@ -161,17 +160,17 @@ func unwrapKey(keys []MultiWrappedKey, rsaKeys map[string]SingleWrappedKey) (unw
// This requires a minimum of min keys to decrypt.
// NOTE: as currently implemented, the maximum value for min is 2.
func Encrypt(in []byte, names []string, min int) (resp []byte, err error) {
// decode data to encrypt
clearFile := padding.PadClearFile(in)
if min > 2 {
return nil, errors.New("Minimum restricted to 2")
}
// decode data to encrypt
clearFile := padding.AddPadding(in)
// set up encrypted data structure
var encrypted EncryptedFile
encrypted.Version = DEFAULT_VERSION
encrypted.VaultId, err = passvault.GetVaultId()
if err != nil {
if encrypted.VaultId, err = passvault.GetVaultId(); err != nil {
return
}
@@ -260,8 +259,7 @@ func Encrypt(in []byte, names []string, min int) (resp []byte, err error) {
func Decrypt(in []byte) (resp []byte, err error) {
// unwrap encrypted file
var encrypted EncryptedFile
err = json.Unmarshal(in, &encrypted)
if err != nil {
if err = json.Unmarshal(in, &encrypted); err != nil {
return
}
if encrypted.Version != DEFAULT_VERSION {
@@ -299,8 +297,7 @@ func Decrypt(in []byte) (resp []byte, err error) {
// decrypt file key with delegate keys
var unwrappedKey = make([]byte, 16)
unwrappedKey, err = unwrapKey(encrypted.KeySet, encrypted.KeySetRSA)
if err != nil {
if unwrappedKey, err = unwrapKey(encrypted.KeySet, encrypted.KeySetRSA); err != nil {
return
}

View File

@@ -25,7 +25,7 @@ type ActiveUser struct {
Type string
Expiry time.Time
Uses int
// non-public members
aesKey []byte
rsaKey rsa.PrivateKey
}
@@ -41,8 +41,7 @@ func setUser(in ActiveUser, name string) {
// mark a use of the key, only for decryption or symmetric encryption
func useKey(name string) {
val, present := matchUser(name)
if present {
if val, present := matchUser(name); present {
val.Uses -= 1
setUser(val, name)
}
@@ -71,7 +70,7 @@ func Refresh() {
}
// AddKeyFromRecord decrypts a key for a given record and adds it to the cache.
func AddKeyFromRecord(record passvault.DiskPasswordRecord, name string, password string, uses int, durationString string) (err error) {
func AddKeyFromRecord(record passvault.PasswordRecord, name string, password string, uses int, durationString string) (err error) {
var current ActiveUser
Refresh()

View File

@@ -4,7 +4,7 @@
package keycache
import (
"passvault"
"redoctober/passvault"
"testing"
"time"
)

View File

@@ -6,25 +6,41 @@ package padding
import "errors"
// RemovePadding removes padding from clear data.
func RemovePadding(bytesPadded []byte) ([]byte, error) {
// last byte is padding byte
paddingLen := int(bytesPadded[len(bytesPadded)-1])
if paddingLen > 16 {
// The final byte of a padded []byte indicates the number of padding
// bytes that were added. The padding bytes are always NUL bytes and
// up to 16 bytes may be added.
//
// Examples:
//
// 1. Data to be padded has a length divisible by 16. 16 bytes will be
// added where the first 15 are 0x00 and the final byte is 0x10.
//
// 2. Data to be padded has a length with remainder 15 when divided by
// 16. One byte will be added and that byte will be 0x01 (indicating
// one byte of padding).
//
// 3. Data to be padded has a length with remainder 2 when divided by
// 16. 14 bytes will be added. The first 13 will be 0x00 and then final
// byte will be 0x0e.
//
// Removing padding is trivial: the number of bytes specified by the
// final byte are removed.
// RemovePadding removes padding from data that was added with
// AddPadding
func RemovePadding(b []byte) ([]byte, error) {
l := int(b[len(b)-1])
if l > 16 {
return nil, errors.New("Padding incorrect")
}
fileLen := len(bytesPadded) - paddingLen
return bytesPadded[:fileLen], nil
return b[:len(b)-l], nil
}
// PadClearFile adds padding to clear file.
func PadClearFile(fileBytes []byte) (paddedFile []byte) {
// pad with zeros, last byte is the size of padding
paddingLen := 16 - len(fileBytes)%16
padding := make([]byte, paddingLen)
padding[paddingLen-1] = byte(paddingLen)
paddedFile = append(fileBytes, padding...)
return
// AddPadding adds padding to a block of data
func AddPadding(b []byte) []byte {
l := 16 - len(b)%16
padding := make([]byte, l)
padding[l-1] = byte(l)
return append(b, padding...)
}

View File

@@ -0,0 +1,73 @@
// padding_test.go: tests for padding.go
//
// Copyright (c) 2013 CloudFlare, Inc.
package padding
import (
"bytes"
"testing"
)
func assert(t *testing.T, b bool) {
if !b {
t.Fail()
}
}
func TestAddPadding(t *testing.T) {
b := make([]byte, 16)
c := AddPadding(b)
assert(t, len(c) == 32)
assert(t, c[31] == 16)
b = make([]byte, 32)
c = AddPadding(b)
assert(t, len(c) == 48)
assert(t, c[47] == 16)
b = make([]byte, 1)
c = AddPadding(b)
assert(t, len(c) == 16)
assert(t, c[15] == 15)
b = make([]byte, 15)
c = AddPadding(b)
assert(t, len(c) == 16)
assert(t, c[15] == 1)
}
func TestRemovePadding(t *testing.T) {
b := []byte("0123456789ABCDEF")
c := AddPadding(b)
assert(t, len(c) == 32)
assert(t, c[31] == 16)
assert(t, bytes.Compare(c[:16], b[:16]) == 0)
d, err := RemovePadding(c)
assert(t, err == nil)
assert(t, len(d) == 16)
assert(t, bytes.Compare(b, d) == 0)
b = []byte("0123456789")
c = AddPadding(b)
assert(t, len(c) == 16)
assert(t, c[15] == 6)
assert(t, bytes.Compare(c[:10], b[:10]) == 0)
d, err = RemovePadding(c)
assert(t, err == nil)
assert(t, len(d) == 10)
assert(t, bytes.Compare(b, d) == 0)
}
func TestDetectBadPadding(t *testing.T) {
b := []byte("0123456789ABCDEF")
c := AddPadding(b)
assert(t, len(c) == 32)
assert(t, c[31] == 16)
assert(t, bytes.Compare(c[:16], b[:16]) == 0)
c[31] = 42
d, err := RemovePadding(c)
assert(t, err != nil)
assert(t, d == nil)
}

View File

@@ -1,5 +1,6 @@
// Package passvault manages the vault containing user records on
// disk.
// disk. It contains usernames and associated passwords which are
// stored hashed (with salt) using scrypt.
//
// Copyright (c) 2013 CloudFlare, Inc.
@@ -19,36 +20,36 @@ import (
"io/ioutil"
"math/big"
mrand "math/rand"
"os"
"redoctober/padding"
)
// Constants for record type.
// Constants for record type
const (
AESRecord = "AES"
RSARecord = "RSA"
ECCRecord = "ECC"
)
// Constants for scrypt.
// Constants for scrypt
const (
KEYLENGTH = 16
N = 16384
R = 8
P = 1
KEYLENGTH = 16 // 16-byte output from scrypt
N = 16384 // Cost parameter
R = 8 // Block size
P = 1 // Parallelization factor
DEFAULT_VERSION = 1
)
// Set of encrypted records from disk
var records diskRecords
// Path of current vault
var localPath string
// DiskPasswordRecord is the set of password records on disk.
type DiskPasswordRecord struct {
// PasswordRecord is the structure used to store password and key
// material for a single user name. It is written and read from
// storage in JSON format.
type PasswordRecord struct {
Type string
Salt []byte
PasswordSalt []byte
HashedPassword []byte
KeySalt []byte
AESKey []byte
@@ -63,96 +64,88 @@ type DiskPasswordRecord struct {
}
Admin bool
}
// diskRecords is the structure used to read and write a JSON file
// containing the contents of a password vault
type diskRecords struct {
Version int
VaultId int
HmacKey []byte
Passwords map[string]DiskPasswordRecord
Passwords map[string]PasswordRecord
}
// Summary is a minmal account summary.
// records is the set of encrypted records read from disk and
// unmarshalled
var records diskRecords
// Summary is a minmial account summary.
type Summary struct {
Admin bool
Type string
}
// Intialization.
func init() {
// seed math.random from crypto.random
seedBytes, _ := makeRandom(8)
seedBuf := bytes.NewBuffer(seedBytes)
n64, _ := binary.ReadVarint(seedBuf)
mrand.Seed(n64)
}
// Take a password and derive a scrypt hashed version
func hashPassword(password string, salt []byte) (hashPass []byte, err error) {
// hashPassword takes a password and derives a scrypt salted and hashed
// version
func hashPassword(password string, salt []byte) ([]byte, error) {
return scrypt.Key([]byte(password), salt, N, R, P, KEYLENGTH)
}
// Helper to make new buffer full of random data
func makeRandom(length int) (bytes []byte, err error) {
bytes = make([]byte, 16)
n, err := rand.Read(bytes)
if n != len(bytes) || err != nil {
return
}
return
// makeRandom is a helper that makes a new buffer full of random data
func makeRandom(length int) ([]byte, error) {
bytes := make([]byte, length)
_, err := rand.Read(bytes)
return bytes, err
}
func encryptRSARecord(newRec *DiskPasswordRecord, rsaPriv *rsa.PrivateKey, passKey []byte) (err error) {
newRec.RSAKey.RSAExpIV, err = makeRandom(16)
if err != nil {
func encryptRSARecord(newRec *PasswordRecord, rsaPriv *rsa.PrivateKey, passKey []byte) (err error) {
if newRec.RSAKey.RSAExpIV, err = makeRandom(16); err != nil {
return
}
paddedExponent := padding.PadClearFile(rsaPriv.D.Bytes())
newRec.RSAKey.RSAExp, err = encryptCBC(paddedExponent, newRec.RSAKey.RSAExpIV, passKey)
if err != nil {
paddedExponent := padding.AddPadding(rsaPriv.D.Bytes())
if newRec.RSAKey.RSAExp, err = encryptCBC(paddedExponent, newRec.RSAKey.RSAExpIV, passKey); err != nil {
return
}
newRec.RSAKey.RSAPrimePIV, err = makeRandom(16)
if err != nil {
if newRec.RSAKey.RSAPrimePIV, err = makeRandom(16); err != nil {
return
}
paddedPrimeP := padding.PadClearFile(rsaPriv.Primes[0].Bytes())
newRec.RSAKey.RSAPrimeP, err = encryptCBC(paddedPrimeP, newRec.RSAKey.RSAPrimePIV, passKey)
if err != nil {
paddedPrimeP := padding.AddPadding(rsaPriv.Primes[0].Bytes())
if newRec.RSAKey.RSAPrimeP, err = encryptCBC(paddedPrimeP, newRec.RSAKey.RSAPrimePIV, passKey); err != nil {
return
}
newRec.RSAKey.RSAPrimeQIV, err = makeRandom(16)
if err != nil {
if newRec.RSAKey.RSAPrimeQIV, err = makeRandom(16); err != nil {
return
}
paddedPrimeQ := padding.PadClearFile(rsaPriv.Primes[1].Bytes())
paddedPrimeQ := padding.AddPadding(rsaPriv.Primes[1].Bytes())
newRec.RSAKey.RSAPrimeQ, err = encryptCBC(paddedPrimeQ, newRec.RSAKey.RSAPrimeQIV, passKey)
if err != nil {
return
}
return
}
// Create new record from username and password
func createPasswordRec(password string, admin bool) (newRec DiskPasswordRecord, err error) {
func createPasswordRec(password string, admin bool) (newRec PasswordRecord, err error) {
newRec.Type = RSARecord
newRec.Salt, err = makeRandom(16)
if err != nil {
if newRec.PasswordSalt, err = makeRandom(16); err != nil {
return
}
newRec.HashedPassword, err = hashPassword(password, newRec.Salt)
if err != nil {
if newRec.HashedPassword, err = hashPassword(password, newRec.PasswordSalt); err != nil {
return
}
newRec.KeySalt, err = makeRandom(16)
if err != nil {
if newRec.KeySalt, err = makeRandom(16); err != nil {
return
}
@@ -168,8 +161,7 @@ func createPasswordRec(password string, admin bool) (newRec DiskPasswordRecord,
}
// encrypt RSA key with password key
err = encryptRSARecord(&newRec, rsaPriv, passKey)
if err != nil {
if err = encryptRSARecord(&newRec, rsaPriv, passKey); err != nil {
return
}
@@ -181,8 +173,7 @@ func createPasswordRec(password string, admin bool) (newRec DiskPasswordRecord,
return
}
newRec.AESKey, err = encryptECB(aesKey, passKey)
if err != nil {
if newRec.AESKey, err = encryptECB(aesKey, passKey); err != nil {
return
}
@@ -191,13 +182,15 @@ func createPasswordRec(password string, admin bool) (newRec DiskPasswordRecord,
return
}
func derivePasswordKey(password string, keySalt []byte) (passwordKey []byte, err error) {
// derivePasswordKey generates a key from a password (and salt) using
// scrypt
func derivePasswordKey(password string, keySalt []byte) ([]byte, error) {
return scrypt.Key([]byte(password), keySalt, N, R, P, KEYLENGTH)
}
// Decrypt bytes using a key in ECB mode.
func decryptECB(data []byte, passwordKey []byte) (decryptedData []byte, err error) {
aesCrypt, err := aes.NewCipher(passwordKey)
// decryptECB decrypts bytes using a key in AES ECB mode.
func decryptECB(data, key []byte) (decryptedData []byte, err error) {
aesCrypt, err := aes.NewCipher(key)
if err != nil {
return
}
@@ -208,9 +201,9 @@ func decryptECB(data []byte, passwordKey []byte) (decryptedData []byte, err erro
return
}
// Decrypt bytes using a key in ECB mode.
func encryptECB(data []byte, passwordKey []byte) (encryptedData []byte, err error) {
aesCrypt, err := aes.NewCipher(passwordKey)
// encryptECB encrypts bytes using a key in AES ECB mode.
func encryptECB(data, key []byte) (encryptedData []byte, err error) {
aesCrypt, err := aes.NewCipher(key)
if err != nil {
return
}
@@ -221,9 +214,9 @@ func encryptECB(data []byte, passwordKey []byte) (encryptedData []byte, err erro
return
}
// Decrypt using a key and IV.
func decryptCBC(data []byte, iv []byte, passwordKey []byte) (decryptedData []byte, err error) {
aesCrypt, err := aes.NewCipher(passwordKey)
// decryptCBC decrypt bytes using a key and IV with AES in CBC mode.
func decryptCBC(data, iv, key []byte) (decryptedData []byte, err error) {
aesCrypt, err := aes.NewCipher(key)
if err != nil {
return
}
@@ -236,9 +229,9 @@ func decryptCBC(data []byte, iv []byte, passwordKey []byte) (decryptedData []byt
return
}
// Encrypt using a key and IV.
func encryptCBC(data []byte, iv []byte, passwordKey []byte) (encryptedData []byte, err error) {
aesCrypt, err := aes.NewCipher(passwordKey)
// encryptCBC encrypt data using a key and IV with AES in CBC mode.
func encryptCBC(data, iv, key []byte) (encryptedData []byte, err error) {
aesCrypt, err := aes.NewCipher(key)
if err != nil {
return
}
@@ -252,124 +245,127 @@ func encryptCBC(data []byte, iv []byte, passwordKey []byte) (encryptedData []byt
}
// InitFromDisk reads the record from disk and initialize global context.
func InitFromDisk(path string) {
func InitFromDisk(path string) error {
jsonDiskRecord, err := ioutil.ReadFile(path)
if err == nil {
err = json.Unmarshal(jsonDiskRecord, &records)
// It's OK for the file to be missing, we'll create it later if
// anything is added.
if err != nil && !os.IsNotExist(err) {
return err
}
// validate sizes
formatErr := false
// Initialized so that we can determine later if anything was read
// from the file.
records.Version = 0
if len(jsonDiskRecord) != 0 {
if err = json.Unmarshal(jsonDiskRecord, &records); err != nil {
return err
}
}
formatErr := errors.New("Format error")
for _, rec := range records.Passwords {
if len(rec.Salt) != 16 {
formatErr = true
if len(rec.PasswordSalt) != 16 {
return formatErr
}
if len(rec.HashedPassword) != 16 {
formatErr = true
return formatErr
}
if len(rec.KeySalt) != 16 {
formatErr = true
return formatErr
}
if rec.Type == AESRecord {
if len(rec.AESKey) != 16 {
formatErr = true
return formatErr
}
}
if rec.Type == RSARecord {
if len(rec.RSAKey.RSAExp) == 0 || len(rec.RSAKey.RSAExp)%16 != 0 {
formatErr = true
return formatErr
}
if len(rec.RSAKey.RSAPrimeP) == 0 || len(rec.RSAKey.RSAPrimeP)%16 != 0 {
formatErr = true
return formatErr
}
if len(rec.RSAKey.RSAPrimeQ) == 0 || len(rec.RSAKey.RSAPrimeQ)%16 != 0 {
formatErr = true
return formatErr
}
if len(rec.RSAKey.RSAExpIV) != 16 {
formatErr = true
return formatErr
}
if len(rec.RSAKey.RSAPrimePIV) != 16 {
formatErr = true
return formatErr
}
if len(rec.RSAKey.RSAPrimeQIV) != 16 {
formatErr = true
return formatErr
}
}
if formatErr {
err = errors.New("Format error")
break
}
}
if err != nil {
// If the Version field is 0 then it indicates that nothing was
// read from the file and so it needs to be initialized.
if records.Version == 0 {
records.Version = DEFAULT_VERSION
records.VaultId = mrand.Int()
records.HmacKey, err = makeRandom(16)
if err != nil {
return
return err
}
// make the record data holder
records.Passwords = make(map[string]DiskPasswordRecord)
records.Passwords = make(map[string]PasswordRecord)
}
localPath = path
return nil
}
// WriteRecordsToDisk saves the current state of the records to disk.
func WriteRecordsToDisk() (err error) {
func WriteRecordsToDisk() error {
if !IsInitialized() {
err = errors.New("Path not initialized")
return
return errors.New("Path not initialized")
}
jsonDiskRecord, err := json.Marshal(records)
if err == nil {
err = ioutil.WriteFile(localPath, jsonDiskRecord, 0644)
if jsonDiskRecord, err := json.Marshal(records); err == nil {
return ioutil.WriteFile(localPath, jsonDiskRecord, 0644)
} else {
return err
}
return
}
// AddNewRecord adds a new record for a given username and password.
func AddNewRecord(name string, password string, admin bool) (passwordRec DiskPasswordRecord, err error) {
passwordRec, err = createPasswordRec(password, admin)
if err != nil {
return
func AddNewRecord(name, password string, admin bool) (PasswordRecord, error) {
if pr, err := createPasswordRec(password, admin); err == nil {
SetRecord(pr, name)
return pr, WriteRecordsToDisk()
} else {
return pr, err
}
SetRecord(passwordRec, name)
err = WriteRecordsToDisk()
if err != nil {
return
}
return
}
// ChangePassword changes the password for a given user.
func ChangePassword(name string, password string, newPassword string) (err error) {
// find and validate name and password
passwordRec, ok := GetRecord(name)
func ChangePassword(name, password, newPassword string) (err error) {
pr, ok := GetRecord(name)
if !ok {
err = errors.New("Record not present")
return
}
err = passwordRec.ValidatePassword(password)
if err != nil {
if err = pr.ValidatePassword(password); err != nil {
return
}
// decrypt key
var key []byte
var rsaKey rsa.PrivateKey
if passwordRec.Type == AESRecord {
key, err = passwordRec.GetKeyAES(password)
if pr.Type == AESRecord {
key, err = pr.GetKeyAES(password)
if err != nil {
return
}
} else if passwordRec.Type == RSARecord {
rsaKey, err = passwordRec.GetKeyRSA(password)
} else if pr.Type == RSARecord {
rsaKey, err = pr.GetKeyRSA(password)
if err != nil {
return
}
@@ -378,38 +374,30 @@ func ChangePassword(name string, password string, newPassword string) (err error
return
}
// create new salt
passwordRec.Salt, err = makeRandom(16)
if err != nil {
if pr.PasswordSalt, err = makeRandom(16); err != nil {
return
}
if pr.HashedPassword, err = hashPassword(newPassword, pr.PasswordSalt); err != nil {
return
}
// hash new password
passwordRec.HashedPassword, err = hashPassword(newPassword, passwordRec.Salt)
if err != nil {
if pr.KeySalt, err = makeRandom(16); err != nil {
return
}
// create new key salt
passwordRec.KeySalt, err = makeRandom(16)
if err != nil {
return
}
newPassKey, err := derivePasswordKey(newPassword, passwordRec.KeySalt)
newPassKey, err := derivePasswordKey(newPassword, pr.KeySalt)
if err != nil {
return
}
// encrypt original key with new password
if passwordRec.Type == AESRecord {
passwordRec.AESKey, err = encryptECB(key, newPassKey)
if pr.Type == AESRecord {
pr.AESKey, err = encryptECB(key, newPassKey)
if err != nil {
return
}
} else if passwordRec.Type == RSARecord {
} else if pr.Type == RSARecord {
// encrypt RSA key with password key
err = encryptRSARecord(&passwordRec, &rsaKey, newPassKey)
err = encryptRSARecord(&pr, &rsaKey, newPassKey)
if err != nil {
return
}
@@ -418,60 +406,52 @@ func ChangePassword(name string, password string, newPassword string) (err error
return
}
SetRecord(passwordRec, name)
SetRecord(pr, name)
// update disk record
err = WriteRecordsToDisk()
if err != nil {
return
}
return
return WriteRecordsToDisk()
}
// DeleteRecord deletes a given record.
func DeleteRecord(name string) error {
if _, ok := GetRecord(name); ok {
delete(records.Passwords, name)
} else {
return errors.New("Record missing")
return nil
}
return nil
return errors.New("Record missing")
}
// RevokeRecord removes admin status from a record.
func RevokeRecord(name string) error {
rec, ok := GetRecord(name)
if ok {
if rec, ok := GetRecord(name); ok {
rec.Admin = false
SetRecord(rec, name)
} else {
return errors.New("Record missing")
return nil
}
return nil
return errors.New("Record missing")
}
// MakeAdmin adds admin status to a given record.
func MakeAdmin(name string) error {
rec, ok := GetRecord(name)
if ok {
if rec, ok := GetRecord(name); ok {
rec.Admin = true
SetRecord(rec, name)
} else {
return errors.New("Record missing")
return nil
}
return nil
return errors.New("Record missing")
}
// SetRecord puts a record into the global status.
func SetRecord(passwordRec DiskPasswordRecord, name string) {
records.Passwords[name] = passwordRec
func SetRecord(pr PasswordRecord, name string) {
records.Passwords[name] = pr
}
// GetRecord returns a record given a name.
func GetRecord(name string) (passwordRec DiskPasswordRecord, ok bool) {
passwordRec, ok = records.Passwords[name]
return
func GetRecord(name string) (PasswordRecord, bool) {
dpr, found := records.Passwords[name]
return dpr, found
}
// GetVaultId returns the id of the current vault.
@@ -506,71 +486,70 @@ func NumRecords() int {
func GetSummary() (summary map[string]Summary) {
summary = make(map[string]Summary)
for name, pass := range records.Passwords {
tempName := Summary{pass.Admin, pass.Type}
summary[name] = tempName
summary[name] = Summary{pass.Admin, pass.Type}
}
return
}
// IsAdmin returns the admin status of the DiskPasswordRecord.
func (passwordRec DiskPasswordRecord) IsAdmin() bool {
return passwordRec.Admin
// IsAdmin returns the admin status of the PasswordRecord.
func (pr PasswordRecord) IsAdmin() bool {
return pr.Admin
}
// GetType returns the type status of the DiskPasswordRecord.
func (passwordRec DiskPasswordRecord) GetType() string {
return passwordRec.Type
// GetType returns the type status of the PasswordRecord.
func (pr PasswordRecord) GetType() string {
return pr.Type
}
// EncryptKey encrypts a 16-byte key with the RSA key of the record.
func (passwordRec DiskPasswordRecord) EncryptKey(in []byte) (out []byte, err error) {
return rsa.EncryptOAEP(sha1.New(), rand.Reader, &passwordRec.RSAKey.RSAPublic, in, nil)
func (pr PasswordRecord) EncryptKey(in []byte) (out []byte, err error) {
return rsa.EncryptOAEP(sha1.New(), rand.Reader, &pr.RSAKey.RSAPublic, in, nil)
}
// GetKeyAES returns the 16-byte key of the record.
func (passwordRec DiskPasswordRecord) GetKeyAES(password string) (key []byte, err error) {
if passwordRec.Type != AESRecord {
func (pr PasswordRecord) GetKeyAES(password string) (key []byte, err error) {
if pr.Type != AESRecord {
return nil, errors.New("Invalid function for record type")
}
err = passwordRec.ValidatePassword(password)
err = pr.ValidatePassword(password)
if err != nil {
return
}
passKey, err := derivePasswordKey(password, passwordRec.KeySalt)
passKey, err := derivePasswordKey(password, pr.KeySalt)
if err != nil {
return
}
return decryptECB(passwordRec.AESKey, passKey)
return decryptECB(pr.AESKey, passKey)
}
// GetKeyAES returns the RSA public key of the record.
func (passwordRec DiskPasswordRecord) GetKeyRSAPub() (out *rsa.PublicKey, err error) {
if passwordRec.Type != RSARecord {
func (pr PasswordRecord) GetKeyRSAPub() (out *rsa.PublicKey, err error) {
if pr.Type != RSARecord {
return out, errors.New("Invalid function for record type")
}
return &passwordRec.RSAKey.RSAPublic, err
return &pr.RSAKey.RSAPublic, err
}
// GetKeyAES returns the RSA private key of the record given the correct password.
func (passwordRec DiskPasswordRecord) GetKeyRSA(password string) (key rsa.PrivateKey, err error) {
if passwordRec.Type != RSARecord {
func (pr PasswordRecord) GetKeyRSA(password string) (key rsa.PrivateKey, err error) {
if pr.Type != RSARecord {
return key, errors.New("Invalid function for record type")
}
err = passwordRec.ValidatePassword(password)
err = pr.ValidatePassword(password)
if err != nil {
return
}
passKey, err := derivePasswordKey(password, passwordRec.KeySalt)
passKey, err := derivePasswordKey(password, pr.KeySalt)
if err != nil {
return
}
rsaExponentPadded, err := decryptCBC(passwordRec.RSAKey.RSAExp, passwordRec.RSAKey.RSAExpIV, passKey)
rsaExponentPadded, err := decryptCBC(pr.RSAKey.RSAExp, pr.RSAKey.RSAExpIV, passKey)
if err != nil {
return
}
@@ -579,7 +558,7 @@ func (passwordRec DiskPasswordRecord) GetKeyRSA(password string) (key rsa.Privat
return
}
rsaPrimePPadded, err := decryptCBC(passwordRec.RSAKey.RSAPrimeP, passwordRec.RSAKey.RSAPrimePIV, passKey)
rsaPrimePPadded, err := decryptCBC(pr.RSAKey.RSAPrimeP, pr.RSAKey.RSAPrimePIV, passKey)
if err != nil {
return
}
@@ -588,7 +567,7 @@ func (passwordRec DiskPasswordRecord) GetKeyRSA(password string) (key rsa.Privat
return
}
rsaPrimeQPadded, err := decryptCBC(passwordRec.RSAKey.RSAPrimeQ, passwordRec.RSAKey.RSAPrimeQIV, passKey)
rsaPrimeQPadded, err := decryptCBC(pr.RSAKey.RSAPrimeQ, pr.RSAKey.RSAPrimeQIV, passKey)
if err != nil {
return
}
@@ -597,7 +576,7 @@ func (passwordRec DiskPasswordRecord) GetKeyRSA(password string) (key rsa.Privat
return
}
key.PublicKey = passwordRec.RSAKey.RSAPublic
key.PublicKey = pr.RSAKey.RSAPublic
key.D = big.NewInt(0).SetBytes(rsaExponent)
key.Primes = []*big.Int{big.NewInt(0), big.NewInt(0)}
key.Primes[0].SetBytes(rsaPrimeP)
@@ -612,14 +591,14 @@ func (passwordRec DiskPasswordRecord) GetKeyRSA(password string) (key rsa.Privat
}
// ValidatePassword returns an error if the password is incorrect.
func (passwordRec DiskPasswordRecord) ValidatePassword(password string) (err error) {
sha, err := hashPassword(password, passwordRec.Salt)
if err != nil {
return
func (pr PasswordRecord) ValidatePassword(password string) error {
if h, err := hashPassword(password, pr.PasswordSalt); err != nil {
return err
} else {
if bytes.Compare(h, pr.HashedPassword) != 0 {
return errors.New("Wrong Password")
}
}
if bytes.Compare(sha, passwordRec.HashedPassword) != 0 {
return errors.New("Wrong Password")
}
return
return nil
}

View File

@@ -12,77 +12,65 @@ import (
"flag"
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
"os"
"redoctober/core"
"runtime"
)
// List of URLs to register
// List of URLs to register and their related functions
const (
Create string = "/create"
Summary = "/summary"
Delegate = "/delegate"
Password = "/password"
Encrypt = "/encrypt"
Decrypt = "/decrypt"
Modify = "/modify"
)
// The channel handling user requests
var process = make(chan userRequest)
var functions = map[string]func([]byte) ([]byte, error){
"/create": core.Create,
"/summary": core.Summary,
"/delegate": core.Delegate,
"/password": core.Password,
"/encrypt": core.Encrypt,
"/decrypt": core.Decrypt,
"/modify": core.Modify,
}
type userRequest struct {
rt string
in []byte
resp chan []byte
rt string // The request type (which will be one of the
// keys of the functions map above
in []byte // Arbitrary input data (depends on the core.*
// function called)
resp chan []byte // Channel down which a response is sent (the
// data sent will depend on the core.* function
// called to handle this request)
}
func init() {
go func() {
for {
req := <-process
switch {
case req.rt == Create:
req.resp <- core.Create(req.in)
case req.rt == Summary:
req.resp <- core.Summary(req.in)
case req.rt == Delegate:
req.resp <- core.Delegate(req.in)
case req.rt == Password:
req.resp <- core.Password(req.in)
case req.rt == Encrypt:
req.resp <- core.Encrypt(req.in)
case req.rt == Decrypt:
req.resp <- core.Decrypt(req.in)
case req.rt == Modify:
req.resp <- core.Modify(req.in)
default:
fmt.Printf("Unknown! %s\n", req.rt)
req.resp <- []byte("Unknown command")
}
}
}()
}
func queueRequest(requestType string, w http.ResponseWriter, r *http.Request, c *tls.ConnectionState) {
// queueRequest handles a single request receive on the JSON API for
// one of the functions named in the functions map above. It reads the
// request and sends it to the goroutine started in main() below for
// processing and then waits for the response.
func queueRequest(process chan userRequest, requestType string, w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
response := make(chan []byte, 1)
req := userRequest{rt: requestType, in: body, resp: response}
process <- req
response := make(chan []byte)
process <- userRequest{rt: requestType, in: body, resp: response}
code := <-response
w.Write(code)
if resp, ok := <-response; ok {
w.Write(resp)
} else {
http.Error(w, "Unknown request", http.StatusInternalServerError)
}
}
func NewServer(addr string, certPath string, keyPath string, caPath string) (*http.Server, *net.Listener, error) {
// NewServer starts an HTTPS server the handles the redoctober JSON
// API. Each of the URIs in the functions map above is setup with a
// separate HandleFunc. Each HandleFunc is an instance of queueRequest
// above.
//
// Returns a valid http.Server handling redoctober JSON requests (and
// its associated listener) or an error
func NewServer(process chan userRequest, addr string, certPath, keyPath, caPath string) (*http.Server, *net.Listener, error) {
mux := http.NewServeMux()
srv := http.Server{
Addr: addr,
@@ -90,8 +78,7 @@ func NewServer(addr string, certPath string, keyPath string, caPath string) (*ht
}
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
if err != nil {
fmt.Println(err)
return nil, nil, err
return nil, nil, fmt.Errorf("Error loading certificate (%s, %s): %s", certPath, keyPath, err)
}
config := tls.Config{
@@ -101,24 +88,23 @@ func NewServer(addr string, certPath string, keyPath string, caPath string) (*ht
PreferServerCipherSuites: true,
SessionTicketsDisabled: true,
}
config.Rand = rand.Reader
// create local cert pool if present
// If a caPath has been specified then a local CA is being used
// and not the system configuration.
if caPath != "" {
rootPool := x509.NewCertPool()
pemCert, err := ioutil.ReadFile(caPath)
if err != nil {
fmt.Println(err)
return nil, nil, err
return nil, nil, fmt.Errorf("Error reading %s: %s\n", caPath, err)
}
derCert, pemCert := pem.Decode(pemCert)
if derCert == nil {
return nil, nil, err
return nil, nil, fmt.Errorf("Error decoding CA certificate: %s\n", err)
}
cert, err := x509.ParseCertificate(derCert.Bytes)
if err != nil {
fmt.Println(err)
return nil, nil, err
return nil, nil, fmt.Errorf("Error parsing CA certificate: %s\n", err)
}
rootPool.AddCert(cert)
@@ -127,16 +113,14 @@ func NewServer(addr string, certPath string, keyPath string, caPath string) (*ht
conn, err := net.Listen("tcp", addr)
if err != nil {
fmt.Println(err)
return nil, nil, err
return nil, nil, fmt.Errorf("Error starting TCP listener on %s: %s\n", addr, err)
}
lstnr := tls.NewListener(conn, &config)
for _, action := range []string{Create, Summary, Delegate, Password, Encrypt, Decrypt, Modify} {
var requestType = action
for requestType := range functions {
mux.HandleFunc(requestType, func(w http.ResponseWriter, r *http.Request) {
queueRequest(requestType, w, r, r.TLS)
queueRequest(process, requestType, w, r)
})
}
@@ -148,8 +132,7 @@ const usage = `Usage:
redoctober -vaultpath <path> -addr <addr> -cert <path> -key <path> [-ca <path>]
example:
redoctober /tmp/diskrecord.json localhost:8080 cert.pem cert.key
redoctober -vaultpath /tmp/diskrecord.json -addr localhost:8080 -cert cert.pem -key cert.key
`
func main() {
@@ -172,7 +155,44 @@ func main() {
os.Exit(2)
}
core.Init(*vaultPath)
s, l, _ := NewServer(*addr, *certPath, *keyPath, *caPath)
s.Serve(*l)
if err := core.Init(*vaultPath); err != nil {
log.Fatalf(err.Error())
}
runtime.GOMAXPROCS(runtime.NumCPU())
// The core package is not safe to be shared across goroutines so
// this supervisor goroutine reads requests from the process
// channel and dispatches them to core for processes.
process := make(chan userRequest)
go func() {
for {
req := <-process
if f, ok := functions[req.rt]; ok {
r, err := f(req.in)
if err == nil {
req.resp <- r
} else {
log.Printf("Error handling %s: %s\n", req.rt, err)
}
} else {
log.Printf("Unknown user request received: %s\n", req.rt)
}
// Note that if an error occurs no message is sent down
// the channel and then channel is closed. The
// queueRequest function will see this as indication of an
// error.
close(req.resp)
}
}()
s, l, err := NewServer(process, *addr, *certPath, *keyPath, *caPath)
if err == nil {
s.Serve(*l)
} else {
log.Fatalf("Error starting redoctober server: %s\n", err)
}
}