WIP 0.4.0 #2

Closed
armin wants to merge 8 commits from 0.4.0 into master
29 changed files with 944 additions and 350 deletions

10
.drone.yml Normal file
View file

@ -0,0 +1,10 @@
kind: pipeline
type: docker
name: default
steps:
- name: build
image: rust
commands:
- cargo build
- cargo test

2
.gitattributes vendored Normal file
View file

@ -0,0 +1,2 @@
*.enc filter=lfs diff=lfs merge=lfs -text
*.cert filter=lfs diff=lfs merge=lfs -text

230
.gitignore vendored
View file

@ -1,4 +1,228 @@
**target/ # Created by https://www.gitignore.io/api/vim,rust,linux,emacs,windows,intellij+all,visualstudiocode
# Edit at https://www.gitignore.io/?templates=vim,rust,linux,emacs,windows,intellij+all,visualstudiocode
### Emacs ###
# -*- mode: gitignore; -*-
*~
\#*\#
/.emacs.desktop
/.emacs.desktop.lock
*.elc
auto-save-list
tramp
.\#*
# Org-mode
.org-id-locations
*_archive
# flymake-mode
*_flymake.*
# eshell files
/eshell/history
/eshell/lastdir
# elpa packages
/elpa/
# reftex files
*.rel
# AUCTeX auto folder
/auto/
# cask packages
.cask/
dist/
# Flycheck
flycheck_*.el
# server auth directory
/server/
# projectiles files
.projectile
# directory configuration
.dir-locals.el
# network security
/network-security.data
### Intellij+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### Intellij+all Patch ###
# Ignores the whole .idea folder and all .iml files
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
.idea/
# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
*.iml
modules.xml
.idea/misc.xml
*.ipr
# Sonarlint plugin
.idea/sonarlint
### Linux ###
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### Rust ###
# Generated by Cargo
# will have compiled files and executables
/target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk **/*.rs.bk
*.cbor
*.yaml ### Vim ###
# Swap
[._]*.s[a-v][a-z]
[._]*.sw[a-p]
[._]s[a-rt-v][a-z]
[._]ss[a-gi-z]
[._]sw[a-p]
# Session
Session.vim
Sessionx.vim
# Temporary
.netrwhist
# Auto-generated tag files
tags
# Persistent undo
[._]*.un~
# Coc configuration directory
.vim
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
### Windows ###
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# End of https://www.gitignore.io/api/vim,rust,linux,emacs,windows,intellij+all,visualstudiocode

53
Cargo.lock generated
View file

@ -36,13 +36,13 @@ dependencies = [
] ]
[[package]] [[package]]
name = "bitflags" name = "base64"
version = "1.2.1" version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "bumpalo" name = "bitflags"
version = "3.1.1" version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
@ -81,10 +81,12 @@ dependencies = [
[[package]] [[package]]
name = "coffer-client" name = "coffer-client"
version = "0.2.0" version = "0.4.0"
dependencies = [ dependencies = [
"coffer-common 0.4.0",
"env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"exec 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "exec 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_cbor 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", "serde_cbor 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
@ -94,26 +96,28 @@ dependencies = [
[[package]] [[package]]
name = "coffer-common" name = "coffer-common"
version = "0.1.0" version = "0.4.0"
dependencies = [ dependencies = [
"bumpalo 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"hex 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"seckey 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", "seckey 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_cbor 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", "serde_cbor 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)",
"sodiumoxide 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", "sodiumoxide 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)",
"toml 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
name = "coffer-companion" name = "coffer-companion"
version = "0.2.0" version = "0.4.0"
dependencies = [ dependencies = [
"coffer-common 0.1.0", "coffer-common 0.4.0",
"env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"hex 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
@ -125,10 +129,10 @@ dependencies = [
[[package]] [[package]]
name = "coffer-server" name = "coffer-server"
version = "0.2.0" version = "0.4.0"
dependencies = [ dependencies = [
"bytes 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "bytes 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
"coffer-common 0.1.0", "coffer-common 0.4.0",
"env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"hex 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "hex 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -365,11 +369,6 @@ dependencies = [
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "itoa"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "kernel32-sys" name = "kernel32-sys"
version = "0.2.2" version = "0.2.2"
@ -612,11 +611,6 @@ name = "rle-decode-fast"
version = "1.0.1" version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "ryu"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "seckey" name = "seckey"
version = "0.9.1" version = "0.9.1"
@ -653,16 +647,6 @@ dependencies = [
"syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "serde_json"
version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
"ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "serde_yaml" name = "serde_yaml"
version = "0.8.11" version = "0.8.11"
@ -932,8 +916,8 @@ dependencies = [
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
"checksum arc-swap 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d7b8a9123b8027467bce0099fe556c628a53c8d83df0507084c31e9ba2e39aff" "checksum arc-swap 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d7b8a9123b8027467bce0099fe556c628a53c8d83df0507084c31e9ba2e39aff"
"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" "checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90"
"checksum base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
"checksum bumpalo 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8fe2567a8d8a3aedb4e39aa39e186d5673acfd56393c6ac83b2bc5bd82f4369c"
"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" "checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5"
"checksum bytes 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "10004c15deb332055f7a4a208190aed362cf9a7c2f6ab70a305fba50e1105f38" "checksum bytes 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "10004c15deb332055f7a4a208190aed362cf9a7c2f6ab70a305fba50e1105f38"
"checksum cc 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)" = "aa87058dce70a3ff5621797f1506cb837edd02ac4c0ae642b4542dce802908b8" "checksum cc 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)" = "aa87058dce70a3ff5621797f1506cb837edd02ac4c0ae642b4542dce802908b8"
@ -966,7 +950,6 @@ dependencies = [
"checksum hex 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "023b39be39e3a2da62a94feb433e91e8bcd37676fbc8bea371daf52b7a769a3e" "checksum hex 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "023b39be39e3a2da62a94feb433e91e8bcd37676fbc8bea371daf52b7a769a3e"
"checksum humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" "checksum humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
"checksum iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" "checksum iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f"
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
"checksum libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)" = "1a31a0627fdf1f6a39ec0dd577e101440b7db22672c0901fe00a9a6fbb5c24e8" "checksum libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)" = "1a31a0627fdf1f6a39ec0dd577e101440b7db22672c0901fe00a9a6fbb5c24e8"
@ -997,12 +980,10 @@ dependencies = [
"checksum regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dc220bd33bdce8f093101afe22a037b8eb0e5af33592e6a9caafff0d4cb81cbd" "checksum regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dc220bd33bdce8f093101afe22a037b8eb0e5af33592e6a9caafff0d4cb81cbd"
"checksum regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716" "checksum regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716"
"checksum rle-decode-fast 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cabe4fa914dec5870285fa7f71f602645da47c486e68486d2b4ceb4a343e90ac" "checksum rle-decode-fast 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cabe4fa914dec5870285fa7f71f602645da47c486e68486d2b4ceb4a343e90ac"
"checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8"
"checksum seckey 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c819d0a699db7622e4ee55a651f992242f754481f97de3024dc548adcce13237" "checksum seckey 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c819d0a699db7622e4ee55a651f992242f754481f97de3024dc548adcce13237"
"checksum serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)" = "0c4b39bd9b0b087684013a792c59e3e07a46a01d2322518d8a1104641a0b1be0" "checksum serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)" = "0c4b39bd9b0b087684013a792c59e3e07a46a01d2322518d8a1104641a0b1be0"
"checksum serde_cbor 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f7081ed758ec726a6ed8ee7e92f5d3f6e6f8c3901b1f972e3a4a2f2599fad14f" "checksum serde_cbor 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f7081ed758ec726a6ed8ee7e92f5d3f6e6f8c3901b1f972e3a4a2f2599fad14f"
"checksum serde_derive 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)" = "ca13fc1a832f793322228923fbb3aba9f3f44444898f835d31ad1b74fa0a2bf8" "checksum serde_derive 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)" = "ca13fc1a832f793322228923fbb3aba9f3f44444898f835d31ad1b74fa0a2bf8"
"checksum serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)" = "48c575e0cc52bdd09b47f330f646cf59afc586e9c4e3ccd6fc1f625b8ea1dad7"
"checksum serde_yaml 0.8.11 (registry+https://github.com/rust-lang/crates.io-index)" = "691b17f19fc1ec9d94ec0b5864859290dff279dbd7b03f017afda54eb36c3c35" "checksum serde_yaml 0.8.11 (registry+https://github.com/rust-lang/crates.io-index)" = "691b17f19fc1ec9d94ec0b5864859290dff279dbd7b03f017afda54eb36c3c35"
"checksum signal-hook-registry 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94f478ede9f64724c5d173d7bb56099ec3e2d9fc2774aac65d34b8b890405f41" "checksum signal-hook-registry 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94f478ede9f64724c5d173d7bb56099ec3e2d9fc2774aac65d34b8b890405f41"
"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"

View file

@ -1,36 +1,35 @@
* Communication * Communication
** Frame ** Frame
Header ::: content-length: u64 | message-type: u8 ::: 72 bit, fixed Header ::: content-length: u16 | message-type: u8 ::: 3 byte, fixed
Body ::: content: [u8; content-length] ::: conent-length byte, variable Body ::: content: [u8; content-length] ::: conent-length byte, variable
Numbers are in network byte order. Unsigned integers in network byte order.
** Message Types ** Message Types
| Ordinal | Type | Body Format | Direction | Transitions | Description | | Ordinal | Type | Body Format | Direction | Transitions | Description |
|---------+-------+-----------------+-----------+------------------+----------------------------------------------| |---------+-------------+-----------------+-----------+--------------------------+-------------------------------------------|
| 0 | Hello | Public Key | C -> S | Waiting for Link | Initiates communication | | 0x00 | Hello | Client PK | C -> S | Link, KeyNotFound, Error | Initiates communication |
| 1 | Link | <empty> | S -> C | Put, Get | Link established, communication can start | | 0x01 | Link | <empty> | S -> C | Get, Bye | Link established, communication can start |
| 2 | Put | Coffer (sealed) | C -> S | OkPut | Merge a ~Coffer~ for the client | | 0x02 | Get | <empt> | C -> S | OkGet, Error | Retrieve a secrets for the client |
| 3 | Get | Coffer (sealed) | C -> S | OkGet | Retrieve a ~Coffer~ for the client | | 0x03 | OkGet | Coffer (sealed) | S -> C | Bye | Send secrets to the client |
| 4 | OkPut | <empty> | S -> C | Put, Get | ~Coffer~ was successfully merged | | 0x99 | Bye | Client PK | C -> S | • | Close connection |
| 5 | OkGet | Coffer (sealed) | S -> C | Put, Get | Return a sealed ~Coffer~ for a ~Get~ request | | 0xaa | KeyNotFound | Client PK | S -> C | • | PK unknown to server |
| 63 | Bye | | C -> S | | Close connection | | 0xff | Error | UTF-8 String | S -> C | • | Generic server error with reason |
| 127 | Error | | S -> C | | Generic server error |
- Error can be returned at any stage
- Communication can end at any stage. Communication ends when connection is closed by either side.
- Seal is determined by communication direction: - Seal is determined by communication direction:
C -> S: sealed by server public key, client private key C -> S: sealed by server public key, client private key
S -> C: sealed by client public key, server private key S -> C: sealed by client public key, server private key
- Secrets returned as sealed cbor
* Coffer * Coffer
- Multitree with each leave terminating in a Vec<u8> - Sharded KV-Store
- Nodes (except leaves = key path) are utf8 strings - Keys are UTF-8 Strings
- A ~Put~ request must contain a fully determined ~Coffer~ (all leaves are values) - Typed values as defined by TOML: String, Integer, Float, Boolean
- A ~Get~ request contains a partially determined ~Coffer~ (values are ignored) - No Dates support
- If a node resolves to a parent, the subtree (which is also a ~Coffer~) is returned - No binary data support
- If a node resolves to a leave, the partial ~Coffer~ terminating in the leave and its value are returned - Floats and Integers are 32 bit
* Coffer Server * Coffer Server
A ~coffer-server~ can support multiple clients by means of /sharding/ the A ~coffer-server~ can support multiple clients by means of /sharding/ the
keyspace. Clients are uniquely identified by their public key. keyspace. Clients are uniquely identified by their public key.
@ -43,15 +42,13 @@
key. No tampered requests can be sent or communication data collected except key. No tampered requests can be sent or communication data collected except
the private keys are compromised. the private keys are compromised.
* Coffer YAML * Coffer Definition (TOML)
** Secrets Definition Encrypted Authentication: SK of coffer-companion, PK of coffer-server
Encrypted with: SK of coffer-companion, PK of coffer-server
#+BEGIN_SRC yaml #+BEGIN_SRC yaml
# Names for ids (public keys) of clients # IDs (public keys) of clients
[clients] file.id = "AAAA-AAAA-AAAA-AAAA"
file = "AAAA-AAAA-AAAA-AAAA" bin.id = "FFFF-FFFF-FFFF-FFFF"
bin = "FFFF-FFFF-FFFF-FFFF"
# Secrets for a named client (defined in clients) # Secrets for a named client (defined in clients)
[file] [file]
@ -59,10 +56,15 @@
secretkey2 = "secret value2" secretkey2 = "secret value2"
#+END_SRC #+END_SRC
** Secret Response * Coffer Response
file client executes GET to server Encrypted Authentication: SK of coffer-server, PK of coffer-client
Format: cbor
#+BEGIN_SRC yaml CofferResponse = List<CofferSecret>
secretkey = "secret value"
secretkey2 = "secret value2" CofferSecret {
#+END_SRC key: UTF-8 String,
value: CofferValue
}
CofferValue = String | Integer | Float | Boolean | Date

View file

@ -7,7 +7,10 @@ release:
cargo build --release cargo build --release
publish: publish:
docker pull clux/muslrust podman pull clux/muslrust
docker run -v $(CURDIR):/volume --rm -t clux/muslrust cargo build --release podman run -v .:/volume:Z --rm -t clux/muslrust cargo build --release
strip target/x86_64-unknown-linux-musl/release/coffer-server
strip target/x86_64-unknown-linux-musl/release/coffer-client
strip target/x86_64-unknown-linux-musl/release/coffer-companion
.PHONY: default release publish .PHONY: default release publish

View file

@ -1,6 +1,6 @@
[package] [package]
name = "coffer-client" name = "coffer-client"
version = "0.2.0" version = "0.4.0"
authors = ["Armin Friedl <dev@friedl.net>"] authors = ["Armin Friedl <dev@friedl.net>"]
edition = "2018" edition = "2018"
@ -17,3 +17,8 @@ serde_yaml = "0.8"
serde_cbor = "0.10.2" serde_cbor = "0.10.2"
# Executing subcommand # Executing subcommand
exec = "0.3.1" exec = "0.3.1"
# Lighter alternative to tokio for
# driving shared frame creation
futures = "0.3.1"
coffer-common = { path = "../coffer-common" }

View file

@ -1,27 +1,28 @@
#[allow(unused_imports)] #[allow(unused_imports)]
use log::{debug, error, info, trace, warn}; use log::{debug, error, info, trace, warn};
use std::net::SocketAddr;
use env_logger; use env_logger;
use structopt::StructOpt; use structopt::StructOpt;
use std::fs::File;
use std::error::Error; use std:: {
use std::net::TcpStream; net::{SocketAddr, TcpStream},
use std::path::PathBuf; error::Error,
use std::io::BufRead; path::PathBuf,
use std::io::BufReader; io::{Write, Read},
use std::io::Write; convert::{TryInto, TryFrom}
};
use coffer_common::certificate::Certificate;
use coffer_common::coffer::{CofferShard, CofferValue};
#[derive(StructOpt, Debug)] #[derive(StructOpt, Debug)]
struct Args { struct Args {
/// Address of the coffer server /// Address of the coffer server
#[structopt(short, long, parse(try_from_str), env = "COFFER_SERVER_ADDRESS", default_value = "127.0.0.1:9187")] #[structopt(short, long, env = "COFFER_SERVER_ADDRESS", default_value = "127.0.0.1:9187")]
server_address: SocketAddr, server_address: String,
/// Path to the request file sent to the server #[structopt(short, long, parse(from_os_str), env = "COFFER_CLIENT_CERTIFICATE", hide_env_values = true)]
#[structopt(parse(from_os_str), env = "COFFER_REQUEST", hide_env_values = true)] certificate: PathBuf,
secrets: PathBuf,
/// The subcommand spawned by coffer-client /// The subcommand spawned by coffer-client
cmd: String, cmd: String,
@ -34,14 +35,39 @@ fn main() -> Result<(), Box<dyn Error>> {
env_logger::init(); env_logger::init();
let args = Args::from_args(); let args = Args::from_args();
info!{"Connecting to coffer server"} debug!{"Reading certificate"}
let stream: TcpStream = TcpStream::connect(args.server_address)?; let cert = Certificate::new_from_cbor(&args.certificate)?;
info!{"Parsing key requests"} debug!{"Connecting to coffer server"}
let keys = parse_from_path(&args.secrets)?; let mut stream: TcpStream = TcpStream::connect(args.server_address)?;
info!{"Reading secrets"} debug!{"Sending hello"}
retrieve_secrets(&keys, stream)?; let hello = framed(0x00, cert.public_key());
stream.write_all(&hello)?;
debug!{"Sending get"}
let get = framed(0x02, Vec::new());
stream.write_all(&get)?;
debug!{"Reading shard"}
let header = read_header(&mut stream).unwrap();
let shard = read_message(header.0, &mut stream).unwrap();
debug!{"Got encrypted shard {:?}", shard}
debug!{"Sending bye"}
let bye = framed(0x99, Vec::new());
stream.write_all(&bye)?;
debug!{"Decrypting shard"}
let shard_clear = cert.open(&shard).unwrap();
let shard_de = serde_cbor::from_slice::<CofferShard>(&shard_clear).unwrap();
debug!{"Setting environment"}
for (key, val) in shard_de.0 {
if let CofferValue::String(val_s) = val {
std::env::set_var(key.trim(), val_s.trim());
}
}
info!{"Spawning coffer'ed command, reaping coffer"} info!{"Spawning coffer'ed command, reaping coffer"}
reap_coffer(&args.cmd, &args.cmd_args); reap_coffer(&args.cmd, &args.cmd_args);
@ -49,28 +75,7 @@ fn main() -> Result<(), Box<dyn Error>> {
Err("Could not spawn sub-command".into()) Err("Could not spawn sub-command".into())
} }
fn retrieve_secrets(keys: &Vec<String>, mut stream: TcpStream) -> Result<(), Box<dyn Error>>{ fn reap_coffer(cmd: &str, args: &[String]) {
for k in keys {
let buf = serde_cbor::to_vec(&k)?;
info!{"Sending key request {} as {:?}", k, buf}
stream.write_all(&buf.len().to_be_bytes())?;
stream.write_all(&buf)?;
info!{"Reading response"}
let mut reader = BufReader::new(&stream); // get buffered reader for line-wise reading from stream
// read line
let mut resp = String::new();
reader.read_line(&mut resp)?;
info!{"Retrieved secret. Setting environment"}
std::env::set_var(k.trim(), resp.trim());
}
Ok(())
}
fn reap_coffer(cmd: &str, args: &Vec<String>) {
let mut cmd = exec::Command::new(cmd); let mut cmd = exec::Command::new(cmd);
// TODO Push cmd as first arg if not already set? // TODO Push cmd as first arg if not already set?
@ -80,8 +85,76 @@ fn reap_coffer(cmd: &str, args: &Vec<String>) {
error!{"Could not execute sub-command {}", err}; error!{"Could not execute sub-command {}", err};
} }
fn parse_from_path(path: &PathBuf) -> Result<Vec<String>, Box<dyn Error>> { pub fn read_header<T>(reader: &mut T) -> Option<(u64, u8)>
let sec_file = File::open(path)?; where T: Read
{
let mut header: [u8; 9] = [0u8;9]; // header buffer
match reader.read_exact(&mut header)
{
Ok(_) => debug!{"Read {} bytes for header", 9},
Err(err) => {
error!{"Error while reading header: {}", err}
return None;
}
}
Ok(serde_yaml::from_reader::<_, Vec<String>>(sec_file)?) trace!{"Header buffer {:?}", header}
let msg_size: u64 = u64::from_be_bytes(
header[0..8]
.try_into()
.unwrap());
let msg_type: u8 = u8::from_be_bytes(
header[8..9]
.try_into()
.unwrap());
debug!{"Message size: {}, Message type: {}", msg_size, msg_type}
Some((msg_size, msg_type))
}
pub fn read_message<T>(msg_size: u64, reader: &mut T) -> Option<Vec<u8>>
where T: Read
{
// TODO: possible to use unallocated memory instead?
// -> https://doc.rust-lang.org/beta/std/mem/union.MaybeUninit.html
// TODO: 32 bit usize? Can't allocate a 64 bit length buffer anyway?
let mut message = Vec::with_capacity(msg_size.try_into().unwrap());
// need to set the size, because otherwise it is assumed to be 0, since
// the vec is allocated but uninitialized at this point, we don't want to
// pre-allocate a potentially huge buffer with 0x00, so unsafe set size.
unsafe {message.set_len(msg_size.try_into().unwrap());}
match reader.read_exact(&mut message)
{
Ok(_) => debug!{"Read {} bytes for message", msg_size},
Err(err) => {
error!{"Error while reading message: {}", err}
return None;
}
}
trace!{"Read message {:?}", message}
Some(message)
}
pub fn framed(msg_type: u8, data: Vec<u8>) -> Vec<u8>
{
trace!{"Creating frame for type: {:?}, data: {:?}", msg_type, data}
// TODO magic number
let mut frame: Vec<u8> = Vec::with_capacity(data.len() + 72);
unsafe {frame.set_len(8);}
frame.splice(0..8, u64::try_from(data.len())
.unwrap()
.to_be_bytes()
.iter()
.cloned());
frame.push(msg_type);
frame.extend(&data);
frame
} }

View file

@ -1,6 +1,6 @@
[package] [package]
name = "coffer-common" name = "coffer-common"
version = "0.1.0" version = "0.4.0"
authors = ["armin"] authors = ["armin"]
edition = "2018" edition = "2018"
@ -15,13 +15,15 @@ export = []
# Base tools # Base tools
log = "^0.4" log = "^0.4"
env_logger = "^0.7" env_logger = "^0.7"
quick-error = "^1.2"
# Serialization
serde = { version = "^1.0", features = ["derive"]} serde = { version = "^1.0", features = ["derive"]}
serde_cbor = "^0.10" serde_cbor = "^0.10"
serde_json = "^1.0"
toml = "^0.5" toml = "^0.5"
quick-error = "^1.2" base64 = "^0.11"
hex = "^0.4"
# Key management/Cryptography # Key management/Cryptography
sodiumoxide = "^0.2" sodiumoxide = "^0.2"
seckey = "^0.9" seckey = "^0.9"
# Memory management #Communication
bumpalo = { version = "^3.1", features = ["collections"]} tokio = { version="^0.2.9", features = ["full"]}

View file

@ -83,6 +83,15 @@ impl Certificate {
Ok(cbor) Ok(cbor)
} }
pub fn public_key(&self) -> Vec<u8> {
self.inner.read().public_key.as_ref().to_owned()
}
#[cfg(feature = "export")]
pub fn secret_key(&self) -> Vec<u8> {
self.inner.read().private_key.as_ref().to_owned()
}
pub fn open(&self, c: &[u8]) -> Result<Vec<u8>, CertificateError> { pub fn open(&self, c: &[u8]) -> Result<Vec<u8>, CertificateError> {
let pk = &self.inner.read().public_key; let pk = &self.inner.read().public_key;
let sk = &self.inner.read().private_key; let sk = &self.inner.read().private_key;
@ -90,6 +99,12 @@ impl Certificate {
sealedbox::open(c, pk, sk) sealedbox::open(c, pk, sk)
.map_err(|_| CertificateError::Crypto) .map_err(|_| CertificateError::Crypto)
} }
pub fn seal(&self, message: &[u8]) -> Result<Vec<u8>, CertificateError> {
let pk = &self.inner.read().public_key;
Ok(sealedbox::seal(message, pk))
}
} }
impl <T: AsRef<Path>> From<T> for Certificate { impl <T: AsRef<Path>> From<T> for Certificate {

View file

@ -1,8 +1,14 @@
//! A storage container for client data //! A storage container for client data
#[allow(unused_imports)] #[allow(unused_imports)]
use log::{debug, error, info, trace, warn}; use log::{debug, error, info, trace, warn};
use std::fmt::Debug;
use std::path::Path;
use std::fs::File;
use std::io::{BufReader, Read};
use toml::Value as TomlValue;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use quick_error::quick_error; use quick_error::quick_error;
@ -29,39 +35,104 @@ pub enum CofferValue {
String(String), String(String),
/// A 32-bit integer /// A 32-bit integer
Integer(i32), Integer(i32),
/// An opaque blob of data /// A 32-bit float
Blob(Vec<u8>) Float(f32),
// A boolean
Boolean(bool)
} }
/// A path to a value #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[derive(Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)] pub struct CofferKey {
pub struct CofferPath(pub Vec<String>); pub shard: String,
pub key: String
}
#[derive(Debug, Serialize, Deserialize)]
pub struct CofferShard(pub Vec<(String, CofferValue)>);
/// Interface for interacting with a `Coffer` /// Interface for interacting with a `Coffer`
pub trait Coffer { pub trait Coffer {
/// Put `value` at `path`. Errors if there is already a value at `path`. /// Put `value` at `path`. Errors if there is already a value at `path`.
fn put(&mut self, path: CofferPath, value: CofferValue) -> CofferResult<()>; fn put(&mut self, key: CofferKey, value: CofferValue) -> CofferResult<()>;
/// Push `value` to `path`. Replaces existing values. /// Push `value` to `path`. Replaces existing values.
fn push(&mut self, path: CofferPath, value: CofferValue); fn push(&mut self, key: CofferKey, value: CofferValue);
/// Retrieve `value` at path. Errors if there is no `value` at path. /// Retrieve `value` at path. Errors if there is no `value` at path.
fn get(&self, path: CofferPath) -> CofferResult<CofferValue>; fn get(&self, key: &CofferKey) -> CofferResult<CofferValue>;
}
impl <T> From<Vec<T>> for CofferPath /// Retrieve `value` at path. Errors if there is no `value` at path.
where T: AsRef<str> fn get_shard<T>(&self, shard: T) -> CofferResult<CofferShard>
{ where T: AsRef<str>;
fn from(val: Vec<T>) -> Self {
CofferPath::from(&val) fn from_toml_file(toml_path: &Path) -> Self
} where Self: Coffer + Default
} {
// read the secrets file into a temporary string
impl <T> From<&Vec<T>> for CofferPath let mut file = BufReader::new(File::open(toml_path).unwrap());
where T: AsRef<str> let mut secrets_buf = String::new();
{ file.read_to_string(&mut secrets_buf).unwrap();
fn from(val: &Vec<T>) -> Self {
let col = val.iter().map(|p| {(*p).as_ref().to_owned()}).collect(); Coffer::from_toml(&secrets_buf)
CofferPath(col) }
fn from_toml(toml: &str) -> Self
where Self: Coffer + Default
{
// call implementation to create an empty coffer
let mut coffer = Self::default();
// parse the string into a toml Table
let clients: toml::value::Table = match toml.parse::<TomlValue>().unwrap() {
TomlValue::Table(t) => t,
_ => panic!{"Invalid secrets file"}
};
coffer.from_toml_table(&clients);
coffer
}
fn from_toml_table(&mut self, toml_table: &toml::value::Table) {
// table has an no id, recourse into subtables
if toml_table.get("id").is_none() {
for (_key, val) in toml_table.iter() {
match val {
TomlValue::Table(subtable) => {
self.from_toml_table(subtable);
},
_ => panic!{"Invalid secrets file"}
}
}
return;
}
/*
* Parse a single shard/table, this is known to have an id
*
* [files]
* id = "ABC-DEF-GHE"
* secret_string = "secret value1"
* secret_int = 12345
* secret_bool = true
*/
let shard = toml_table.get("id").and_then(|id| id.as_str()).unwrap();
for (key, val) in toml_table {
if "id" == key { continue } // ids are for sharding
let value = match val {
TomlValue::String(s) => CofferValue::String(s.to_owned()),
TomlValue::Integer(i) => CofferValue::Integer(*i as i32),
TomlValue::Float(f) => CofferValue::Float(*f as f32),
TomlValue::Boolean(b) => CofferValue::Boolean(*b),
_ => panic!{"Value {:?} unsupported", val}
};
let key = key.to_owned();
let shard = shard.to_string();
self.put(CofferKey{shard, key}, value).unwrap();
}
} }
} }

