[redoctober] Initial logic for alpha server

This commit is contained in:
Nick Sullivan
2013-10-30 16:19:45 -07:00
parent 0358ef9e63
commit 4e1aa78fb8
12 changed files with 2513 additions and 1 deletions

13
LICENSE.md Normal file
View File

@@ -0,0 +1,13 @@
## Copyright & License
This module is licensed under the terms of the BSD license.
Copyright (c) 2013, Nicholas Sullivan, CloudFlare Inc.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

63
Makefile Normal file
View File

@@ -0,0 +1,63 @@
NAME := redoctober
VERSION := 0.1
ITERATION := $(shell date +%s)
REVISION := $(shell git log -n1 --pretty=format:%h)
GOPATH := '$(CURDIR)'
BUILD_DEPS := go
.PHONY: all
all: $(NAME)
.PHONY: test
test:
GOPATH=$(GOPATH) go test $(NAME)/...
.PHONY: print-builddeps
print-builddeps:
@echo $(BUILD_DEPS)
.PHONY: $(NAME)
$(NAME): bin/$(NAME)
SRC := $(shell find src/$(NAME) -type f)
bin/$(NAME): $(SRC)
GOPATH=$(GOPATH) go install -tags "$(TAGS)" -ldflags "$(LDFLAGS)" $(NAME)
BUILD_PATH := build
INSTALL_PREFIX := usr/local
CFSSL_BUILD_PATH := $(BUILD_PATH)/$(INSTALL_PREFIX)/$(NAME)
FPM := fakeroot fpm -C $(BUILD_PATH) \
-s dir \
-t deb \
--deb-compression bzip2 \
-v $(VERSION) \
--iteration $(ITERATION)
DEB_PACKAGE := $(NAME)_$(VERSION)-$(ITERATION)_amd64.deb
$(DEB_PACKAGE): TAGS := release
$(DEB_PACKAGE): LDFLAGS := -X main.version $(VERSION) -X main.revision $(REVISION)
$(DEB_PACKAGE): clean all
mkdir -p $(CFSSL_BUILD_PATH)
cp bin/$(NAME) $(CFSSL_BUILD_PATH)
$(FPM) -n $(NAME) $(INSTALL_PREFIX)/$(NAME)
register-%.deb: ; $(PACKAGE_REGISTER_BIN) $*.deb
.PHONY: package
package: $(DEB_PACKAGE)
.PHONY: clean-package
clean-package:
$(RM) -r $(BUILD_PATH)
$(RM) $(DEB_PACKAGE)
.PHONY: clean
clean: clean-package
GOPATH=$(GOPATH) go clean -i $(NAME)/...
$(RM) -r pkg

113
README.md
View File

