Conflicts:
	src/redoctober/core/core_test.go
	src/redoctober/passvault/passvault_test.go
This commit is contained in:
Nick Sullivan
2013-11-20 08:32:11 -08:00
12 changed files with 827 additions and 613 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
bin/
pkg/
src/code.google.com/
*~

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

@@ -1,317 +1,310 @@
// Pacakge core handles the main operations of the Red October server.
// Package core handles the main operations of the Red October server.
//
// Copyright (c) 2013 CloudFlare, Inc.
package core
import (
"log"
"errors"
"encoding/json"
"redoctober/passvault"
"errors"
"fmt"
"log"
"redoctober/cryptor"
"redoctober/keycache"
"redoctober/passvault"
)
// format of incoming sign-in request
// 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
Name string
Password string
}
type summary struct {
Name string
Name string
Password string
}
type delegate struct {
Name string
Name string
Password string
Uses int
Time string
Uses int
Time string
}
type password struct {
Name string
Name string
Password string
NewPassword string
}
type encrypt struct {
Name string
Name string
Password string
Minimum int
Owners []string
Data []byte
Minimum int
Owners []string
Data []byte
}
type decrypt struct {
Name string
Name string
Password string
Data []byte
Data []byte
}
type modify struct {
Name string
Name string
Password string
ToModify string
Command string
Command string
}
// response JSON format
// These structures map the JSON responses that will be sent from the API
type status struct {
Status string
}
type responseData struct {
Status string
Status string
Response []byte
}
type summaryData struct {
Status string
Live map[string]keycache.ActiveUser
All map[string]passvault.Summary
Live map[string]keycache.ActiveUser
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"))
}
switch s.Command {
case "delete": {
err = passvault.DeleteRecord(s.ToModify)
}
case "revoke": {
err = passvault.RevokeRecord(s.ToModify)
}
case "admin": {
err = passvault.MakeAdmin(s.ToModify)
}
default: {
return errToJson(errors.New("Unknown command"))
}
}
return errToJson(err)
}
var err error
switch s.Command {
case "delete":
err = passvault.DeleteRecord(s.ToModify)
case "revoke":
err = passvault.RevokeRecord(s.ToModify)
case "admin":
err = passvault.MakeAdmin(s.ToModify)
default:
return jsonStatusError(errors.New("Unknown command"))
}
if err != nil {
return jsonStatusError(err)
} else {
return jsonStatusOk()
}
}

View File

@@ -1,23 +1,32 @@
// core_test.go: tests for core.go
//
// Copyright (c) 2013 CloudFlare, Inc.
package core
import (
"os"
"encoding/json"
"testing"
"os"
"redoctober/passvault"
"redoctober/keycache"
"redoctober/keycache"
"redoctober/passvault"
)
func TestCreate(t *testing.T) {
createJson := []byte("{\"Name\":\"Alice\",\"Password\":\"Hello\"}")
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)
}
@@ -26,7 +35,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)
@@ -44,9 +56,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)
}
@@ -57,7 +72,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)
@@ -66,7 +84,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)
@@ -87,7 +108,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)
@@ -96,7 +120,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)
@@ -138,7 +165,6 @@ func TestSummary(t *testing.T) {
t.Fatalf("Error in summary of account, record missing ")
}
//
keycache.FlushCache()
os.Remove("/tmp/db1.json")
@@ -156,8 +182,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)
}
@@ -165,7 +194,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)
@@ -174,7 +206,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)
@@ -183,7 +218,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)
@@ -192,7 +230,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)
@@ -201,7 +242,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)
@@ -210,7 +254,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)
@@ -219,7 +266,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)
@@ -248,16 +298,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)
@@ -266,7 +322,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)
@@ -277,7 +336,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 {
@@ -291,7 +353,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)
@@ -301,7 +366,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)
@@ -310,14 +378,16 @@ func TestEncryptDecrypt(t *testing.T) {
t.Fatalf("Error in encrypt, ", s.Status)
}
// decrypt file
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)
@@ -327,7 +397,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)
@@ -336,7 +409,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)
@@ -348,7 +424,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)
@@ -360,7 +439,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)
@@ -394,16 +476,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)
@@ -412,7 +500,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)
@@ -423,7 +514,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 {
@@ -437,7 +531,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)
@@ -447,7 +544,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)
@@ -457,7 +557,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)
@@ -466,7 +569,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)
@@ -479,7 +585,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)
@@ -488,7 +597,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)
@@ -501,7 +613,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)
@@ -511,7 +626,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)
@@ -527,4 +645,3 @@ func TestModify(t *testing.T) {
os.Remove("/tmp/db1.json")
}

View File

