mirror of
https://github.com/cloudflare/redoctober.git
synced 2026-01-04 04:04:24 +00:00
[redoctober] Initial logic for alpha server
This commit is contained in:
13
LICENSE.md
Normal file
13
LICENSE.md
Normal 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
63
Makefile
Normal 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
113
README.md
@@ -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
317
src/redoctober/core/core.go
Normal 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)
|
||||
}
|
||||
|
||||
530
src/redoctober/core/core_test.go
Normal file
530
src/redoctober/core/core_test.go
Normal 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")
|
||||
}
|
||||
|
||||
317
src/redoctober/cryptor/cryptor.go
Normal file
317
src/redoctober/cryptor/cryptor.go
Normal 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)
|
||||
}
|
||||
|
||||
187
src/redoctober/keycache/keycache.go
Normal file
187
src/redoctober/keycache/keycache.go
Normal 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
|
||||
}
|
||||
|
||||
80
src/redoctober/keycache/keycache_test.go
Normal file
80
src/redoctober/keycache/keycache_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
29
src/redoctober/padding/padding.go
Normal file
29
src/redoctober/padding/padding.go
Normal 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
|
||||
}
|
||||
|
||||
|
||||
622
src/redoctober/passvault/passvault.go
Normal file
622
src/redoctober/passvault/passvault.go
Normal 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
|
||||
}
|
||||
|
||||
68
src/redoctober/passvault/passvault_test.go
Normal file
68
src/redoctober/passvault/passvault_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
175
src/redoctober/redoctober.go
Normal file
175
src/redoctober/redoctober.go
Normal 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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user