@@ -1,4 +1,115 @@
redoctober
==========
Go daemon for two-man rule style file encryption and decryption.
## Summary
Go server for two-man rule style file encryption and decryption.
## Running
usage:
The Red October server is a TLS server. It requires a local file to hold the key vault, an internet address and a certificate keypair.
i.e.
redoctober /tmp/diskrecord.json localhost:8080 cert.pem cert.key
## Using
The server exposes several JSON API endpoints. JSON of the prescribed format is POSTed and JSON is returned.
- Create = "/create"
- Summary = "/summary"
- Delegate = "/delegate"
- Password = "/password
- Modify = "/modify"
- Encrypt = "/encrypt"
- Decrypt = "/decrypt"
### Create
Create is the necessary first call to a new red october vault. It creates an admin account.
Example Input JSON format:
{"Name":"Bob","Password":"Rob"}
Example Output JSON format:
{"Status":"ok"}
### Summary
Summary provides a list of the users with keys on the system, and a list of users who have currently delegated their key to the server. Only Admins are allowed to call summary.
Example Input JSON format:
{"Name":"Bob","Password":"Rob"}
Example Output JSON format:
{"Status":"ok",
"Live":{
"Bob":{"Admin":true,"Type":"RSA","Expiry":"2013-11-15T12:13:52.238352947-08:00","Uses":5},
"Carol":{"Admin":false,"Type":"RSA","Expiry":"2013-11-15T14:11:15.5374364-08:00","Uses":30}
},
"All":{
"Alice":{"Admin":true,"Type":"RSA"},
"Bob":{"Admin":true,"Type":"RSA"},
"Carol":{"Admin":false,"Type":"RSA"}
}
}
### Delegate
Delegate allows a user to delegate their decryption password to the server for a fixed period of time and for a fixed number of decryptions. If the user's account is not created, it creates it. Any new delegation overrides the previous delegation.
Example Input JSON format:
{"Name":"Bob","Password":"Rob","Time":"2h34m","Uses":3}
Example Output JSON format:
{"Status":"ok"}
### Password
Password allows a user to change their password. This password change does not require the previously encrypted files to be re-encrypted.
Example Input JSON format:
{"Name":"Bob","Password":"Rob","NewPassword":"Robby"}
Example Output JSON format:
{"Status":"ok"}
### Modify
Modify allows an admin user to change information about a given user.
There are 3 commands:
- "revoke" : revokes the admin status of a user
- "admin" : grants admin status to a user
- "delete" : removes the account of a user
Example Input JSON format:
{"Name":"Bob","Password":"Rob","ToModify":"Alice","Command":"admin"}
Example Output JSON format:
{"Status":"ok"}
### Encrypt
Encrypt allows an admin to encrypt a piece of data. A list of valid users is provided and a minimum number of delegated users required to decrypt. The returned data can be decryped as long as "Minimum" number users from the set of "Owners" have delegated their keys to the server.
Example Input JSON format:
{"Name":"Alice","Password":"Hello","Minimum":2,"Owners":["Bob","Alice","Carol"],"Data":"dGhpcyBpcyBhIHNlY3JldCBzdHJpbmcsIHNoaGhoaGhoaC4gZG9uJ3QgdGVsbCBhbnlvbmUsIG9rPyB3aGF0ZXZlciB5b3UgZG8sIGRvbid0IHRlbGwgdGhlIGNvcHMK"}
Example Output JSON format:
{"Status":"ok","Response":"eyJWZXJzaW9uIjoxLCJWYXVsdElkIjo2NzA0ODUyOTMyNTYwNDk3NjM0LCJLZXlTZXQiOlt7Ik5hbWUiOlsiQm9iIiwiQWxpY2UiXSwiS2V5IjoicmZoSkFrSFk5eWRlQ0ppQzU1UU1JUT09In0seyJOYW1lIjpbIkJvYiIsIkNhcm9sIl0sIktleSI6Ik8xUFRFSE8rN1MvVWtLNnVYcVk5VFE9PSJ9LHsiTmFtZSI6WyJBbGljZSIsIkJvYiJdLCJLZXkiOiJBWVdWa3JwQVJwMk9iaTQ0S3VZbnNBPT0ifSx7Ik5hbWUiOlsiQWxpY2UiLCJDYXJvbCJdLCJLZXkiOiJ4aElTZUh2dDMzNkFTNVhmeXgwd1p3PT0ifSx7Ik5hbWUiOlsiQ2Fyb2wiLCJCb2IiXSwiS2V5Ijoia21VOGZiaDVkYjI1N0VMbXNzK0FQdz09In0seyJOYW1lIjpbIkNhcm9sIiwiQWxpY2UiXSwiS2V5IjoiVFpjaXd5QXphSWFSUW5USThpQTN4dz09In1dLCJLZXlTZXRSU0EiOnsiQWxpY2UiOnsiS2V5IjoiTUVEZFphSGtsMGVWRktHa2d6c0ttRXFyQkQ4SlB4Ykdla2VDRkUxWHRnUjVxdGYvTi8xUFE1SklaeVlEdXRFYWR1SVdybHc1K2phQWo2MVBVME01VDFsd0V1VktvMVpwV3RsWHAxU05Ed0R5MHNqaEZXb2lSQ0ZTUm9ZcW5ES3V0RHluUThVejFLOXBuRVlTRllnSVJDRnJYM2VhMUh0OEVEV1NUb0dsVGhBV1MrOTB1M2F4V3ZtZ1Nyc09FRm44dXZQRjdQNlRlNjdPY0FXNEV2MmFSMk92VlJHeUNJTXc1RVIvTTVPL1JRWW5ERGowT2lwMmhRRnJhK3c0MnJaYWpJejhSR3BCdzl1RWNnNDF5ZUZHRE5QNlRYQ25QZ2hFbUdCNTBJTVhMMzRUQkNNTG9UZTZMYlRGZTJpRnFGQ1c3dGNnZkdKY2h6aSs4SmVTUFRWWEFRPT0ifSwiQm9iIjp7IktleSI6IkRzSEZBTEs3cHFQQnBMSVN6b3BXbDhjb25SS0NPRlZFaW44T1l5aGZiMFQ1MVRKSVZPd1FUL2tIS0ZwSjFzcnlMSHh5UVM3TUk3T0FpZzJ3Z2NmVmZob0drdENmOExuK2tKNUpVT3JlSDY4YmJ3cnBEK3pHNnp3aFpKc0NkbEJEQ2pKU09rajc2QmtiL0JSMUhLN3FZcUdxRTMvN25jeWNuT0tPcHp5bS9DcFVrRFphSkRMak4welR2K3RxNityN0ZHUUpUdlpqRzZLbjBzamlGREtlbnlmSVhzd0hSQ3NjbTVENU8xZXZBSXNxeE9kOGJoUmw5bXovQWNCd1BpT3A1YVlpdGJxNnZidW1VbVg0UDYzeVJBaWxaaTZnOGZvN255Nnl1REtYeE9DYzFPeUFGamZGMVloSTJrT2pEUExkTCtrQjI5dlFsblkxODltRVVvL3pRUT09In0sIkNhcm9sIjp7IktleSI6ImNQZEhGYXZ5aUF4VmpPR2x6eVF2d0lTZGhtMCtZNzFVcUpXUFNKZ1h5a3Zoekl6TzNoQmV6OHhUTkxrRldkdnQ0T0NWc2hPMTd3VDY4cnhxZUE1bzJocmVjUVBUZ2IzbHVaWFZ5MDFxaUlDZXZveUw0cTljR2hNQUVsVWZoMGZEb3MrZVo1VEpTbElESWZSZGRXbEZ1NmUxT3ZmQWxOZ3A5Z3VSRVhNR05QcGxNa2YwUlpWZ1RHNGpka0VpUVVTRDQxemJBOWwxand4UkxvYXNrRXBLeFEycVp3YnBjbGRhUUk3WUlqbzlEMk5zM2V5N29YbmQ2RXJOQXpOMzRKc1h1QXE0a3NkNlRJdTdpQktYYzlvNUV1WWNnaEpNTnRaWkljc05ueVBHVlNQY2IwdzFrYVJPWTNndTFidFZhU2FqVDN6TmtyZXpzQy9td1hBaDRmajVvdz09In19LCJJViI6InRqZWF3VWVKU0hocEwzRnFZZy9hNkE9PSIsIkRhdGEiOiJnRGxORHFiQWU5eVd0M1ZNMW1SSldDMjJVRkFvcFdSbVVyWTI5RjJQdUlLUmJaYWpJRlE1RmZsTmtzR2tRTjREVWZZc0wrelJTeHp6bUtTaXNCZVdHNUVCZDlTdXJmdktSQlQ1TEhmUHN0UzNxbEJtY2lWb21Qc1lOaUthN0ZVUGxVcUE2NHVyaTVXQUxpdWQvVExZQXc9PSIsIlNpZ25hdHVyZSI6IlB0dkxNUkpxR1FvNHlIRXVuN083d2NzV1A1az0ifQ=="}
The data expansion is not tied to the size of the input.
### Decrypt
Decrypt allows an admin to decrypt a piece of data. As long as "Minimum" number users from the set of "Owners" have delegated their keys to the server, the clear data will be returned.
Example Input JSON format:
{"Name":"Alice","Password":"Hello","Data":"eyJWZXJzaW9uIjoxLCJWYXVsdElkIjo2NzA0ODUyOTMyNTYwNDk3NjM0LCJLZXlTZXQiOlt7Ik5hbWUiOlsiQm9iIiwiQWxpY2UiXSwiS2V5IjoicmZoSkFrSFk5eWRlQ0ppQzU1UU1JUT09In0seyJOYW1lIjpbIkJvYiIsIkNhcm9sIl0sIktleSI6Ik8xUFRFSE8rN1MvVWtLNnVYcVk5VFE9PSJ9LHsiTmFtZSI6WyJBbGljZSIsIkJvYiJdLCJLZXkiOiJBWVdWa3JwQVJwMk9iaTQ0S3VZbnNBPT0ifSx7Ik5hbWUiOlsiQWxpY2UiLCJDYXJvbCJdLCJLZXkiOiJ4aElTZUh2dDMzNkFTNVhmeXgwd1p3PT0ifSx7Ik5hbWUiOlsiQ2Fyb2wiLCJCb2IiXSwiS2V5Ijoia21VOGZiaDVkYjI1N0VMbXNzK0FQdz09In0seyJOYW1lIjpbIkNhcm9sIiwiQWxpY2UiXSwiS2V5IjoiVFpjaXd5QXphSWFSUW5USThpQTN4dz09In1dLCJLZXlTZXRSU0EiOnsiQWxpY2UiOnsiS2V5IjoiTUVEZFphSGtsMGVWRktHa2d6c0ttRXFyQkQ4SlB4Ykdla2VDRkUxWHRnUjVxdGYvTi8xUFE1SklaeVlEdXRFYWR1SVdybHc1K2phQWo2MVBVME01VDFsd0V1VktvMVpwV3RsWHAxU05Ed0R5MHNqaEZXb2lSQ0ZTUm9ZcW5ES3V0RHluUThVejFLOXBuRVlTRllnSVJDRnJYM2VhMUh0OEVEV1NUb0dsVGhBV1MrOTB1M2F4V3ZtZ1Nyc09FRm44dXZQRjdQNlRlNjdPY0FXNEV2MmFSMk92VlJHeUNJTXc1RVIvTTVPL1JRWW5ERGowT2lwMmhRRnJhK3c0MnJaYWpJejhSR3BCdzl1RWNnNDF5ZUZHRE5QNlRYQ25QZ2hFbUdCNTBJTVhMMzRUQkNNTG9UZTZMYlRGZTJpRnFGQ1c3dGNnZkdKY2h6aSs4SmVTUFRWWEFRPT0ifSwiQm9iIjp7IktleSI6IkRzSEZBTEs3cHFQQnBMSVN6b3BXbDhjb25SS0NPRlZFaW44T1l5aGZiMFQ1MVRKSVZPd1FUL2tIS0ZwSjFzcnlMSHh5UVM3TUk3T0FpZzJ3Z2NmVmZob0drdENmOExuK2tKNUpVT3JlSDY4YmJ3cnBEK3pHNnp3aFpKc0NkbEJEQ2pKU09rajc2QmtiL0JSMUhLN3FZcUdxRTMvN25jeWNuT0tPcHp5bS9DcFVrRFphSkRMak4welR2K3RxNityN0ZHUUpUdlpqRzZLbjBzamlGREtlbnlmSVhzd0hSQ3NjbTVENU8xZXZBSXNxeE9kOGJoUmw5bXovQWNCd1BpT3A1YVlpdGJxNnZidW1VbVg0UDYzeVJBaWxaaTZnOGZvN255Nnl1REtYeE9DYzFPeUFGamZGMVloSTJrT2pEUExkTCtrQjI5dlFsblkxODltRVVvL3pRUT09In0sIkNhcm9sIjp7IktleSI6ImNQZEhGYXZ5aUF4VmpPR2x6eVF2d0lTZGhtMCtZNzFVcUpXUFNKZ1h5a3Zoekl6TzNoQmV6OHhUTkxrRldkdnQ0T0NWc2hPMTd3VDY4cnhxZUE1bzJocmVjUVBUZ2IzbHVaWFZ5MDFxaUlDZXZveUw0cTljR2hNQUVsVWZoMGZEb3MrZVo1VEpTbElESWZSZGRXbEZ1NmUxT3ZmQWxOZ3A5Z3VSRVhNR05QcGxNa2YwUlpWZ1RHNGpka0VpUVVTRDQxemJBOWwxand4UkxvYXNrRXBLeFEycVp3YnBjbGRhUUk3WUlqbzlEMk5zM2V5N29YbmQ2RXJOQXpOMzRKc1h1QXE0a3NkNlRJdTdpQktYYzlvNUV1WWNnaEpNTnRaWkljc05ueVBHVlNQY2IwdzFrYVJPWTNndTFidFZhU2FqVDN6TmtyZXpzQy9td1hBaDRmajVvdz09In19LCJJViI6InRqZWF3VWVKU0hocEwzRnFZZy9hNkE9PSIsIkRhdGEiOiJnRGxORHFiQWU5eVd0M1ZNMW1SSldDMjJVRkFvcFdSbVVyWTI5RjJQdUlLUmJaYWpJRlE1RmZsTmtzR2tRTjREVWZZc0wrelJTeHp6bUtTaXNCZVdHNUVCZDlTdXJmdktSQlQ1TEhmUHN0UzNxbEJtY2lWb21Qc1lOaUthN0ZVUGxVcUE2NHVyaTVXQUxpdWQvVExZQXc9PSIsIlNpZ25hdHVyZSI6IlB0dkxNUkpxR1FvNHlIRXVuN083d2NzV1A1az0ifQ=="}
Example Output JSON format:
{"Status":"ok","Result":"dGhpcyBpcyBhIHNlY3JldCBzdHJpbmcsIHNoaGhoaGhoaC4gZG9uJ3QgdGVsbCBhbnlvbmUsIG9rPyB3aGF0ZXZlciB5b3UgZG8sIGRvbid0IHRlbGwgdGhlIGNvcHMK"}