@@ -1,47 +1,51 @@
// Package cryptor encrypts and decrypts files using the Red October vault and key cache.
// Package cryptor encrypts and decrypts files using the Red October
// vault and key cache.
//
// Copyright (c) 2013 CloudFlare, Inc.
package cryptor
import (
"crypto/aes"
"crypto/hmac"
"crypto/sha1"
"crypto/rand"
"crypto/cipher"
"crypto/hmac"
"crypto/rand"
"crypto/sha1"
"encoding/json"
"errors"
"redoctober/passvault"
"redoctober/keycache"
"redoctober/padding"
"redoctober/passvault"
)
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
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
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
KeySet []MultiWrappedKey
Version int
VaultId int
KeySet []MultiWrappedKey
KeySetRSA map[string]SingleWrappedKey
IV []byte
Data []byte
IV []byte
Data []byte
Signature []byte
}
@@ -56,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}
@@ -90,7 +95,7 @@ func encryptKey(nameInner, nameOuter string, clearKey []byte, rsaKeys map[string
err = errors.New("Missing user in file")
return
}
overrideOuter, ok = rsaKeys[nameOuter]
if !ok {
err = errors.New("Missing user in file")
@@ -105,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
}
@@ -122,7 +125,7 @@ func encryptKey(nameInner, nameOuter string, clearKey []byte, rsaKeys map[string
// decrypt first key in keys whose encryption keys are in keycache
func unwrapKey(keys []MultiWrappedKey, rsaKeys map[string]SingleWrappedKey) (unwrappedKey []byte, err error) {
var (
keyFound error
keyFound error
fullMatch bool = false
)
for _, mwKey := range keys {
@@ -153,22 +156,21 @@ func unwrapKey(keys []MultiWrappedKey, rsaKeys map[string]SingleWrappedKey) (unw
return
}
// Encrypt encrypts data with the keys associated with names
// 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
}
@@ -182,14 +184,14 @@ func Encrypt(in []byte, names []string, min int) (resp []byte, err error) {
if err != nil {
return
}
// allocate set of keys to be able to cover all ordered subsets
// of length 2 of names
encrypted.KeySet = make([]MultiWrappedKey, len(names)*(len(names)-1))
// create map to hold RSA encrypted keys
encrypted.KeySetRSA = make(map[string]SingleWrappedKey)
var singleWrappedKey SingleWrappedKey
for _, name := range names {
rec, ok := passvault.GetRecord(name)
@@ -257,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 {
@@ -296,11 +297,10 @@ 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
}
// set up the decryption context
aesCrypt, err := aes.NewCipher(unwrappedKey)
if err != nil {
@@ -314,4 +314,3 @@ func Decrypt(in []byte) (resp []byte, err error) {
return padding.RemovePadding(clearData)
}

View File

@@ -1,16 +1,19 @@
// Package keycache provides the ability to hold active keys in memory
// for the Red October server.
//
// Copyright (c) 2013 CloudFlare, Inc.
package keycache
import (
"log"
"time"
"errors"
"crypto/aes"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/rand"
"errors"
"log"
"redoctober/passvault"
"time"
)
// UserKeys is the set of decrypted keys in memory, indexed by name.
@@ -18,11 +21,11 @@ var UserKeys map[string]ActiveUser = make(map[string]ActiveUser)
// ActiveUser holds the information about an actively delegated key
type ActiveUser struct {
Admin bool
Type string
Admin bool
Type string
Expiry time.Time
Uses int
// non-public members
Uses int
aesKey []byte
rsaKey rsa.PrivateKey
}
@@ -38,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)
}
@@ -68,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()
@@ -184,4 +186,3 @@ func DecryptKey(in []byte, name string, rsaEncryptedKey []byte) (out []byte, err
return
}

View File

@@ -1,9 +1,12 @@
// keycache_test.go: tests for keycache.go
//
// Copyright (c) 2013 CloudFlare, Inc.
package keycache
import (
"passvault"
"time"
"redoctober/passvault"
"testing"
"time"
)
var now = time.Now()
@@ -13,10 +16,10 @@ var dummy = make([]byte, 16)
func TestUsesFlush(t *testing.T) {
singleUse := ActiveUser{
Admin: true,
Type: passvault.AESRecord,
Admin: true,
Type: passvault.AESRecord,
Expiry: nextYear,
Uses: 2,
Uses: 2,
aesKey: emptyKey,
}
@@ -47,10 +50,10 @@ func TestTimeFlush(t *testing.T) {
one := now.Add(oneSec)
singleUse := ActiveUser{
Admin: true,
Type: passvault.AESRecord,
Admin: true,
Type: passvault.AESRecord,
Expiry: one,
Uses: 10,
Uses: 10,
aesKey: emptyKey,
}
@@ -76,5 +79,3 @@ func TestTimeFlush(t *testing.T) {
t.Fatalf("Error in pruning expired key")
}
}

View File

@@ -1,29 +1,46 @@
// Package padding adds and removes padding for AES-CBC mode.
//
// Copyright (c) 2013 CloudFlare, Inc.
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,154 +1,151 @@
// Package passvault manages the vault containing user records on disk.
// Package passvault manages the vault containing user records on
// disk. It contains usernames and associated passwords which are
// stored hashed (with salt) using scrypt.
//
// Copyright (c) 2013 CloudFlare, Inc.
package passvault
import (
"code.google.com/p/go.crypto/scrypt"
"crypto/sha1"
"crypto/aes"
"crypto/rsa"
"crypto/rand"
"crypto/cipher"
mrand "math/rand"
"math/big"
"io/ioutil"
"encoding/json"
"bytes"
"code.google.com/p/go.crypto/scrypt"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"encoding/binary"
"encoding/json"
"errors"
"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 {
Type string
Salt []byte
// 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
PasswordSalt []byte
HashedPassword []byte
KeySalt []byte
AESKey []byte
RSAKey struct {
RSAExp []byte
RSAExpIV []byte
RSAPrimeP []byte
KeySalt []byte
AESKey []byte
RSAKey struct {
RSAExp []byte
RSAExpIV []byte
RSAPrimeP []byte
RSAPrimePIV []byte
RSAPrimeQ []byte
RSAPrimeQ []byte
RSAPrimeQIV []byte
RSAPublic rsa.PublicKey
RSAPublic rsa.PublicKey
}
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
Version int
VaultId int
HmacKey []byte
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
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
}
@@ -164,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
}
@@ -177,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
}
@@ -187,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
}
@@ -204,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
}
@@ -217,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
}
@@ -232,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
}
@@ -248,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
if len(rec.RSAKey.RSAExp) == 0 || len(rec.RSAKey.RSAExp)%16 != 0 {
return formatErr
}
if len(rec.RSAKey.RSAPrimeP) == 0 || len(rec.RSAKey.RSAPrimeP) % 16 != 0 {
formatErr = true
if len(rec.RSAKey.RSAPrimeP) == 0 || len(rec.RSAKey.RSAPrimeP)%16 != 0 {
return formatErr
}
if len(rec.RSAKey.RSAPrimeQ) == 0 || len(rec.RSAKey.RSAPrimeQ) % 16 != 0 {
formatErr = true
if len(rec.RSAKey.RSAPrimeQ) == 0 || len(rec.RSAKey.RSAPrimeQ)%16 != 0 {
return formatErr
}
if len(rec.RSAKey.RSAExpIV) != 16 {
formatErr = true
if len(rec.RSAKey.RSAExpIV) != 16 {
return formatErr
}
if len(rec.RSAKey.RSAPrimePIV) != 16 {
formatErr = true
if len(rec.RSAKey.RSAPrimePIV) != 16 {
return formatErr
}
if len(rec.RSAKey.RSAPrimeQIV) != 16 {
formatErr = true
if len(rec.RSAKey.RSAPrimeQIV) != 16 {
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
}
@@ -374,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 {
// encrypt RSA key with password key
err = encryptRSARecord(&passwordRec, &rsaKey, newPassKey)
} else if pr.Type == RSARecord {
// encrypt RSA key with password key
err = encryptRSARecord(&pr, &rsaKey, newPassKey)
if err != nil {
return
}
@@ -414,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.
@@ -502,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
}
@@ -575,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
}
@@ -584,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
}
@@ -593,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)
@@ -608,15 +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

@@ -1,3 +1,7 @@
// passvault_test: tests for passvault.go
//
// Copyright (c) 2013 CloudFlare, Inc.
package passvault
import (

View File

@@ -1,84 +1,76 @@
// Package redoctober contains the server code for Red October.
//
// Copyright (c) 2013 CloudFlare, Inc.
package main
import (
"fmt"
"flag"
"os"
"io/ioutil"
"net"
"net/http"
"crypto/tls"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"flag"
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
"os"
"redoctober/core"
"runtime"
)
// list of URLs to register
const (
Create string = "/create"
Summary = "/summary"
Delegate = "/delegate"
Password = "/password"
Encrypt = "/encrypt"
Decrypt = "/decrypt"
Modify = "/modify"
)
// List of URLs to register and their related functions
// the channel handling user request
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 {
foo := <-process
switch {
case foo.rt == Create:
foo.resp <- core.Create(foo.in)
case foo.rt == Summary:
foo.resp <- core.Summary(foo.in)
case foo.rt == Delegate:
foo.resp <- core.Delegate(foo.in)
case foo.rt == Password:
foo.resp <- core.Password(foo.in)
case foo.rt == Encrypt:
foo.resp <- core.Encrypt(foo.in)
case foo.rt == Decrypt:
foo.resp <- core.Decrypt(foo.in)
case foo.rt == Modify:
foo.resp <- core.Modify(foo.in)
default:
fmt.Printf("Unknown! %s\n", foo.rt)
foo.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) {
// set up server
// 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,
@@ -86,35 +78,33 @@ 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{
Certificates: []tls.Certificate{cert},
Rand: rand.Reader,
ClientAuth: tls.RequestClientCert,
Certificates: []tls.Certificate{cert},
Rand: rand.Reader,
ClientAuth: tls.RequestClientCert,
PreferServerCipherSuites: true,
SessionTicketsDisabled: 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)
@@ -123,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)
})
}
@@ -144,11 +132,10 @@ 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 () {
func main() {
flag.Usage = func() {
fmt.Fprint(os.Stderr, usage)
flag.PrintDefaults()
@@ -168,8 +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)
}
}