View file

@ -1,12 +1,15 @@
#[allow(unused_imports)] #[allow(unused_imports)]
use log::{debug, error, info, trace, warn}; use log::{debug, error, info, trace, warn};
use std::path::Path;
use std::collections::HashMap; use std::collections::HashMap;
use quick_error::quick_error; use quick_error::quick_error;
use sodiumoxide::crypto::box_; use sodiumoxide::crypto::box_;
use sodiumoxide::crypto::sealedbox; use sodiumoxide::crypto::sealedbox;
use toml::Value as TomlValue;
use crate::certificate::{Certificate, CertificateError}; use crate::certificate::{Certificate, CertificateError};
quick_error! { quick_error! {
@ -17,6 +20,12 @@ quick_error! {
Certificate(err: CertificateError) { Certificate(err: CertificateError) {
from() from()
} }
HexDecodeError(err: hex::FromHexError) {
from()
}
IoError(err: std::io::Error) {
from()
}
Msg(err: &'static str) { Msg(err: &'static str) {
from(err) from(err)
display("{}", err) display("{}", err)
@ -40,6 +49,49 @@ impl Keyring {
} }
} }
pub fn new_from_path<T>(certificate_path: T) -> Keyring
where T: AsRef<Path>
{
Keyring {
certificate: Certificate::from(certificate_path),
known_keys: HashMap::new()
}
}
pub fn add_known_keys_toml(&mut self, toml: &str) -> Result<(), KeyringError> {
// parse the string into a toml Table
let clients: toml::value::Table = match toml.parse::<TomlValue>().unwrap() {
TomlValue::Table(t) => t,
_ => panic!{"Invalid secrets file"}
};
self.add_known_keys_toml_table(&clients)?;
debug!{"Known keys {:?}", self.known_keys}
Ok(())
}
fn add_known_keys_toml_table(&mut self, toml_table: &toml::value::Table) -> Result<(), KeyringError> {
// table has an no id, recourse into subtables
if toml_table.get("id").is_none() {
debug!{"{:?}", toml_table}
for (_key, val) in toml_table.iter() {
match val {
TomlValue::Table(subtable) => {
self.add_known_keys_toml_table(subtable)?;
},
_ => panic!{"Invalid secrets file"}
}
}
return Ok(());
}
let shard = toml_table.get("id").and_then(|id| id.as_str()).ok_or(KeyringError::Msg("Invalid key parsing state"))?;
self.add_known_key(&hex::decode(shard)?)
}
pub fn add_known_key(&mut self, key: &[u8]) -> Result<(), KeyringError> { pub fn add_known_key(&mut self, key: &[u8]) -> Result<(), KeyringError> {
let public_key = box_::PublicKey::from_slice(key) let public_key = box_::PublicKey::from_slice(key)
.ok_or(KeyringError::InvalidClientKey)?; .ok_or(KeyringError::InvalidClientKey)?;
@ -50,7 +102,7 @@ impl Keyring {
pub fn open(&self, message: &[u8]) -> Result<Vec<u8>, KeyringError> { pub fn open(&self, message: &[u8]) -> Result<Vec<u8>, KeyringError> {
self.certificate.open(message) self.certificate.open(message)
.map_err(|e| KeyringError::from(e)) .map_err(KeyringError::from)
} }
pub fn seal(&self, client: &[u8], message: &[u8]) -> Result<Vec<u8>, KeyringError> { pub fn seal(&self, client: &[u8], message: &[u8]) -> Result<Vec<u8>, KeyringError> {

View file

@ -1,6 +1,6 @@
[package] [package]
name = "coffer-companion" name = "coffer-companion"
version = "0.2.0" version = "0.4.0"
authors = ["Armin Friedl <dev@friedl.net>"] authors = ["Armin Friedl <dev@friedl.net>"]
edition = "2018" edition = "2018"
@ -14,6 +14,7 @@ structopt = "0.3"
quick-error = "1.2" quick-error = "1.2"
# Key management/Cryptography # Key management/Cryptography
sodiumoxide = "0.2.5" sodiumoxide = "0.2.5"
hex = "^0.4"
# Communication # Communication
serde = { version = "1.0", features = ["derive"]} serde = { version = "1.0", features = ["derive"]}
serde_cbor = "0.10.2" serde_cbor = "0.10.2"

View file

@ -13,5 +13,11 @@ pub fn generate_key(out: PathBuf) {
.expect(&format!{"Could not create out file {}", &out.display()}); .expect(&format!{"Could not create out file {}", &out.display()});
writer.write_all(&cert).unwrap(); writer.write_all(&cert).unwrap();
}
pub fn info(out: PathBuf) {
let cert = Certificate::new_from_cbor(out).unwrap();
println!{"Public Key: {}", hex::encode_upper(cert.public_key())}
println!{"Secret Key: {}", hex::encode_upper(cert.secret_key())}
} }

View file

@ -0,0 +1,110 @@
#[allow(unused_imports)]
use log::{debug, error, info, trace, warn};
use std::path::PathBuf;
use std::convert::{TryFrom, TryInto};
use std::net::{TcpStream};
use std::io::{Write, Read};
use coffer_common::certificate::Certificate;
use coffer_common::coffer::CofferShard;
use serde_cbor;
pub fn print_get(out: PathBuf) {
let cert = Certificate::new_from_cbor(out).unwrap();
let hello = framed(0x00, cert.public_key());
let get = framed(0x02, Vec::new());
let bye = framed(0x99, Vec::new());
let mut listener = TcpStream::connect("127.0.0.1:9187").unwrap();
listener.write_all(&hello).unwrap();
listener.write_all(&get).unwrap();
let header = read_header(&mut listener).unwrap();
let shard = read_message(header.0, &mut listener).unwrap();
debug!{"Got encrypted shard {:?}", shard}
listener.write_all(&bye).unwrap();
let shard_clear = cert.open(&shard).unwrap();
let shard_de = serde_cbor::from_slice::<CofferShard>(&shard_clear).unwrap();
println!{"{:?}", shard_de}
}
fn framed(msg_type: u8, data: Vec<u8>) -> Vec<u8>
{
trace!{"Creating frame for type: {:?}, data: {:?}", msg_type, data}
// TODO magic number
let mut frame: Vec<u8> = Vec::with_capacity(data.len() + 72);
unsafe {frame.set_len(8);}
frame.splice(0..8, u64::try_from(data.len())
.unwrap()
.to_be_bytes()
.iter()
.cloned());
frame.push(msg_type);
frame.extend(&data);
frame
}
fn read_header<T>(reader: &mut T) -> Option<(u64, u8)>
where T: Read
{
let mut header: [u8; 9] = [0u8;9]; // header buffer
match reader.read_exact(&mut header)
{
Ok(_) => debug!{"Read {} bytes for header", 9},
Err(err) => {
error!{"Error while reading header: {}", err}
return None;
}
}
trace!{"Header buffer {:?}", header}
let msg_size: u64 = u64::from_be_bytes(
header[0..8]
.try_into()
.unwrap());
let msg_type: u8 = u8::from_be_bytes(
header[8..9]
.try_into()
.unwrap());
debug!{"Message size: {}, Message type: {}", msg_size, msg_type}
Some((msg_size, msg_type))
}
fn read_message<T>(msg_size: u64, reader: &mut T) -> Option<Vec<u8>>
where T: Read
{
// TODO: possible to use unallocated memory instead?
// -> https://doc.rust-lang.org/beta/std/mem/union.MaybeUninit.html
// TODO: 32 bit usize? Can't allocate a 64 bit length buffer anyway?
let mut message = Vec::with_capacity(msg_size.try_into().unwrap());
// need to set the size, because otherwise it is assumed to be 0, since
// the vec is allocated but uninitialized at this point, we don't want to
// pre-allocate a potentially huge buffer with 0x00, so unsafe set size.
unsafe {message.set_len(msg_size.try_into().unwrap());}
match reader.read_exact(&mut message)
{
Ok(_) => debug!{"Read {} bytes for message", msg_size},
Err(err) => {
error!{"Error while reading message: {}", err}
return None;
}
}
trace!{"Read message {:?}", message}
Some(message)
}

View file

@ -2,13 +2,16 @@ use coffer_common::certificate::Certificate;
use std::path::PathBuf; use std::path::PathBuf;
use std::fs::File; use std::fs::File;
use std::io::Read;
use std::io::Write; use std::io::Write;
use serde::Deserialize; #[allow(unused)]
use serde_yaml;
pub fn encrypt_yaml(yaml:PathBuf, out: PathBuf, certificate: PathBuf) { pub fn encrypt_yaml(yaml:PathBuf, out: PathBuf, certificate: PathBuf) {
let cert = Certificate::new_from_cbor(certificate).unwrap(); let cert = Certificate::new_from_cbor(certificate).unwrap();
let mut secrets = Vec::new();
File::open(yaml).unwrap().read_to_end(&mut secrets).unwrap();
let let sealed = cert.seal(&secrets).unwrap();
let mut out_file = File::create(out).unwrap();
out_file.write_all(&sealed);
} }

View file

@ -1,20 +1,27 @@
use std::path::PathBuf; use std::path::PathBuf;
use structopt::StructOpt; use structopt::StructOpt;
mod generate; mod certificate;
mod encrypt; mod encrypt;
mod client;
#[derive(StructOpt, Debug)] #[derive(StructOpt, Debug)]
enum Args { enum Args {
Certificate { Certificate {
#[structopt(short, long, parse(from_os_str))] #[structopt(short, long, parse(from_os_str))]
out: PathBuf out: PathBuf,
#[structopt(short, long)]
info: bool,
}, },
Encrypt { Encrypt {
#[structopt(short, long, parse(from_os_str))]
certificate: PathBuf,
#[structopt(short, long, parse(from_os_str))] #[structopt(short, long, parse(from_os_str))]
yaml: PathBuf, yaml: PathBuf,
#[structopt(short, long, parse(from_os_str))] #[structopt(short, long, parse(from_os_str))]
out: PathBuf, out: PathBuf
},
Client {
#[structopt(short, long, parse(from_os_str))] #[structopt(short, long, parse(from_os_str))]
certificate: PathBuf, certificate: PathBuf,
} }
@ -24,7 +31,15 @@ fn main() {
let args: Args = Args::from_args(); let args: Args = Args::from_args();
match args { match args {
Args::Certificate {out} => generate::generate_key(out), Args::Certificate {out, info} => {
Args::Encrypt {yaml, out, certificate} => {} if info { certificate::info(out) }
else { certificate::generate_key(out) }
}
Args::Encrypt {certificate, yaml, out} => {
encrypt::encrypt_yaml(yaml, out, certificate)
}
Args::Client {certificate} => {
client::print_get(certificate)
}
} }
} }

View file

@ -1,6 +1,6 @@
[package] [package]
name = "coffer-server" name = "coffer-server"
version = "0.2.0" version = "0.4.0"
authors = ["Armin Friedl <dev@friedl.net>"] authors = ["Armin Friedl <dev@friedl.net>"]
edition = "2018" edition = "2018"

View file

@ -1,47 +1,91 @@
//! A simple, thread-safe coffer implementation backed by a hash map //! Thread-safe coffer implementation backed by hash map
#[allow(unused_imports)] #[allow(unused_imports)]
use log::{debug, error, info, trace, warn}; use log::{debug, error, info, trace, warn};
use std::sync::RwLock; use std::sync::RwLock;
use std::sync::RwLockReadGuard;
use std::sync::RwLockWriteGuard;
use std::collections::HashMap; use std::collections::HashMap;
use coffer_common::coffer::*; use coffer_common::coffer::*;
pub struct CofferMap { type ShardedCoffer = HashMap<String, HashMap<String, CofferValue>>;
coffer: RwLock<HashMap<CofferPath, CofferValue>> pub struct CofferMap(RwLock<ShardedCoffer>);
}
impl CofferMap { impl CofferMap {
pub fn new() -> CofferMap { pub fn new() -> CofferMap {
CofferMap { CofferMap(RwLock::new(HashMap::new()))
coffer: RwLock::new(HashMap::new())
} }
fn read(&self) -> RwLockReadGuard<'_, ShardedCoffer> {
self.0.read().unwrap()
}
fn write(&self) -> RwLockWriteGuard<'_, ShardedCoffer> {
self.0.write().unwrap()
} }
} }
impl Coffer for CofferMap { impl Coffer for CofferMap {
fn put(&mut self, path: CofferPath, value: CofferValue) -> CofferResult<()> { fn put(&mut self, key: CofferKey, value: CofferValue) -> CofferResult<()> {
let mut lock = self.coffer.write().unwrap(); let mut lock = self.write();
match (*lock).contains_key(&path) { match lock.get_mut(&key.shard) {
true => Err(CofferError::Msg("test")), Some(shard) => {
false => {(*lock).insert(path, value); Ok(())} if shard.contains_key(&key.key) { Err(CofferError::Msg("Key exists")) }
else { shard.insert(key.key, value); Ok(()) }
}
None => {
lock.insert(key.shard.clone(), HashMap::new());
lock.get_mut(&key.shard).unwrap().insert(key.key, value);
Ok(())
}
} }
} }
fn push(&mut self, path: CofferPath, value: CofferValue) { fn push(&mut self, key: CofferKey, value: CofferValue) {
let mut lock = self.coffer.write().unwrap(); let mut lock = self.write();
(*lock).insert(path, value); match lock.get_mut(&key.shard) {
Some(shard) => {
shard.insert(key.key, value);
}
None => {
lock.insert(key.shard.clone(), HashMap::new());
lock.get_mut(&key.shard).unwrap().insert(key.key, value);
}
}
} }
fn get(&self, path: CofferPath) -> CofferResult<CofferValue> { fn get(&self, key: &CofferKey) -> CofferResult<CofferValue> {
let lock = self.coffer.read().unwrap(); let lock = self.read();
(*lock).get(&path) let res = lock.get(&key.shard)
.and_then(|v| Some(v.clone())) .and_then( |shard| { shard.get(&key.key) } )
.ok_or(CofferError::Msg("Key not found")) .ok_or(CofferError::Msg("Key not found"))?;
Ok(res.clone())
}
fn get_shard<T>(&self, shard: T) -> CofferResult<CofferShard>
where T: AsRef<str>
{
let lock = self.read();
debug!{"Coffer {:?}", *lock}
let coffer_shard = lock.get(shard.as_ref())
.ok_or(CofferError::Msg("Shard not found"))?;
let mut res = CofferShard(Vec::new());
for (k,v) in coffer_shard {
res.0.push((k.clone(), v.clone()));
}
Ok(res)
} }
} }

View file

@ -4,33 +4,36 @@ use log::{debug, error, info, trace, warn};
use env_logger; use env_logger;
use std::path::PathBuf; use std::path::PathBuf;
use std::fs::File;
use std::io::{Read};
use structopt::StructOpt; use structopt::StructOpt;
use std::net::SocketAddr;
use coffer_common::certificate::Certificate;
use coffer_common::keyring::Keyring; use coffer_common::keyring::Keyring;
use coffer_common::coffer::Coffer;
mod server; mod server;
mod coffer_map; mod coffer_map;
mod protocol; mod protocol;
use server::ServerBuilder; use server::Server;
use coffer_map::CofferMap; use coffer_map::CofferMap;
#[derive(StructOpt, Debug)] #[derive(StructOpt, Debug)]
struct Args { struct Args {
/// Path to the server certificate. Will be deleted after processing. /// Path to the server certificate. Will be deleted after processing.
#[structopt(short, long, parse(from_os_str), env = "COFFER_SERVER_CERTIFICATE", hide_env_values = true)] #[structopt(short, long, parse(from_os_str), env = "COFFER_SERVER_CERTIFICATE", hide_env_values = true)]
certificate: Option<PathBuf>, certificate: PathBuf,
/// Path to secrets file. Will be deleted after processing. /// Path to secrets file. Will be deleted after processing.
/// Must be sealed by the public key of the server certificate /// Must be sealed by the public key of the server certificate
#[structopt(short, long, parse(from_os_str), env = "COFFER_SERVER_SECRETS", hide_env_values = true)] #[structopt(short, long, parse(from_os_str), env = "COFFER_SERVER_SECRETS", hide_env_values = true)]
secrets: Option<PathBuf>, secrets: PathBuf,
/// Address, the coffer server should bind to /// Address, the coffer server should bind to
#[structopt(short, long, parse(try_from_str), env = "COFFER_SERVER_ADDRESS", default_value = "127.0.0.1:9187")] #[structopt(short, long, env = "COFFER_SERVER_ADDRESS", default_value = "127.0.0.1:9187")]
address: SocketAddr, address: String, // unfortunately we have to take a opaque string here,
// since we can't parse a hostname otherwise.
// Parsers are not customizable yet in structopt
} }
#[tokio::main] #[tokio::main]
@ -40,12 +43,23 @@ async fn main() {
_print_banner(); _print_banner();
let server = ServerBuilder::new() // create keyring from server certificate
.with_keyring(args.certificate.and_then(|cert_path| Some(Keyring::new(Certificate::from(cert_path))))) let mut keyring = Keyring::new_from_path(&args.certificate);
.with_coffer(Some(CofferMap::new()))
.build()
.expect("Couldn't build server");
// decrypt secrets file and put into coffer
let mut secrets_file = File::open(&args.secrets).unwrap();
let mut secrets_buf = Vec::new();
secrets_file.read_to_end(&mut secrets_buf).unwrap();
let secrets_buf_clear = String::from_utf8(keyring.open(&secrets_buf).unwrap()).unwrap();
// read known client ids from secrets file
keyring.add_known_keys_toml(&secrets_buf_clear).unwrap();
// read secrets from secrets file
let coffer = CofferMap::from_toml(&secrets_buf_clear);
// start server
let server = Server::new(keyring, coffer);
server.run(args.address).await; server.run(args.address).await;
} }

View file

@ -2,23 +2,18 @@
use log::{debug, error, info, trace, warn}; use log::{debug, error, info, trace, warn};
use std::sync::Arc; use std::sync::Arc;
use std::convert::{TryFrom, TryInto};
use std::net::Shutdown; use std::net::Shutdown;
use tokio::io::{AsyncRead, use tokio::io::AsyncWriteExt;
AsyncReadExt,
AsyncWriteExt};
use tokio::net::TcpStream; use tokio::net::TcpStream;
use tokio::sync::RwLock;
use serde_cbor; use serde_cbor;
use quick_error::quick_error; use quick_error::quick_error;
use coffer_common::coffer::{CofferValue, use coffer_common::coffer::Coffer;
CofferPath,
Coffer};
use coffer_common::keyring::Keyring; use coffer_common::keyring::Keyring;
use hex; use hex;
quick_error! { quick_error! {
@ -38,25 +33,23 @@ quick_error! {
enum State { enum State {
Start, Start,
Link, Link,
Error, Bye,
End End
} }
#[derive(Debug)] #[derive(Debug)]
enum Request { enum Request {
Hello(Vec<u8>), Hello(Vec<u8>),
Put(Vec<u8>), Get,
Get(Vec<u8>), Bye
Bye,
Error
} }
pub struct Protocol<C> pub struct Protocol<C>
where C: Coffer where C: Coffer
{ {
stream: TcpStream, stream: TcpStream,
coffer: Arc<RwLock<C>>, coffer: Arc<C>,
keyring: Arc<RwLock<Keyring>>, keyring: Arc<Keyring>,
client: Option<Vec<u8>>, client: Option<Vec<u8>>,
state: State state: State
} }
@ -64,11 +57,7 @@ where C: Coffer
impl<C> Protocol<C> impl<C> Protocol<C>
where C: Coffer where C: Coffer
{ {
pub fn new( pub fn new(stream: TcpStream, coffer: Arc<C>, keyring: Arc<Keyring>) -> Protocol<C>
stream: TcpStream,
coffer: Arc<RwLock<C>>,
keyring: Arc<RwLock<Keyring>>
) -> Protocol<C>
{ {
let state = State::Start; let state = State::Start;
let client = None; let client = None;
@ -93,26 +82,71 @@ where C: Coffer
// TODO restrict msg_size more, otherwise bad client could bring server // TODO restrict msg_size more, otherwise bad client could bring server
// to allocate vast amounts of memory // to allocate vast amounts of memory
let (msg_size, msg_type) = Self::read_header(&mut reader).await let (msg_size, msg_type) = frame::read_header(&mut reader).await
.unwrap(); .unwrap();
// TODO only read message if message expected by message type // TODO only read message if message expected by message type
// currently relies on client sending good message // currently relies on client sending good message
// (0x00 message size) // (0x00 message size)
let message = Self::read_message(msg_size, &mut reader).await let message = frame::read_message(msg_size, &mut reader).await
.unwrap(); .unwrap();
match msg_type { match msg_type {
0x00 => Request::Hello(message), 0x00 => Request::Hello(message),
0x02 => Request::Put(message), 0x02 => Request::Get,
0x03 => Request::Get(message), 0x99 => Request::Bye,
0x63 => Request::Bye, _ => panic!{"Invalid message type {}", msg_type}
0xff => Request::Error,
_ => Request::Error
} }
} }
async fn read_header<T>(reader: &mut T) -> Option<(u64, u8)> async fn transit(&mut self, event: Request)
{
match (&self.state, event) {
(State::Start, Request::Hello(pk)) => {
debug!{"Reading public key"}
self.client = Some(pk);
self.state = State::Link;
}
(State::Link, Request::Get) => {
debug!{"Writing response"}
let shard_id = hex::encode_upper(self.client.as_ref().unwrap());
let res = self.coffer
.get_shard(shard_id)
.unwrap();
let response = self.keyring.seal(
&self.client.as_ref().unwrap(),
&serde_cbor::to_vec(&res).unwrap()
).unwrap();
// TODO magic number
let frame = frame::framed(0x05u8, response).await;
trace!{"OkGet Frame: {:?}", frame}
// TODO Proper result handling
self.stream.write_all(&frame).await.unwrap();
self.stream.flush().await.unwrap();
self.state = State::Bye;
}
(State::Link, Request::Bye) => self.state = State::End,
(State::Bye, Request::Bye) => self.state = State::End,
_ => self.state = State::End
}
}
}
mod frame {
#[allow(unused_imports)]
use log::{debug, error, info, trace, warn};
use std::convert::{TryFrom, TryInto};
use tokio::io::{AsyncRead, AsyncReadExt};
pub async fn read_header<T>(reader: &mut T) -> Option<(u64, u8)>
where T: AsyncRead + Unpin where T: AsyncRead + Unpin
{ {
let mut header: [u8; 9] = [0u8;9]; // header buffer let mut header: [u8; 9] = [0u8;9]; // header buffer
@ -141,7 +175,7 @@ where C: Coffer
Some((msg_size, msg_type)) Some((msg_size, msg_type))
} }
async fn read_message<T>(msg_size: u64, reader: &mut T) -> Option<Vec<u8>> pub async fn read_message<T>(msg_size: u64, reader: &mut T) -> Option<Vec<u8>>
where T: AsyncRead + Unpin where T: AsyncRead + Unpin
{ {
// TODO: possible to use unallocated memory instead? // TODO: possible to use unallocated memory instead?
@ -166,80 +200,7 @@ where C: Coffer
Some(message) Some(message)
} }
async fn transit(&mut self, event: Request) pub async fn framed(msg_type: u8, data: Vec<u8>) -> Vec<u8>
{
match (&self.state, event) {
(State::Start, Request::Hello(pk)) => {
debug!{"Reading public key"}
self.keyring.write().await
.add_known_key(&pk)
.unwrap();
self.client = Some(pk);
self.state = State::Link;
}
(State::Link, Request::Get(req)) => {
debug!{"Writing response"}
let mut req: CofferPath =
serde_cbor::from_slice(
&self.keyring.read().await
.open(&req)
.unwrap()
).unwrap();
req.0.insert(0, hex::encode(self.client.as_ref().unwrap()));
let res = self.coffer.read().await
.get(req)
.unwrap();
let response = self.keyring.read().await
.seal(
&self.client.as_ref().unwrap(),
&serde_cbor::to_vec(&res).unwrap()
).unwrap();
// TODO magic number
let frame = Self::framed(0x05u8, response).await;
trace!{"OkGet Frame: {:?}", frame}
// TODO Proper result handling
self.stream.write_all(&frame).await.unwrap();
self.state = State::Link;
}
(State::Link, Request::Put(put)) => {
debug!{"Putting secrets"}
let mut put: Vec<(CofferPath, CofferValue)> =
serde_cbor::from_slice(
&self.keyring.read().await
.open(&put)
.unwrap()
).unwrap();
let key_string = hex::encode(self.client.as_ref().unwrap());
put.iter_mut().map( |(cp, _cv)| &mut cp.0)
.for_each(|cp| cp.insert(0, key_string.clone()));
for (coffer_path, coffer_value) in put {
self.coffer.write().await
.put(coffer_path, coffer_value)
.unwrap();
}
self.state = State::Link;
}
(_, Request::Bye) => self.state = State::End,
(_, Request::Error) => self.state = State::End,
_ => self.state = State::End
}
}
async fn framed(msg_type: u8, data: Vec<u8>) -> Vec<u8>
{ {
trace!{"Creating frame for type: {:?}, data: {:?}", msg_type, data} trace!{"Creating frame for type: {:?}, data: {:?}", msg_type, data}
@ -258,4 +219,5 @@ where C: Coffer
frame frame
} }
} }

View file

@ -5,14 +5,13 @@ use quick_error::quick_error;
use tokio::net::{TcpListener}; use tokio::net::{TcpListener};
use tokio::stream::StreamExt; use tokio::stream::StreamExt;
use tokio::sync::RwLock;
use std::net::{ToSocketAddrs, SocketAddr}; use std::net::{ToSocketAddrs, SocketAddr};
use std::sync::Arc; use std::sync::Arc;
use coffer_common::keyring::Keyring; use coffer_common::keyring::Keyring;
use coffer_common::coffer::Coffer; use coffer_common::coffer::Coffer;
use coffer_common::certificate::{Certificate, CertificateError}; use coffer_common::certificate::CertificateError;
use crate::protocol::Protocol; use crate::protocol::Protocol;
@ -35,13 +34,19 @@ quick_error! {
pub struct Server<C> pub struct Server<C>
where C: Coffer where C: Coffer
{ {
keyring: Arc<RwLock<Keyring>>, keyring: Arc<Keyring>,
coffer: Arc<RwLock<C>> coffer: Arc<C>
} }
impl <C> Server<C> impl <C> Server <C>
where C: Coffer + Send + Sync + 'static where C: Coffer + Send + Sync + 'static
{ {
pub fn new(keyring: Keyring, coffer: C) -> Self {
Server { keyring: Arc::new(keyring),
coffer: Arc::new(coffer) }
}
pub async fn run<T>(self, addr: T) pub async fn run<T>(self, addr: T)
where T: ToSocketAddrs where T: ToSocketAddrs
{ {
@ -70,6 +75,7 @@ where C: Coffer + Send + Sync + 'static
let coffer = self.coffer.clone(); let coffer = self.coffer.clone();
let protocol = Protocol::new(tcp_stream, coffer, keyring); let protocol = Protocol::new(tcp_stream, coffer, keyring);
tokio::spawn(async move { tokio::spawn(async move {
protocol.run().await; protocol.run().await;
}); });
@ -84,43 +90,3 @@ where C: Coffer + Send + Sync + 'static
server.await server.await
} }
} }
pub struct ServerBuilder<C>
where C: Coffer
{
keyring: Option<Keyring>,
coffer: Option<C>
}
impl <'a, C> ServerBuilder<C>
where C: Coffer + Default
{
pub fn new() -> ServerBuilder<C> {
ServerBuilder {
keyring: None,
coffer: None
}
}
pub fn with_keyring(mut self, keyring: Option<Keyring>) -> ServerBuilder<C> {
self.keyring = keyring;
self
}
pub fn with_coffer(mut self, coffer: Option<C>) -> ServerBuilder<C> {
self.coffer = coffer;
self
}
pub fn build(self) -> Result<Server<C>, ServerError> {
let keyring = match self.keyring {
Some(k) => Arc::new(RwLock::new(k)),
None => {let cert = Certificate::new()?;
Arc::new(RwLock::new(Keyring::new(cert)))}
};
let coffer = Arc::new(RwLock::new(self.coffer.unwrap_or_else(|| { C::default() } )));
Ok(Server {keyring, coffer})
}
}

BIN
testcoffer/client.cert (Stored with Git LFS) Normal file

Binary file not shown.

BIN
testcoffer/client1.cert (Stored with Git LFS) Normal file

Binary file not shown.

BIN
testcoffer/client2.cert (Stored with Git LFS) Normal file

Binary file not shown.

BIN
testcoffer/coffer.enc (Stored with Git LFS) Normal file

Binary file not shown.

15
testcoffer/coffer.yaml Normal file
View file

@ -0,0 +1,15 @@
[test]
id = "F11C86D52A70977D866F813903BC73DB4CB8AC40249DF668475B1BFE48AD1E41"
key1 = "secret1"
key2 = "secret2"
[clients]
[client.1]
id = "00E4A58C6905C2932F73F4D3AB449507E0166E53ED52F769721618F8C836E736"
client1key1 = "client1secret"
client1key2 = "client1secret2"
[client.2]
id = "D4A39CD8C4695A70F758252E9D877ACE24BD3DE98BC09E90C37C06F99061CD5B"
client2key1 = "client2secret"
client2key2 = "client2secret2"

BIN
testcoffer/no_keys_client.cert (Stored with Git LFS) Normal file

Binary file not shown.

BIN
testcoffer/server.cert (Stored with Git LFS) Normal file

Binary file not shown.