317
src/redoctober/core/core.go Normal file
View File

@@ -0,0 +1,317 @@
// Pacakge core handles the main operations of the Red October server.
package core
import (
"log"
"errors"
"encoding/json"
"redoctober/passvault"
"redoctober/cryptor"
"redoctober/keycache"
)
// format of incoming sign-in request
type create struct {
Name string
Password string
}
type summary struct {
Name string
Password string
}
type delegate struct {
Name string
Password string
Uses int
Time string
}
type password struct {
Name string
Password string
NewPassword string
}
type encrypt struct {
Name string
Password string
Minimum int
Owners []string
Data []byte
}
type decrypt struct {
Name string
Password string
Data []byte
}
type modify struct {
Name string
Password string
ToModify string
Command string
}
// response JSON format
type status struct {
Status string
}
type responseData struct {
Status string
Response []byte
}
type summaryData struct {
Status string
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
}
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) {
if passvault.NumRecords() == 0 {
return errors.New("Vault is not created yet")
}
// find record
passwordRec, ok := passvault.GetRecord(name)
if !ok {
return errors.New("User not present")
}
err = passwordRec.ValidatePassword(password)
if err != nil {
return
}
if !passwordRec.IsAdmin() {
return errors.New("Admin required")
}
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 {
var s create
err := json.Unmarshal(jsonIn, &s)
if err != nil {
return errToJson(err)
}
if passvault.NumRecords() != 0 {
return errToJson(errors.New("Vault is already created"))
}
_, err = passvault.AddNewRecord(s.Name, s.Password, true)
if err != nil {
log.Println("Error adding record:", err)
return errToJson(err)
}
return errToJson(err)
}
// Summary processes a summary request.
func Summary(jsonIn []byte) []byte {
var s summary
keycache.Refresh()
err := json.Unmarshal(jsonIn, &s)
if err != nil {
return errToJson(err)
}
if passvault.NumRecords() == 0 {
return errToJson(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)
}
// populate
return summaryToJson(err)
}
// Delegate processes a delegation request.
func Delegate(jsonIn []byte) []byte {
var s delegate
err := json.Unmarshal(jsonIn, &s)
if err != nil {
return errToJson(err)
}
if passvault.NumRecords() == 0 {
return errToJson(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)
}
} else {
passwordRec, err = passvault.AddNewRecord(s.Name, s.Password, false)
if err != nil {
log.Println("Error adding record:", err)
return errToJson(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)
}
return errToJson(err)
}
// Password processes a password change request.
func Password(jsonIn []byte) []byte {
var s password
err := json.Unmarshal(jsonIn, &s)
if err != nil {
return errToJson(err)
}
if passvault.NumRecords() == 0 {
return errToJson(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 {
log.Println("Error changing password:", err)
return errToJson(err)
}
return errToJson(err)
}
// Encrypt processes an encrypt request.
func Encrypt(jsonIn []byte) (ret []byte) {
var s encrypt
err := json.Unmarshal(jsonIn, &s)
if err != nil {
return errToJson(err)
}
err = validateAdmin(s.Name, s.Password)
if err != nil {
log.Println("Error validating admin status", err)
return errToJson(err)
}
// Encrypt file with list of owners
resp, err := cryptor.Encrypt(s.Data, s.Owners, s.Minimum)
if err != nil {
log.Println("Error encrypting:", err)
return errToJson(err)
}
return responseToJson(resp, err)
}
// Decrypt processes a decrypt request.
func Decrypt(jsonIn []byte) (ret []byte) {
var s decrypt
err := json.Unmarshal(jsonIn, &s)
if err != nil {
return errToJson(err)
}
err = validateAdmin(s.Name, s.Password)
if err != nil {
log.Println("Error validating admin status", err)
return errToJson(err)
}
resp, err := cryptor.Decrypt(s.Data)
if err != nil {
log.Println("Error decrypting:", err)
return errToJson(err)
}
return responseToJson(resp, err)
}
// Modify processes a modify request.
func Modify(jsonIn []byte) []byte {
var s modify
err := json.Unmarshal(jsonIn, &s)
if err != nil {
return errToJson(err)
}
err = validateAdmin(s.Name, s.Password)
if err != nil {
log.Println("Error validating admin status", err)
return errToJson(err)
}
if _, ok := passvault.GetRecord(s.ToModify); !ok {
return errToJson(errors.New("Record to modify missing"))
}
if s.Name == s.ToModify {
return errToJson(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)
}

View File

@@ -0,0 +1,530 @@
package core
import (
"os"
"encoding/json"
"redoctober/passvault"
"redoctober/keycache"
"redoctober/testing"
)
func TestCreate(t *testing.T) {
createJson := []byte("{\"Name\":\"Alice\",\"Password\":\"Hello\"}")
os.Remove("/tmp/db1.json")
Init("/tmp/db1.json")
respJson := Create(createJson)
var s responseData
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)
}
// check to see if creation can happen twice
respJson = Create(createJson)
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in creating account when one exists, ", err)
}
if s.Status == "ok" {
t.Fatalf("Error in creating account when one exists, ", s.Status)
}
os.Remove("/tmp/db1.json")
}
func TestSummary(t *testing.T) {
createJson := []byte("{\"Name\":\"Alice\",\"Password\":\"Hello\"}")
delegateJson := []byte("{\"Name\":\"Bob\",\"Password\":\"Rob\",\"Time\":\"2h\",\"Uses\":1}")
os.Remove("/tmp/db1.json")
// check for summary of uninitialized vault
respJson := Summary(createJson)
var s summaryData
err := json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in summary of account with no vault,", err)
}
if s.Status != "ok" {
t.Fatalf("Error in summary of account with no vault, ", s.Status)
}
Init("/tmp/db1.json")
// check for summary of initialized vault
respJson = Create(createJson)
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 = Summary(createJson)
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in summary of account,", err)
}
if s.Status != "ok" {
t.Fatalf("Error in summary of account, ", s.Status)
}
data, ok := s.All["Alice"]
if !ok {
t.Fatalf("Error in summary of account, record missing ")
}
if data.Admin != true {
t.Fatalf("Error in summary of account, record incorrect ")
}
if data.Type != passvault.RSARecord {
t.Fatalf("Error in summary of account, record incorrect ")
}
// check for summary of initialized vault with new member
respJson = Delegate(delegateJson)
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in delegating account,", err)
}
if s.Status != "ok" {
t.Fatalf("Error in delegating account, ", s.Status)
}
respJson = Summary(createJson)
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in summary of account with no vault,", err)
}
if s.Status != "ok" {
t.Fatalf("Error in summary of account with no vault, ", s.Status)
}
data, ok = s.All["Alice"]
if !ok {
t.Fatalf("Error in summary of account, record missing ")
}
if data.Admin != true {
t.Fatalf("Error in summary of account, record missing ")
}
if data.Type != passvault.RSARecord {
t.Fatalf("Error in summary of account, record missing ")
}
data, ok = s.All["Bob"]
if !ok {
t.Fatalf("Error in summary of account, record missing ")
}
if data.Admin != false {
t.Fatalf("Error in summary of account, record missing ")
}
if data.Type != passvault.RSARecord {
t.Fatalf("Error in summary of account, record missing ")
}
dataLive, ok := s.Live["Bob"]
if !ok {
t.Fatalf("Error in summary of account, record missing", keycache.UserKeys)
}
if dataLive.Admin != false {
t.Fatalf("Error in summary of account, record missing ")
}
if dataLive.Type != passvault.RSARecord {
t.Fatalf("Error in summary of account, record missing ")
}
//
keycache.FlushCache()
os.Remove("/tmp/db1.json")
}
func TestPassword(t *testing.T) {
createJson := []byte("{\"Name\":\"Alice\",\"Password\":\"Hello\"}")
delegateJson := []byte("{\"Name\":\"Alice\",\"Password\":\"Hello\",\"Time\":\"2h\",\"Uses\":1}")
passwordJson := []byte("{\"Name\":\"Alice\",\"Password\":\"Hello\",\"NewPassword\":\"Olleh\"}")
delegateJson2 := []byte("{\"Name\":\"Alice\",\"Password\":\"Olleh\",\"Time\":\"2h\",\"Uses\":1}")
passwordJson2 := []byte("{\"Name\":\"Alice\",\"Password\":\"Olleh\",\"NewPassword\":\"Hello\"}")
os.Remove("/tmp/db1.json")
Init("/tmp/db1.json")
// check for summary of initialized vault with new member
var s responseData
respJson := Create(createJson)
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 = Delegate(delegateJson)
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in delegating account,", err)
}
if s.Status != "ok" {
t.Fatalf("Error in delegating account, ", s.Status)
}
respJson = Password(passwordJson2)
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in password", err)
}
if s.Status == "ok" {
t.Fatalf("Error in password, ", s.Status)
}
respJson = Password(passwordJson)
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in password", err)
}
if s.Status != "ok" {
t.Fatalf("Error in password, ", s.Status)
}
respJson = Delegate(delegateJson)
err = json.Unmarshal(respJson, &s)
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)
}
if s.Status != "ok" {
t.Fatalf("Error in delegating account, ", s.Status)
}
respJson = Password(passwordJson2)
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in password", err)
}
if s.Status != "ok" {
t.Fatalf("Error in password, ", s.Status)
}
respJson = Delegate(delegateJson)
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in delegating account,", err)
}
if s.Status != "ok" {
t.Fatalf("Error in delegating account, ", s.Status)
}
keycache.FlushCache()
os.Remove("/tmp/db1.json")
}
func TestEncryptDecrypt(t *testing.T) {
summaryJson := []byte("{\"Name\":\"Alice\",\"Password\":\"Hello\"}")
delegateJson := []byte("{\"Name\":\"Alice\",\"Password\":\"Hello\",\"Time\":\"0s\",\"Uses\":0}")
delegateJson2 := []byte("{\"Name\":\"Bob\",\"Password\":\"Hello\",\"Time\":\"0s\",\"Uses\":0}")
delegateJson3 := []byte("{\"Name\":\"Carol\",\"Password\":\"Hello\",\"Time\":\"0s\",\"Uses\":0}")
delegateJson4 := []byte("{\"Name\":\"Bob\",\"Password\":\"Hello\",\"Time\":\"10s\",\"Uses\":2}")
delegateJson5 := []byte("{\"Name\":\"Carol\",\"Password\":\"Hello\",\"Time\":\"10s\",\"Uses\":2}")
encryptJson := []byte("{\"Name\":\"Carol\",\"Password\":\"Hello\",\"Minumum\":2,\"Owners\":[\"Alice\",\"Bob\",\"Carol\"],\"Data\":\"SGVsbG8gSmVsbG8=\"}")
encryptJson2 := []byte("{\"Name\":\"Alice\",\"Password\":\"Hello\",\"Minumum\":2,\"Owners\":[\"Alice\",\"Bob\",\"Carol\"],\"Data\":\"SGVsbG8gSmVsbG8=\"}")
os.Remove("/tmp/db1.json")
Init("/tmp/db1.json")
// check for summary of initialized vault with new member
var s responseData
respJson := Create(delegateJson)
err := json.Unmarshal(respJson, &s)
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)
}
if s.Status != "ok" {
t.Fatalf("Error in delegating account, ", s.Status)
}
respJson = Delegate(delegateJson3)
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in delegating account,", err)
}
if s.Status != "ok" {
t.Fatalf("Error in delegating account, ", s.Status)
}
// check summary to see if none are delegated
keycache.Refresh()
respJson = Summary(summaryJson)
var sum summaryData
err = json.Unmarshal(respJson, &sum)
if err != nil {
t.Fatalf("Error in summary,", err)
}
if sum.Status != "ok" {
t.Fatalf("Error in summary, ", sum.Status)
}
if len(sum.Live) != 0 {
t.Fatalf("Error in summary, ", sum.Status)
}
// Encrypt with non-admin (fail)
respJson = Encrypt(encryptJson)
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in encrypt,", err)
}
if s.Status == "ok" {
t.Fatalf("Error in encrypt, ", s.Status)
}
// Encrypt
respJson = Encrypt(encryptJson2)
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in encrypt,", err)
}
if s.Status != "ok" {
t.Fatalf("Error in encrypt, ", s.Status)
}
// decrypt file
decryptJson, err := json.Marshal(decrypt{Name:"Alice", Password:"Hello", In:s.Response})
if err != nil {
t.Fatalf("Error in marshalling decryption,", err)
}
respJson2 := Decrypt(decryptJson)
err = json.Unmarshal(respJson2, &s)
if err != nil {
t.Fatalf("Error in decrypt,", err)
}
if s.Status == "ok" {
t.Fatalf("Error in decrypt, ", s.Status)
}
// delegate two valid decryptors
respJson = Delegate(delegateJson4)
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in delegating account,", err)
}
if s.Status != "ok" {
t.Fatalf("Error in delegating account, ", s.Status)
}
respJson = Delegate(delegateJson5)
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in delegating account,", err)
}
if s.Status != "ok" {
t.Fatalf("Error in delegating account, ", s.Status)
}
// verify the presence of the two delgations
keycache.Refresh()
var sum2 summaryData
respJson = Summary(summaryJson)
err = json.Unmarshal(respJson, &sum2)
if err != nil {
t.Fatalf("Error in summary,", err)
}
if sum2.Status != "ok" {
t.Fatalf("Error in summary, ", sum2.Status)
}
if len(sum2.Live) != 2 {
t.Fatalf("Error in summary, ", sum2.Live)
}
respJson2 = Decrypt(decryptJson)
err = json.Unmarshal(respJson2, &s)
if err != nil {
t.Fatalf("Error in decrypt,", err)
}
if s.Status != "ok" {
t.Fatalf("Error in decrypt, ", s.Status)
}
if string(s.Response) != "Hello Jello" {
t.Fatalf("Error in decrypt, ", string(s.Response))
}
keycache.FlushCache()
os.Remove("/tmp/db1.json")
}
func TestModify(t *testing.T) {
summaryJson := []byte("{\"Name\":\"Alice\",\"Password\":\"Hello\"}")
summaryJson2 := []byte("{\"Name\":\"Carol\",\"Password\":\"Hello\"}")
delegateJson := []byte("{\"Name\":\"Alice\",\"Password\":\"Hello\",\"Time\":\"0s\",\"Uses\":0}")
delegateJson2 := []byte("{\"Name\":\"Bob\",\"Password\":\"Hello\",\"Time\":\"0s\",\"Uses\":0}")
delegateJson3 := []byte("{\"Name\":\"Carol\",\"Password\":\"Hello\",\"Time\":\"0s\",\"Uses\":0}")
modifyJson := []byte("{\"Name\":\"Alice\",\"Password\":\"Hello\",\"ToModify\":\"Alice\",\"Command\":\"admin\"}")
modifyJson2 := []byte("{\"Name\":\"Carol\",\"Password\":\"Hello\",\"ToModify\":\"Alice\",\"Command\":\"revoke\"}")
modifyJson3 := []byte("{\"Name\":\"Alice\",\"Password\":\"Hello\",\"ToModify\":\"Carol\",\"Command\":\"admin\"}")
modifyJson4 := []byte("{\"Name\":\"Carol\",\"Password\":\"Hello\",\"ToModify\":\"Alice\",\"Command\":\"revoke\"}")
modifyJson5 := []byte("{\"Name\":\"Carol\",\"Password\":\"Hello\",\"ToModify\":\"Alice\",\"Command\":\"delete\"}")
os.Remove("/tmp/db1.json")
Init("/tmp/db1.json")
// check for summary of initialized vault with new member
var s responseData
respJson := Create(delegateJson)
err := json.Unmarshal(respJson, &s)
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)
}
if s.Status != "ok" {
t.Fatalf("Error in delegating account, ", s.Status)
}
respJson = Delegate(delegateJson3)
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in delegating account,", err)
}
if s.Status != "ok" {
t.Fatalf("Error in delegating account, ", s.Status)
}
// check summary to see if none are delegated
keycache.Refresh()
respJson = Summary(summaryJson)
var sum summaryData
err = json.Unmarshal(respJson, &sum)
if err != nil {
t.Fatalf("Error in summary,", err)
}
if sum.Status != "ok" {
t.Fatalf("Error in summary, ", sum.Status)
}
if len(sum.Live) != 0 {
t.Fatalf("Error in summary, ", sum.Status)
}
// Modify from non-admin (fail)
respJson = Modify(modifyJson)
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in modify,", err)
}
if s.Status == "ok" {
t.Fatalf("Error in modify, ", s.Status)
}
// Modify self from admin (fail)
respJson = Modify(modifyJson2)
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in modify,", err)
}
if s.Status == "ok" {
t.Fatalf("Error in modify, ", s.Status)
}
// Modify admin from admin
respJson = Modify(modifyJson3)
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in modify,", err)
}
if s.Status != "ok" {
t.Fatalf("Error in modify, ", s.Status)
}
respJson = Summary(summaryJson)
err = json.Unmarshal(respJson, &sum)
if err != nil {
t.Fatalf("Error in summary,", err)
}
if sum.Status != "ok" {
t.Fatalf("Error in summary, ", sum.Status)
}
if sum.All["Carol"].Admin != true {
t.Fatalf("Error in summary, ", sum.All)
}
// Revoke admin from admin
respJson = Modify(modifyJson4)
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in modify,", err)
}
if s.Status != "ok" {
t.Fatalf("Error in modify, ", s.Status)
}
respJson = Summary(summaryJson2)
err = json.Unmarshal(respJson, &sum)
if err != nil {
t.Fatalf("Error in summary,", err)
}
if sum.Status != "ok" {
t.Fatalf("Error in summary, ", sum.Status)
}
if sum.All["Alice"].Admin == true {
t.Fatalf("Error in summary, ", sum.All)
}
// Delete from admin
respJson = Modify(modifyJson5)
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error in modify,", err)
}
if s.Status != "ok" {
t.Fatalf("Error in modify, ", s.Status)
}
var sum3 summaryData
respJson = Summary(summaryJson2)
err = json.Unmarshal(respJson, &sum3)
if err != nil {
t.Fatalf("Error in summary,", err)
}
if sum3.Status != "ok" {
t.Fatalf("Error in summary, ", sum.Status)
}
if len(sum3.All) != 2 {
t.Fatalf("Error in summary, ", sum.All)
}
keycache.FlushCache()
os.Remove("/tmp/db1.json")
}

View File

@@ -0,0 +1,317 @@
// Package cryptor encrypts and decrypts files using the Red October vault and key cache.
package cryptor
import (
"crypto/aes"
"crypto/hmac"
"crypto/sha1"
"crypto/rand"
"crypto/cipher"
"encoding/json"
"errors"
"redoctober/passvault"
"redoctober/keycache"
"redoctober/padding"
)
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.
type MultiWrappedKey struct {
Name []string
Key []byte
}
// 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.
type EncryptedFile struct {
Version int
VaultId int
KeySet []MultiWrappedKey
KeySetRSA map[string]SingleWrappedKey
IV []byte
Data []byte
Signature []byte
}
// Private Functions
// 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
}
// 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}
recInner, ok := passvault.GetRecord(nameInner)
if !ok {
err = errors.New("Missing user on disk")
return
}
recOuter, ok := passvault.GetRecord(nameOuter)
if !ok {
err = errors.New("Missing user on disk")
return
}
if recInner.Type != recOuter.Type {
err = errors.New("Mismatched record types")
return
}
var keyBytes []byte
var overrideInner SingleWrappedKey
var overrideOuter SingleWrappedKey
// For AES records, use the live user key
// For RSA records, use the public key from the passvault
switch recInner.Type {
case passvault.RSARecord:
overrideInner, ok = rsaKeys[nameInner]
if !ok {
err = errors.New("Missing user in file")
return
}
overrideOuter, ok = rsaKeys[nameOuter]
if !ok {
err = errors.New("Missing user in file")
return
}
case passvault.AESRecord:
break
default:
return out, errors.New("Unknown record type inner")
}
// double-wrap the keys
keyBytes, err = keycache.EncryptKey(clearKey, nameInner, overrideInner.aesKey)
if err != nil {
return out, err
}
keyBytes, err = keycache.EncryptKey(keyBytes, nameOuter, overrideOuter.aesKey)
if err != nil {
return out, err
}
out.Key = keyBytes
return
}
// 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
fullMatch bool = false
)
for _, mwKey := range keys {
tmpKeyValue := mwKey.Key
if err != nil {
return nil, err
}
for _, mwName := range mwKey.Name {
rsaEncrypted := rsaKeys[mwName]
// if this is null, it's an AES encrypted key
tmpKeyValue, keyFound = keycache.DecryptKey(tmpKeyValue, mwName, rsaEncrypted.Key)
if keyFound != nil {
break
}
}
if keyFound == nil {
fullMatch = true
// concatenate all the decrypted bytes
unwrappedKey = tmpKeyValue
break
}
}
if fullMatch == false {
err = errors.New("Need more delegated keys")
}
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")
}
// set up encrypted data structure
var encrypted EncryptedFile
encrypted.Version = DEFAULT_VERSION
encrypted.VaultId, err = passvault.GetVaultId()
if err != nil {
return
}
// generate random IV and encryption key
ivBytes, err := makeRandom(16)
if err != nil {
return
}
encrypted.IV = append([]byte{}, ivBytes...)
clearKey, err := makeRandom(16)
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)
if !ok {
err = errors.New("Missing user on disk")
return
}
if rec.GetType() == passvault.RSARecord {
// only wrap key with RSA key if found
singleWrappedKey.aesKey, err = makeRandom(16)
if err != nil {
return nil, err
}
singleWrappedKey.Key, err = rec.EncryptKey(singleWrappedKey.aesKey)
if err != nil {
return nil, err
}
encrypted.KeySetRSA[name] = singleWrappedKey
} else {
err = nil
}
}
// encrypt file key with every combination of two keys
var n int
for _, nameOuter := range names {
for _, nameInner := range names {
if nameInner != nameOuter {
encrypted.KeySet[n], err = encryptKey(nameInner, nameOuter, clearKey, encrypted.KeySetRSA)
n += 1
}
if err != nil {
return
}
}
}
// encrypt file with clear key
aesCrypt, err := aes.NewCipher(clearKey)
if err != nil {
return
}
encryptedFile := make([]byte, len(clearFile))
aesCBC := cipher.NewCBCEncrypter(aesCrypt, ivBytes)
aesCBC.CryptBlocks(encryptedFile, clearFile)
// encode result
encrypted.Data = encryptedFile
// compute HMAC
hmacKey, err := passvault.GetHmacKey()
if err != nil {
return
}
mac := hmac.New(sha1.New, hmacKey)
mac.Write(encrypted.Data)
encrypted.Signature = mac.Sum(nil)
return json.Marshal(encrypted)
}
// Decrypt decrypts a file using the keys in the key cache.
func Decrypt(in []byte) (resp []byte, err error) {
// unwrap encrypted file
var encrypted EncryptedFile
err = json.Unmarshal(in, &encrypted)
if err != nil {
return
}
if encrypted.Version != DEFAULT_VERSION {
return nil, errors.New("Unknown version")
}
// make sure file was encrypted with the active vault
vaultId, err := passvault.GetVaultId()
if err != nil {
return
}
if encrypted.VaultId != vaultId {
return nil, errors.New("Wrong vault")
}
// validate the size of the keys
for _, multiKey := range encrypted.KeySet {
if len(multiKey.Key) != 16 {
err = errors.New("Invalid Input")
}
}
// compute HMAC
hmacKey, err := passvault.GetHmacKey()
if err != nil {
return
}
mac := hmac.New(sha1.New, hmacKey)
mac.Write(encrypted.Data)
expectedMAC := mac.Sum(nil)
if !hmac.Equal(encrypted.Signature, expectedMAC) {
err = errors.New("Signature mismatch")
return
}
// decrypt file key with delegate keys
var unwrappedKey = make([]byte, 16)
unwrappedKey, err = unwrapKey(encrypted.KeySet, encrypted.KeySetRSA)
if err != nil {
return
}
// set up the decryption context
aesCrypt, err := aes.NewCipher(unwrappedKey)
if err != nil {
return
}
clearData := make([]byte, len(encrypted.Data))
aesCBC := cipher.NewCBCDecrypter(aesCrypt, encrypted.IV)
// decrypt contents of file
aesCBC.CryptBlocks(clearData, encrypted.Data)
return padding.RemovePadding(clearData)
}

View File

@@ -0,0 +1,187 @@
// Package keycache provides the ability to hold active keys in memory
// for the Red October server.
package keycache
import (
"log"
"time"
"errors"
"crypto/aes"
"crypto/rsa"
"crypto/sha1"
"crypto/rand"
"redoctober/passvault"
)
// UserKeys is the set of decrypted keys in memory, indexed by name.
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
Expiry time.Time
Uses int
// non-public members
aesKey []byte
rsaKey rsa.PrivateKey
}
func matchUser(name string) (out ActiveUser, present bool) {
out, present = UserKeys[name]
return
}
func setUser(in ActiveUser, name string) {
UserKeys[name] = in
}
// mark a use of the key, only for decryption or symmetric encryption
func useKey(name string) {
val, present := matchUser(name)
if present {
val.Uses -= 1
setUser(val, name)
}
}
// GetSummary returns the list of active user keys.
func GetSummary() map[string]ActiveUser {
return UserKeys
}
// FlushCache removes all delegated keys.
func FlushCache() {
for name, _ := range UserKeys {
delete(UserKeys, name)
}
}
// Refresh purges all expired or used up keys.
func Refresh() {
for name, active := range UserKeys {
if active.Expiry.Before(time.Now()) || active.Uses <= 0 {
log.Println("Record expired", name)
delete(UserKeys, name)
}
}
}
// 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) {
var current ActiveUser
Refresh()
// compute exipiration
duration, err := time.ParseDuration(durationString)
if err != nil {
return
}
current.Uses = uses
current.Expiry = time.Now().Add(duration)
// get decryption keys
switch record.Type {
case passvault.AESRecord:
current.aesKey, err = record.GetKeyAES(password)
case passvault.RSARecord:
current.rsaKey, err = record.GetKeyRSA(password)
default:
err = errors.New("Unknown record type")
}
if err != nil {
return
}
// set types
current.Type = record.Type
current.Admin = record.Admin
// add current to map (overwriting previous for this name)
setUser(current, name)
return
}
// EncryptKey encrypts a 16 byte key using the cached key corresponding to name.
// For AES keys, use the cached key.
// For RSA keys, the cache is not necessary use the override key instead.
func EncryptKey(in []byte, name string, override []byte) (out []byte, err error) {
Refresh()
aesKey := override
// if the override key is not set, extract from the cache
if aesKey == nil {
encryptKey, ok := matchUser(name)
if !ok {
return nil, errors.New("Key not delegated")
}
switch encryptKey.Type {
case passvault.AESRecord:
aesKey = encryptKey.aesKey
default:
return out, errors.New("Require override for key")
}
useKey(name)
}
// encrypt
aesSession, err := aes.NewCipher(aesKey)
if err != nil {
return
}
out = make([]byte, 16)
aesSession.Encrypt(out, in)
return
}
// DecryptKey decrypts a 16 byte key using the key corresponding to the name parameter
// for AES keys, the cached AES key is used directly to decrypt in
// for RSA keys, the cached RSA key is used to decrypt the rsaEncryptedKey
// which is then used to decrypt the input buffer.
func DecryptKey(in []byte, name string, rsaEncryptedKey []byte) (out []byte, err error) {
Refresh()
decryptKey, ok := matchUser(name)
if !ok {
return nil, errors.New("Key not delegated")
}
var aesKey []byte
// pick the aesKey to use for decryption
switch decryptKey.Type {
case passvault.AESRecord:
aesKey = decryptKey.aesKey
case passvault.RSARecord:
// extract the aes key from the rsaEncryptedKey
aesKey, err = rsa.DecryptOAEP(sha1.New(), rand.Reader, &decryptKey.rsaKey, rsaEncryptedKey, nil)
if err != nil {
return out, err
}
default:
return nil, errors.New("unknown type")
}
// decrypt
aesSession, err := aes.NewCipher(aesKey)
if err != nil {
return out, err
}
out = make([]byte, 16)
aesSession.Decrypt(out, in)
useKey(name)
return
}

View File

@@ -0,0 +1,80 @@
package keycache
import (
"passvault"
"time"
"testing"
)
var now = time.Now()
var nextYear = now.AddDate(1, 0, 0)
var emptyKey = make([]byte, 16)
var dummy = make([]byte, 16)
func TestUsesFlush(t *testing.T) {
singleUse := ActiveUser{
Admin: true,
Type: passvault.AESRecord,
Expiry: nextYear,
Uses: 2,
aesKey: emptyKey,
}
UserKeys["first"] = singleUse
Refresh()
if len(UserKeys) != 1 {
t.Fatalf("Error in number of live keys")
}
EncryptKey(dummy, "first", nil)
Refresh()
if len(UserKeys) != 1 {
t.Fatalf("Error in number of live keys", UserKeys)
}
DecryptKey(dummy, "first", nil)
Refresh()
if len(UserKeys) != 0 {
t.Fatalf("Error in number of live keys", UserKeys)
}
}
func TestTimeFlush(t *testing.T) {
oneSec, _ := time.ParseDuration("1s")
one := now.Add(oneSec)
singleUse := ActiveUser{
Admin: true,
Type: passvault.AESRecord,
Expiry: one,
Uses: 10,
aesKey: emptyKey,
}
UserKeys["first"] = singleUse
Refresh()
if len(UserKeys) != 1 {
t.Fatalf("Error in number of live keys")
}
EncryptKey(dummy, "first", nil)
Refresh()
if len(UserKeys) != 1 {
t.Fatalf("Error in number of live keys")
}
time.Sleep(oneSec)
_, err := DecryptKey(dummy, "first", nil)
if err == nil {
t.Fatalf("Error in pruning expired key")
}
}

View File

@@ -0,0 +1,29 @@
// Package padding adds and removes padding for AES-CBC mode.
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 {
return nil, errors.New("Padding incorrect")
}
fileLen := len(bytesPadded) - paddingLen
return bytesPadded[:fileLen], 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
}

View File

@@ -0,0 +1,622 @@
// Package passvault manages the vault containing user records on disk.
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"
"encoding/binary"
"errors"
"redoctober/padding"
)
// Constants for record type.
const (
AESRecord = "AES"
RSARecord = "RSA"
ECCRecord = "ECC"
)
// Constants for scrypt.
const (
KEYLENGTH = 16
N = 16384
R = 8
P = 1
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
HashedPassword []byte
KeySalt []byte
AESKey []byte
RSAKey struct {
RSAExp []byte
RSAExpIV []byte
RSAPrimeP []byte
RSAPrimePIV []byte
RSAPrimeQ []byte
RSAPrimeQIV []byte
RSAPublic rsa.PublicKey
}
Admin bool
}
type diskRecords struct {
Version int
VaultId int
HmacKey []byte
Passwords map[string]DiskPasswordRecord
}
// Summary is a minmal 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) {
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
}
func encryptRSARecord(newRec *DiskPasswordRecord, rsaPriv *rsa.PrivateKey, passKey []byte) (err error) {
newRec.RSAKey.RSAExpIV, err = makeRandom(16)
if err != nil {
return
}
paddedExponent := padding.PadClearFile(rsaPriv.D.Bytes())
newRec.RSAKey.RSAExp, err = encryptCBC(paddedExponent, newRec.RSAKey.RSAExpIV, passKey)
if err != nil {
return
}
newRec.RSAKey.RSAPrimePIV, err = makeRandom(16)
if err != nil {
return
}
paddedPrimeP := padding.PadClearFile(rsaPriv.Primes[0].Bytes())
newRec.RSAKey.RSAPrimeP, err = encryptCBC(paddedPrimeP, newRec.RSAKey.RSAPrimePIV, passKey)
if err != nil {
return
}
newRec.RSAKey.RSAPrimeQIV, err = makeRandom(16)
if err != nil {
return
}
paddedPrimeQ := padding.PadClearFile(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) {
newRec.Type = RSARecord
newRec.Salt, err = makeRandom(16)
if err != nil {
return
}
newRec.HashedPassword, err = hashPassword(password, newRec.Salt)
if err != nil {
return
}
newRec.KeySalt, err = makeRandom(16)
if err != nil {
return
}
passKey, err := derivePasswordKey(password, newRec.KeySalt)
if err != nil {
return
}
// generate a key pair
rsaPriv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return
}
// encrypt RSA key with password key
err = encryptRSARecord(&newRec, rsaPriv, passKey)
if err != nil {
return
}
newRec.RSAKey.RSAPublic = rsaPriv.PublicKey
// encrypt AES key with password key
aesKey, err := makeRandom(16)
if err != nil {
return
}
newRec.AESKey, err = encryptECB(aesKey, passKey)
if err != nil {
return
}
newRec.Admin = admin
return
}
func derivePasswordKey(password string, keySalt []byte) (passwordKey []byte, err 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)
if err != nil {
return
}
decryptedData = make([]byte, len(data))
aesCrypt.Decrypt(decryptedData, data)
return
}
// Decrypt bytes using a key in ECB mode.
func encryptECB(data []byte, passwordKey []byte) (encryptedData []byte, err error) {
aesCrypt, err := aes.NewCipher(passwordKey)
if err != nil {
return
}
encryptedData = make([]byte, len(data))
aesCrypt.Encrypt(encryptedData, data)
return
}
// Decrypt using a key and IV.
func decryptCBC(data []byte, iv []byte, passwordKey []byte) (decryptedData []byte, err error) {
aesCrypt, err := aes.NewCipher(passwordKey)
if err != nil {
return
}
ivBytes := append([]byte{}, iv...)
decryptedData = make([]byte, len(data))
aesCBC := cipher.NewCBCDecrypter(aesCrypt, ivBytes)
aesCBC.CryptBlocks(decryptedData, data)
return
}
// Encrypt using a key and IV.
func encryptCBC(data []byte, iv []byte, passwordKey []byte) (encryptedData []byte, err error) {
aesCrypt, err := aes.NewCipher(passwordKey)
if err != nil {
return
}
ivBytes := append([]byte{}, iv...)
encryptedData = make([]byte, len(data))
aesCBC := cipher.NewCBCEncrypter(aesCrypt, ivBytes)
aesCBC.CryptBlocks(encryptedData, data)
return
}
// InitFromDisk reads the record from disk and initialize global context.
func InitFromDisk(path string) {
jsonDiskRecord, err := ioutil.ReadFile(path)
if err == nil {
err = json.Unmarshal(jsonDiskRecord, &records)
}
// validate sizes
formatErr := false
for _, rec := range records.Passwords {
if len(rec.Salt) != 16 {
formatErr = true
}
if len(rec.HashedPassword) != 16 {
formatErr = true
}
if len(rec.KeySalt) != 16 {
formatErr = true
}
if rec.Type == AESRecord {
if len(rec.AESKey) != 16 {
formatErr = true
}
}
if rec.Type == RSARecord {
if len(rec.RSAKey.RSAExp) == 0 || len(rec.RSAKey.RSAExp) % 16 != 0 {
formatErr = true
}
if len(rec.RSAKey.RSAPrimeP) == 0 || len(rec.RSAKey.RSAPrimeP) % 16 != 0 {
formatErr = true
}
if len(rec.RSAKey.RSAPrimeQ) == 0 || len(rec.RSAKey.RSAPrimeQ) % 16 != 0 {
formatErr = true
}
if len(rec.RSAKey.RSAExpIV) != 16 {
formatErr = true
}
if len(rec.RSAKey.RSAPrimePIV) != 16 {
formatErr = true
}
if len(rec.RSAKey.RSAPrimeQIV) != 16 {
formatErr = true
}
}
if formatErr {
err = errors.New("Format error")
break
}
}
if err != nil {
records.Version = DEFAULT_VERSION
records.VaultId = mrand.Int()
records.HmacKey, err = makeRandom(16)
if err != nil {
return
}
// make the record data holder
records.Passwords = make(map[string]DiskPasswordRecord)
}
localPath = path
}
// WriteRecordsToDisk saves the current state of the records to disk.
func WriteRecordsToDisk() (err error) {
if !IsInitialized() {
err = errors.New("Path not initialized")
return
}
jsonDiskRecord, err := json.Marshal(records)
if err == nil {
err = ioutil.WriteFile(localPath, jsonDiskRecord, 0644)
}
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
}
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)
if !ok {
err = errors.New("Record not present")
return
}
err = passwordRec.ValidatePassword(password)
if err != nil {
return
}
// decrypt key
var key []byte
var rsaKey rsa.PrivateKey
if passwordRec.Type == AESRecord {
key, err = passwordRec.GetKeyAES(password)
if err != nil {
return
}
} else if passwordRec.Type == RSARecord {
rsaKey, err = passwordRec.GetKeyRSA(password)
if err != nil {
return
}
} else {
err = errors.New("Unkown record type")
return
}
// create new salt
passwordRec.Salt, err = makeRandom(16)
if err != nil {
return
}
// hash new password
passwordRec.HashedPassword, err = hashPassword(newPassword, passwordRec.Salt)
if err != nil {
return
}
// create new key salt
passwordRec.KeySalt, err = makeRandom(16)
if err != nil {
return
}
newPassKey, err := derivePasswordKey(newPassword, passwordRec.KeySalt)
if err != nil {
return
}
// encrypt original key with new password
if passwordRec.Type == AESRecord {
passwordRec.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)
if err != nil {
return
}
} else {
err = errors.New("Unkown record type")
return
}
SetRecord(passwordRec, name)
// update disk record
err = WriteRecordsToDisk()
if err != nil {
return
}
return
}
// 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
}
// RevokeRecord removes admin status from a record.
func RevokeRecord(name string) error {
rec, ok := GetRecord(name)
if ok {
rec.Admin = false
SetRecord(rec, name)
} else {
return errors.New("Record missing")
}
return nil
}
// MakeAdmin adds admin status to a given record.
func MakeAdmin(name string) error {
rec, ok := GetRecord(name)
if ok {
rec.Admin = true
SetRecord(rec, name)
} else {
return errors.New("Record missing")
}
return nil
}
// SetRecord puts a record into the global status.
func SetRecord(passwordRec DiskPasswordRecord, name string) {
records.Passwords[name] = passwordRec
}
// GetRecord returns a record given a name.
func GetRecord(name string) (passwordRec DiskPasswordRecord, ok bool) {
passwordRec, ok = records.Passwords[name]
return
}
// GetVaultId returns the id of the current vault.
func GetVaultId() (id int, err error) {
if !IsInitialized() {
return 0, errors.New("Path not initialized")
}
return records.VaultId, nil
}
// GetHmacKey returns the hmac key of the current vault.
func GetHmacKey() (key []byte, err error) {
if !IsInitialized() {
return nil, errors.New("Path not initialized")
}
return records.HmacKey, nil
}
// IsInitialized returns true if the disk vault has been loaded.
func IsInitialized() bool {
return localPath != ""
}
// NumRecords returns the number of records in the vault.
func NumRecords() int {
return len(records.Passwords)
}
// GetSummary returns a summary of the records on disk.
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
}
return
}
// IsAdmin returns the admin status of the DiskPasswordRecord.
func (passwordRec DiskPasswordRecord) IsAdmin() bool {
return passwordRec.Admin
}
// GetType returns the type status of the DiskPasswordRecord.
func (passwordRec DiskPasswordRecord) GetType() string {
return passwordRec.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)
}
// GetKeyAES returns the 16-byte key of the record.
func (passwordRec DiskPasswordRecord) GetKeyAES(password string) (key []byte, err error) {
if passwordRec.Type != AESRecord {
return nil, errors.New("Invalid function for record type")
}
err = passwordRec.ValidatePassword(password)
if err != nil {
return
}
passKey, err := derivePasswordKey(password, passwordRec.KeySalt)
if err != nil {
return
}
return decryptECB(passwordRec.AESKey, passKey)
}
// GetKeyAES returns the RSA public key of the record.
func (passwordRec DiskPasswordRecord) GetKeyRSAPub() (out *rsa.PublicKey, err error) {
if passwordRec.Type != RSARecord {
return out, errors.New("Invalid function for record type")
}
return &passwordRec.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 {
return key, errors.New("Invalid function for record type")
}
err = passwordRec.ValidatePassword(password)
if err != nil {
return
}
passKey, err := derivePasswordKey(password, passwordRec.KeySalt)
if err != nil {
return
}
rsaExponentPadded, err := decryptCBC(passwordRec.RSAKey.RSAExp, passwordRec.RSAKey.RSAExpIV, passKey)
if err != nil {
return
}
rsaExponent, err := padding.RemovePadding(rsaExponentPadded)
if err != nil {
return
}
rsaPrimePPadded, err := decryptCBC(passwordRec.RSAKey.RSAPrimeP, passwordRec.RSAKey.RSAPrimePIV, passKey)
if err != nil {
return
}
rsaPrimeP, err := padding.RemovePadding(rsaPrimePPadded)
if err != nil {
return
}
rsaPrimeQPadded, err := decryptCBC(passwordRec.RSAKey.RSAPrimeQ, passwordRec.RSAKey.RSAPrimeQIV, passKey)
if err != nil {
return
}
rsaPrimeQ, err := padding.RemovePadding(rsaPrimeQPadded)
if err != nil {
return
}
key.PublicKey = passwordRec.RSAKey.RSAPublic
key.D = big.NewInt(0).SetBytes(rsaExponent)
key.Primes = []*big.Int{big.NewInt(0), big.NewInt(0)}
key.Primes[0].SetBytes(rsaPrimeP)
key.Primes[1].SetBytes(rsaPrimeQ)
err = key.Validate()
if err != nil {
return
}
return
}
// 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
}
if bytes.Compare(sha, passwordRec.HashedPassword) != 0 {
return errors.New("Wrong Password")
}
return
}

View File

@@ -0,0 +1,68 @@
package passvault
import (
"testing"
)
var emptyKey = make([]byte, 16)
var dummy = make([]byte, 16)
func TestUsesFlush(t *testing.T) {
singleUse := ActiveUser{
Admin: true,
Expiry: nextYear,
Uses: 1,
key: emptyKey,
}
LiveKeys["first"] = singleUse
FlushCache()
if len(LiveKeys) != 1 {
t.Fatalf("Error in number of live keys")
}
EncryptKey(dummy, "first")
FlushCache()
if len(LiveKeys) != 0 {
t.Fatalf("Error in number of live keys")
}
}
func TestTimeFlush(t *testing.T) {
oneSec, _ := time.ParseDuration("1s")
one := now.Add(oneSec)
singleUse := ActiveUser{
Admin: true,
Expiry: one,
Uses: 10,
key: emptyKey,
}
LiveKeys["first"] = singleUse
FlushCache()
if len(LiveKeys) != 1 {
t.Fatalf("Error in number of live keys")
}
EncryptKey(dummy, "first")
FlushCache()
if len(LiveKeys) != 1 {
t.Fatalf("Error in number of live keys")
}
time.Sleep(oneSec)
_, err := DecryptKey(dummy, "first")
if err == nil {
t.Fatalf("Error in pruning expired key")
}
}

View File

@@ -0,0 +1,175 @@
// Package redoctober contains the server code for Red October.
package main
import (
"fmt"
"flag"
"os"
"io/ioutil"
"net"
"net/http"
"crypto/tls"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"redoctober/core"
)
// list of URLs to register
const (
Create string = "/create"
Summary = "/summary"
Delegate = "/delegate"
Password = "/password"
Encrypt = "/encrypt"
Decrypt = "/decrypt"
Modify = "/modify"
)
// the channel handling user request
var process = make(chan userRequest)
type userRequest struct {
rt string
in []byte
resp chan []byte
}
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) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
return
}
response := make(chan []byte, 1)
req := userRequest{rt: requestType, in: body, resp: response}
process <-req
code := <-response
w.Write(code)
}
func NewServer(addr string, certPath string, keyPath string, caPath string) (*http.Server, *net.Listener, error) {
// set up server
mux := http.NewServeMux()
srv := http.Server{
Addr: addr,
Handler: mux,
}
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
if err != nil {
fmt.Println(err)
return nil, nil, err
}
config := tls.Config{
Certificates: []tls.Certificate{cert},
Rand: rand.Reader,
ClientAuth: tls.RequestClientCert,
PreferServerCipherSuites: true,
SessionTicketsDisabled: true,
}
config.Rand = rand.Reader
// create local cert pool if present
if caPath != "" {
rootPool := x509.NewCertPool()
pemCert, err := ioutil.ReadFile(caPath)
if err != nil {
fmt.Println(err)
return nil, nil, err
}
derCert, pemCert := pem.Decode(pemCert)
if derCert == nil {
return nil, nil, err
}
cert, err := x509.ParseCertificate(derCert.Bytes)
if err != nil {
fmt.Println(err)
return nil, nil, err
}
rootPool.AddCert(cert)
config.ClientCAs = rootPool
}
conn, err := net.Listen("tcp", addr)
if err != nil {
fmt.Println(err)
return nil, nil, err
}
lstnr := tls.NewListener(conn, &config)
for _, action := range []string {Create, Summary, Delegate, Password, Encrypt, Decrypt, Modify} {
var requestType = action
mux.HandleFunc(requestType, func(w http.ResponseWriter, r *http.Request) {
queueRequest(requestType, w, r, r.TLS)
})
}
return &srv, &lstnr, nil
}
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
`
func main () {
flag.Usage = func() {
fmt.Fprint(os.Stderr, usage)
flag.PrintDefaults()
os.Exit(2)
}
var vaultPath = flag.String("vaultpath", "/tmp/tmpvault", "Path to the the disk vault")
var addr = flag.String("addr", "localhost:8000", "Server and port separated by :")
var certPath = flag.String("cert", "", "Path of TLS certificate in PEM format")
var keyPath = flag.String("key", "", "Path of TLS private key in PEM format")
var caPath = flag.String("ca", "", "Path of TLS CA for client authentication (optional)")
flag.Parse()
if *vaultPath == "" || *addr == "" || *certPath == "" || *keyPath == "" {
fmt.Fprint(os.Stderr, usage)
flag.PrintDefaults()
os.Exit(2)
}
core.Init(*vaultPath)
s, l, _ := NewServer(*addr, *certPath, *keyPath, *caPath)
s.Serve(*l)
}