First working version of encrypt/decrypt
* use protobuf for encryption-header * use fixed size file-header (42 bytes) before the encryption-header * add encryption/decryption contexts * teach MakePrivateKey() to fixup its internal public key bits
This commit is contained in:
parent
9473c10bfd
commit
21445ba1a1
12 changed files with 1893 additions and 144 deletions
5
Makefile
5
Makefile
|
@ -4,11 +4,10 @@ pwd = $(shell pwd)
|
|||
.PHONY: all test clean realclean
|
||||
|
||||
all:
|
||||
mkdir -p bin
|
||||
go build -o bin/sigtool .
|
||||
./build -s
|
||||
|
||||
test:
|
||||
go test ./sign
|
||||
|
||||
clean realclean:
|
||||
rm -f bin/sigtool
|
||||
rm -rf bin
|
||||
|
|
82
README.md
82
README.md
|
@ -4,15 +4,54 @@
|
|||
|
||||
|
||||
## What is this?
|
||||
`sigtool` is an opinionated tool to generate, sign and verify Ed25519
|
||||
signatures on files. In many ways, it is like like OpenBSD's signify_
|
||||
-- except written in Golang and definitely easier to use.
|
||||
`sigtool` is an opinionated tool to generate keys, sign, verify, encrypt &
|
||||
decrypt files using Ed25519 signature scheme. In many ways, it is like
|
||||
like OpenBSD's [signify][1] -- except written in Golang and definitely
|
||||
easier to use.
|
||||
|
||||
It can sign and verify very large files - it prehashes the files
|
||||
with SHA-512 and then signs the SHA-512 checksum.
|
||||
with SHA-512 and then signs the SHA-512 checksum. The keys and signatures
|
||||
are YAML files and so, human readable.
|
||||
|
||||
All the artifacts produced by this tool are standard YAML files -
|
||||
thus, human readable.
|
||||
It can encrypt & decrypt files by converting the Ed25519 keys to their
|
||||
corresponding Curve25519 variants. This elliptic co-ordinate transform
|
||||
follows [FiloSottile's writeup][2]. The file encryption uses
|
||||
AES-GCM-256 (AEAD); the input is broken into chunks and each chunk is
|
||||
AEAD encrypted. The default chunk size is 4MB (4 * 1048576 bytes).
|
||||
|
||||
A random 32-byte key is used to actually encrypt the file contents in
|
||||
AES-GCM mode. This file-encryption key is **wrapped** using the recipient's
|
||||
public key. Thus, a given input file (or stream) can be encrypted to be
|
||||
read by multiple recipients - each of whom is identified by their Ed25519
|
||||
public keys. The file-encryptionb-key can optionally be wrapped using the
|
||||
sender's Private Key - this authenticates the sender. If this private key is
|
||||
not provided for the encrypt operation, then `sigtool` generates ephemeral
|
||||
Curve25519 keys and wraps the file-encryption key using the ephemeral
|
||||
private key and the recipient's public key.
|
||||
|
||||
Every encrypted file starts with a header:
|
||||
|
||||
7 byte magic ("SigTool")
|
||||
1 byte version number
|
||||
4 byte header length
|
||||
32 byte SHA256 of the encryption-header
|
||||
|
||||
The encryption-header is described as a protobuf file (sign/hdr.proto):
|
||||
|
||||
```protobuf
|
||||
message header {
|
||||
uint32 chunk_size = 1;
|
||||
bytes salt = 2;
|
||||
repeated wrapped_key keys = 3;
|
||||
}
|
||||
|
||||
message wrapped_key {
|
||||
bytes pk_hash = 1; // hash of Ed25519 PK
|
||||
bytes pk = 2; // curve25519 PK
|
||||
bytes nonce = 3; // AEAD nonce
|
||||
bytes key = 4; // AEAD encrypted key
|
||||
}
|
||||
```
|
||||
|
||||
## How do I build it?
|
||||
With Go 1.5 and later:
|
||||
|
@ -21,7 +60,9 @@ With Go 1.5 and later:
|
|||
cd sigtool
|
||||
make
|
||||
|
||||
The binary will be in `./sigtool`.
|
||||
The binary will be in `./bin/$HOSTOS-$ARCH/sigtool`.
|
||||
where `$HOSTOS` is the host OS where you are building (e.g., openbsd)
|
||||
and `$ARCH` is the CPU architecture (e.g., amd64).
|
||||
|
||||
## How do I use it?
|
||||
Broadly, the tool can:
|
||||
|
@ -29,6 +70,8 @@ Broadly, the tool can:
|
|||
- generate new key pairs (public key and private key)
|
||||
- sign a file
|
||||
- verify a file against its signature
|
||||
- encrypt a file
|
||||
- decrypt a file
|
||||
|
||||
### Generate Key pair
|
||||
To start with, you generate a new key pair (a public key used for
|
||||
|
@ -73,6 +116,22 @@ e.g., to verify the signature of *archive.tar.gz* against
|
|||
|
||||
sigtool verify /tmp/testkey.pub archive.sig archive.tar.gz
|
||||
|
||||
### Encrypt a file by authenticating the sender
|
||||
If the sender wishes to prove to the recipient that they encrypted
|
||||
a file:
|
||||
|
||||
sigtool encrypt -s mykey.key theirkey.pub -o archive.tar.gz.enc archive.tar.gz
|
||||
|
||||
|
||||
This will create an encrypted file *archive.tar.gz.enc* such that the
|
||||
recipient in possession of *theikey.key* can decrypt it. Furthermore, if
|
||||
the recipient has *mykey.pub*, they can verify that the sender is indeed
|
||||
who they expect.
|
||||
|
||||
### Encrypt a file *without* authenticating the sender
|
||||
|
||||
### Decrypt a file
|
||||
|
||||
## How is the private key protected?
|
||||
The Ed25519 private key is encrypted using a key derived from the
|
||||
user supplied pass phrase. This pass phrase is used to derive an
|
||||
|
@ -111,6 +170,8 @@ A serialized Ed25519 public key looks like so:
|
|||
### Ed25519 Private Key
|
||||
And, a serialized Ed25519 private key looks like so:
|
||||
|
||||
```yaml
|
||||
|
||||
esk: t3vfqHbgUiA733KKPymFjWT8DdnBEkiMfsDHolPUdQWpvVn/F1Z4J6KYV3M5rGO9xgKxh5RAmqt+6LKgOiJAMQ==
|
||||
salt: pPHKG55UJYtJ5wU0G9hBvNQJ0DvT0a7T4Fmj4aPB84s=
|
||||
algo: scrypt-sha256
|
||||
|
@ -118,6 +179,7 @@ And, a serialized Ed25519 private key looks like so:
|
|||
Z: 131072
|
||||
r: 16
|
||||
p: 1
|
||||
```
|
||||
|
||||
The Ed25519 private key is encrypted using Scrypt password hashing
|
||||
mechanism. A user supplied passphrase to protect the private key
|
||||
|
@ -146,9 +208,12 @@ ensures that the supplied passphrase yields the same value as
|
|||
### Ed25519 Signature
|
||||
A generated signature looks like below after serialization:
|
||||
|
||||
```yaml
|
||||
|
||||
comment: inpfile=/tmp/file.txt
|
||||
pkhash: 36z9tCwTIVNwwDlExrB0SQ==
|
||||
signature: ow2oBP+buDbEvlNakOrsxgB5Yc/7PYyPVZCkfyu7oahw8BakF4Qf32uswPaKGZ8RVz4uXboYHdZtfrEjCgP/Cg==
|
||||
```
|
||||
|
||||
Here, ```pkhash`` is a SHA256 of the public key needed to verify
|
||||
this signature.
|
||||
|
@ -163,4 +228,5 @@ See the file ``LICENSE.md`` for the full terms of the license.
|
|||
## Author
|
||||
Sudhi Herle <sw@herle.net>
|
||||
|
||||
.. _signify: https://www.openbsd.org/papers/bsdcan-signify.html
|
||||
[1]: https://www.openbsd.org/papers/bsdcan-signify.html
|
||||
[2]: https://blog.filippo.io/using-ed25519-keys-for-encryption/
|
||||
|
|
373
build
Executable file
373
build
Executable file
|
@ -0,0 +1,373 @@
|
|||
#! /usr/bin/env bash
|
||||
|
||||
# Tool to build go programs in this repo
|
||||
#
|
||||
# - it tacks on a version number for use by the individual tools
|
||||
# - it supports git and mercurial version#
|
||||
#
|
||||
# NB:
|
||||
# o the attempt at decoding dirty repo state for mercurial is
|
||||
# borked. It doesn't know about untracked files
|
||||
#
|
||||
# (c) 2016 Sudhi Herle
|
||||
#
|
||||
# License: GPLv2
|
||||
#
|
||||
Progs=".:sigtool"
|
||||
|
||||
# Relative path to protobuf sources
|
||||
# e.g. src/foo/a.proto
|
||||
Protobufs="sign/hdr.proto"
|
||||
|
||||
|
||||
# -- DO NOT CHANGE ANYTHING AFTER THIS --
|
||||
|
||||
Z=`basename $0`
|
||||
PWD=`pwd`
|
||||
|
||||
Static=0
|
||||
Dryrun=0
|
||||
Prodver=0.1
|
||||
Verbose=0
|
||||
|
||||
hostos=$(go env GOHOSTOS) || exit 1
|
||||
hostcpu=$(go env GOHOSTARCH) || exit 1
|
||||
|
||||
[ -f ./version ] && Prodver=$(cat ./version)
|
||||
|
||||
die() {
|
||||
echo "$Z: $@" 1>&2
|
||||
exit 0
|
||||
}
|
||||
|
||||
warn() {
|
||||
echo "$Z: $@" 1>&2
|
||||
}
|
||||
|
||||
case $BASH_VERSION in
|
||||
4.*|5.*) ;;
|
||||
|
||||
*) die "I need bash 4.x to run!"
|
||||
;;
|
||||
esac
|
||||
|
||||
|
||||
# build a tool that runs on the host - if needed.
|
||||
hosttool() {
|
||||
local tool=$1
|
||||
local bindir=$2
|
||||
local src=$3
|
||||
|
||||
local p=$(type -P $tool)
|
||||
if [ -n "$p" ]; then
|
||||
echo $p
|
||||
return 0
|
||||
fi
|
||||
|
||||
# from here - we want this dir to find all build artifacts
|
||||
PATH=$PATH:$bindir
|
||||
export PATH
|
||||
|
||||
p=$bindir/$tool
|
||||
if [ -x $p ]; then
|
||||
echo $p
|
||||
return 0
|
||||
fi
|
||||
|
||||
# build it and stash it in the hostdir
|
||||
echo "Building tool $tool from $src .."
|
||||
$e go get -d $src || exit 1
|
||||
$e go build -o $p $src || exit 1
|
||||
echo $p
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
usage() {
|
||||
|
||||
cat <<EOF
|
||||
$0 - A Go production build tool that adds git-repository information,
|
||||
product version, build-timestamp etc. It supports cross-compilation,
|
||||
static linking and generating protobuf output.
|
||||
|
||||
If needed, it uses the gogo-slick protobuf compiler [github.com/gogo/protobuf].
|
||||
|
||||
Build output is in bin/\$OS-\$CPU for a given OS, CPU combination.
|
||||
|
||||
Usage: $0
|
||||
$0 [options] [PROGS]
|
||||
|
||||
Where OS-ARCH denotes one of the valid OS, ARCH combinations supported by 'go'.
|
||||
And, PROGS is one or more go programs.
|
||||
|
||||
With no arguments, $0 builds: $Progs (source in ./src/)
|
||||
|
||||
If ./version is present, its content are used as version number for the binary.
|
||||
|
||||
Options:
|
||||
-h, --help Show this help message and quit
|
||||
-s, --static Build a statically linked binary [False]
|
||||
-V N, --version=N Use 'N' as the product version string [$Prodver]
|
||||
-a X, --arch=X Cross compile for OS-CPU 'X' [$hostos-$hostcpu]
|
||||
-n, --dry-run Dry-run, don't actually build anything [False]
|
||||
-t, --test Run "go test" on modules named on the command line [False]
|
||||
-v, --verbose Build verbosely (adds "-v" to go tooling) [False]
|
||||
--vet Run "go vet" on modules named on the command line [False]
|
||||
-x Run in debug/trace mode [False]
|
||||
EOF
|
||||
|
||||
exit 0
|
||||
}
|
||||
|
||||
host=`uname|tr '[A-Z]' '[a-z]'`
|
||||
export GO15VENDOREXPERIMENT=1
|
||||
|
||||
declare -A oses
|
||||
declare -A cpus
|
||||
declare -A cgo
|
||||
|
||||
# Supported & Verified OS/CPU combos for this script
|
||||
oslist="linux android openbsd freebsd darwin dragonfly netbsd windows"
|
||||
needcgo="android"
|
||||
cpulist="i386 amd64 arm arm64"
|
||||
cpualias_i386="i486 i586 i686"
|
||||
cpualias_amd64="x86_64"
|
||||
cpualias_arm64="aarch64"
|
||||
|
||||
# CGO Cross-Compilers for various CPU+OS combinations of Android
|
||||
android_i386=i686-linux-android-gcc
|
||||
android_arm64=aarch64-linux-android-gcc
|
||||
android_arm=arm-linux-androideabi-gcc
|
||||
|
||||
# initialize the various hash tables
|
||||
for o in $oslist; do oses[$o]=$o; done
|
||||
for o in $needcgo; do cgo[$o]=$o; done
|
||||
for c in $cpulist; do
|
||||
cpus[$c]=$c
|
||||
a="cpualias_$c"
|
||||
a=${!a}
|
||||
for x in $a; do cpus[$x]=$c; done
|
||||
done
|
||||
|
||||
|
||||
Tool=
|
||||
doinit=0
|
||||
args=
|
||||
|
||||
#set -x
|
||||
ac_prev=
|
||||
for ac_option
|
||||
do
|
||||
shift
|
||||
|
||||
if [ -n "$ac_prev" ]; then
|
||||
eval "$ac_prev=\$ac_option"
|
||||
ac_prev=
|
||||
continue
|
||||
fi
|
||||
|
||||
case "$ac_option" in
|
||||
-*=*) ac_optarg=`echo "$ac_option" | sed 's/[-_a-zA-Z0-9]*=//'` ;;
|
||||
*) ac_optarg= ;;
|
||||
esac
|
||||
|
||||
|
||||
case "$ac_option" in
|
||||
--help|-h|--hel|--he|--h)
|
||||
usage;
|
||||
;;
|
||||
|
||||
--arch=*)
|
||||
Arch=$ac_optarg
|
||||
;;
|
||||
-a|--arch)
|
||||
ac_prev=Arch
|
||||
;;
|
||||
|
||||
--version=*)
|
||||
Prodver=$ac_optarg
|
||||
;;
|
||||
--test|-t)
|
||||
Tool=test
|
||||
;;
|
||||
|
||||
--vet)
|
||||
Tool=vet
|
||||
;;
|
||||
|
||||
-V|--version)
|
||||
ac_prev=Prodver
|
||||
;;
|
||||
-v|--verbose)
|
||||
Verbose=1
|
||||
;;
|
||||
|
||||
-s|--static)
|
||||
Static=1
|
||||
;;
|
||||
|
||||
--dry-run|-n)
|
||||
Dryrun=1
|
||||
;;
|
||||
|
||||
--debug|-x)
|
||||
set -x
|
||||
;;
|
||||
|
||||
*) # first non option terminates option processing.
|
||||
# we gather all remaining args and bundle them up.
|
||||
args="$args $ac_option"
|
||||
for xx
|
||||
do
|
||||
args="$args $xx"
|
||||
done
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
[ $Dryrun -gt 0 ] && e=echo
|
||||
|
||||
# let every error abort
|
||||
set -e
|
||||
|
||||
# This fragment can't be in a function - since it exports several vars
|
||||
if [ -n "$Arch" ]; then
|
||||
ox=${Arch%%-*}
|
||||
cx=${Arch##*-}
|
||||
[ "$ox" = "$cx" ] && cx=$hostcpu
|
||||
|
||||
os=${oses[$ox]}
|
||||
cpu=${cpus[$cx]}
|
||||
[ -z "$os" ] && die "Don't know anything about OS $ox"
|
||||
[ -z "$cpu" ] && die "Don't know anything about CPU $cx"
|
||||
|
||||
export GOOS=$os GOARCH=$cpu
|
||||
cross=$os-$cpu
|
||||
|
||||
else
|
||||
os=$hostos
|
||||
cpu=$hostcpu
|
||||
cross=$os-$cpu
|
||||
fi
|
||||
|
||||
# If we don't need CGO, then we can attempt a static link
|
||||
if [ -n "${cgo[$os]}" ]; then
|
||||
export CGO_ENABLED=1
|
||||
|
||||
# See if we have a specific cross-compiler for this CPU+OS combo
|
||||
xcc="${GOOS}_${GOARCH}"
|
||||
xcc=${!xcc}
|
||||
if [ -n "$xcc" ]; then
|
||||
p=`type -p $xcc`
|
||||
[ -n "$p" ] || die "Can't find $xcc! Do you have compilers for $GOARCH available in PATH?"
|
||||
export CC=$xcc
|
||||
else
|
||||
echo "$Z: No Cross compiler defined for $GOOS-$GOARCH. Build may fail.." 1>&2
|
||||
fi
|
||||
else
|
||||
if [ $Static -gt 0 ]; then
|
||||
export CGO_ENABLED=0
|
||||
|
||||
isuffix="--installsuffix cgo"
|
||||
ldflags="-s"
|
||||
msg="statically linked"
|
||||
fi
|
||||
fi
|
||||
|
||||
# This is where build outputs go
|
||||
Bindir=$PWD/bin/$cross
|
||||
Hostbindir=$PWD/bin/$hostos-$hostcpu
|
||||
|
||||
[ -d $Bindir ] || mkdir -p $Bindir
|
||||
[ -d $Hostbindir ] || mkdir -p $Hostbindir
|
||||
|
||||
# Get git/hg version info for the build
|
||||
if [ -d "./.hg" ]; then
|
||||
xrev=$(hg id --id) || exit 1
|
||||
brev=${xrev%+}
|
||||
if [ "$brev" != "$xrev" ]; then
|
||||
rev="hg:${brev}-dirty"
|
||||
else
|
||||
rev="hg:${brev}"
|
||||
fi
|
||||
elif [ -d "./.git" ]; then
|
||||
xrev=$(git describe --always --dirty --long --abbrev=12) || exit 1
|
||||
rev="git:$xrev"
|
||||
else
|
||||
rev="UNKNOWN-VER"
|
||||
echo "$0: Can't find version info" 1>&2
|
||||
fi
|
||||
|
||||
# Do Protobufs if needed
|
||||
if [ -n "$Protobufs" ]; then
|
||||
slick=$Hostbindir/protoc-gen-gogoslick
|
||||
slicksrc=github.com/gogo/protobuf/protoc-gen-gogoslick
|
||||
pc=$(type -p protoc)
|
||||
|
||||
[ -z "$pc" ] && die "Need 'protoc' for building .."
|
||||
|
||||
slick=$(hosttool protoc-gen-gogoslick $Hostbindir $slicksrc) || exit 1
|
||||
#if [ ! -f $slick ]; then
|
||||
# echo "Building $slick .."
|
||||
# $e go build -o $slick github.com/gogo/protobuf/protoc-gen-gogoslick || exit 1
|
||||
#i
|
||||
|
||||
PATH=$Hostbindir:$PATH
|
||||
export PATH
|
||||
|
||||
for f in $Protobufs; do
|
||||
dn=$(dirname $f)
|
||||
bn=$(basename $f .proto)
|
||||
of=$dn/${bn}.pb.go
|
||||
if [ $f -nt $of ]; then
|
||||
echo "gogoslick: $f -> $of ..."
|
||||
$e $pc --gogoslick_out=. $f || exit 1
|
||||
$e gofmt -w $of
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
repover="main.RepoVersion=$rev"
|
||||
prodver="main.ProductVersion=$Prodver"
|
||||
date="main.Buildtime=`date -u '+%Y-%m-%dT%H:%M.%SZ'`"
|
||||
ldflags="-ldflags \"-X $repover -X $prodver -X $date $ldflags\""
|
||||
vflag=""
|
||||
|
||||
[ $Verbose -gt 0 ] && vflag="-v"
|
||||
|
||||
case $Tool in
|
||||
test)
|
||||
set -- $args
|
||||
$e go test $vflag "$@"
|
||||
;;
|
||||
|
||||
vet)
|
||||
set -- $args
|
||||
$e go vet $vflag "$@"
|
||||
;;
|
||||
|
||||
*) # Default is to build programs
|
||||
set -- $args
|
||||
if [ -z "$1" ]; then
|
||||
all=$Progs
|
||||
else
|
||||
all="$@"
|
||||
fi
|
||||
|
||||
echo "Building $rev, $cross $msg .."
|
||||
|
||||
for p in $all; do
|
||||
if echo $p | grep -q ':' ; then
|
||||
out=${p##*:}
|
||||
dir=${p%%:*}
|
||||
else
|
||||
out=$p
|
||||
dir=$p
|
||||
fi
|
||||
echo " $dir: $out .. "
|
||||
$e eval go build $vflag -o $Bindir/$out $isuffix "$ldflags" ./$dir || exit 1
|
||||
done
|
||||
;;
|
||||
esac
|
||||
|
||||
# vim: ft=sh:expandtab:ts=4:sw=4:tw=84:
|
47
crypt.go
47
crypt.go
|
@ -18,8 +18,8 @@ import (
|
|||
"io"
|
||||
"os"
|
||||
|
||||
flag "github.com/opencoff/pflag"
|
||||
"github.com/opencoff/go-utils"
|
||||
flag "github.com/opencoff/pflag"
|
||||
"github.com/opencoff/sigtool/sign"
|
||||
)
|
||||
|
||||
|
@ -108,7 +108,10 @@ func encrypt(args []string) {
|
|||
outfd = outf
|
||||
}
|
||||
|
||||
var recip []*sign.PublicKey
|
||||
en, err := sign.NewEncryptor(sk)
|
||||
if err != nil {
|
||||
die("%s", err)
|
||||
}
|
||||
|
||||
for i := 0; i < len(args)-1; i++ {
|
||||
fn := args[i]
|
||||
|
@ -116,11 +119,18 @@ func encrypt(args []string) {
|
|||
if err != nil {
|
||||
die("%s", err)
|
||||
}
|
||||
recip = append(recip, pk)
|
||||
|
||||
err = en.AddRecipient(pk)
|
||||
if err != nil {
|
||||
die("%s", err)
|
||||
}
|
||||
}
|
||||
|
||||
encryptFile(sk, recip, infd, outfd)
|
||||
|
||||
err = en.Encrypt(infd, outfd)
|
||||
if err != nil {
|
||||
die("%s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// sigtool decrypt a.key [file] [-o output]
|
||||
|
@ -132,11 +142,13 @@ func decrypt(args []string) {
|
|||
|
||||
var envpw string
|
||||
var outfile string
|
||||
var pubkey string
|
||||
var pw bool
|
||||
|
||||
fs.StringVarP(&outfile, "outfile", "o", "", "Write the output to file `F`")
|
||||
fs.BoolVarP(&pw, "password", "p", false, "Ask for passphrase to decrypt the private key")
|
||||
fs.StringVarP(&envpw, "env-password", "", "", "Use passphrase from environment variable `E`")
|
||||
fs.StringVarP(&pubkey, "verify-sender", "v", "", "Verify that the sender matches public key in `F`")
|
||||
|
||||
err := fs.Parse(args)
|
||||
if err != nil {
|
||||
|
@ -153,7 +165,6 @@ func decrypt(args []string) {
|
|||
var inf *os.File
|
||||
var pws, infile string
|
||||
|
||||
|
||||
if len(envpw) > 0 {
|
||||
pws = os.Getenv(envpw)
|
||||
} else if pw {
|
||||
|
@ -169,6 +180,15 @@ func decrypt(args []string) {
|
|||
die("%s", err)
|
||||
}
|
||||
|
||||
var pk *sign.PublicKey
|
||||
|
||||
if len(pubkey) > 0 {
|
||||
pk, err = sign.ReadPublicKey(pubkey)
|
||||
if err != nil {
|
||||
die("%s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(args) > 1 {
|
||||
infile = args[1]
|
||||
if infile != "-" {
|
||||
|
@ -200,14 +220,21 @@ func decrypt(args []string) {
|
|||
outfd = outf
|
||||
}
|
||||
|
||||
decryptFile(sk, infd, outfd)
|
||||
}
|
||||
|
||||
d, err := sign.NewDecryptor(infd, pk)
|
||||
if err != nil {
|
||||
die("%s", err)
|
||||
}
|
||||
|
||||
func encryptFile(sk *sign.PrivateKey, pks []*sign.PublicKey, infd io.Reader, outfd io.Writer) {
|
||||
}
|
||||
err = d.SetPrivateKey(sk)
|
||||
if err != nil {
|
||||
die("%s", err)
|
||||
}
|
||||
|
||||
func decryptFile(sk *sign.PrivateKey, infd io.Reader, outfd io.Writer) {
|
||||
err = d.Decrypt(outfd)
|
||||
if err != nil {
|
||||
die("%s", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
1
go.mod
1
go.mod
|
@ -3,6 +3,7 @@ module github.com/opencoff/sigtool
|
|||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/gogo/protobuf v1.3.1
|
||||
github.com/opencoff/go-utils v0.3.0
|
||||
github.com/opencoff/pflag v0.3.3
|
||||
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc
|
||||
|
|
5
go.sum
5
go.sum
|
@ -1,3 +1,7 @@
|
|||
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/opencoff/go-utils v0.3.0 h1:/TQXjf50o3GSB9MItog5L8Gf4GWJ4B5+rmqjB4g2RZQ=
|
||||
github.com/opencoff/go-utils v0.3.0/go.mod h1:c+7QUAiCCHcNH6OGvsZ0fviG7cgse8Y3ucg+xy7sGXM=
|
||||
github.com/opencoff/pflag v0.3.3 h1:yohZkwYGPkB34WXvUQzU5GyLhImnjfePDARUaE8me3U=
|
||||
|
@ -11,6 +15,7 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
|
|||
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
|
592
sign/encrypt.go
592
sign/encrypt.go
|
@ -17,98 +17,427 @@ import (
|
|||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/ed25519"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"encoding/hex"
|
||||
"crypto/subtle"
|
||||
"fmt"
|
||||
"golang.org/x/crypto/curve25519"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
"io"
|
||||
"math/big"
|
||||
"strings"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
// A File-encryption-key wrapped by the Ed25519 public key of the recipient
|
||||
type WrappedKey struct {
|
||||
Key []byte // KEK - wrapped with the Curve25519 PK of recipient
|
||||
Pk []byte // Curve25519 PK used to wrap
|
||||
PkHash []byte // hash of the corresponding Ed25519 PK
|
||||
|
||||
// Encryption chunk size = 4MB
|
||||
const chunkSize int = 4 * 1048576
|
||||
|
||||
const _Magic = "SigTool"
|
||||
const _MagicLen = len(_Magic)
|
||||
const _AEADNonceLen = 32
|
||||
|
||||
|
||||
// Encryptor holds the encryption context
|
||||
type Encryptor struct {
|
||||
Header
|
||||
key [32]byte // file encryption key
|
||||
|
||||
ae cipher.AEAD
|
||||
sender *PrivateKey
|
||||
started bool
|
||||
|
||||
buf []byte
|
||||
}
|
||||
|
||||
func hx(b []byte) string {
|
||||
return hex.EncodeToString(b)
|
||||
}
|
||||
// Create a new Encryption context and use the optional private key 'sk' for
|
||||
// signing any recipient keys. If 'sk' is nil, then ephmeral Curve25519 keys
|
||||
// are generated and used with recipient's public key.
|
||||
func NewEncryptor(sk *PrivateKey) (*Encryptor, error) {
|
||||
|
||||
func unhx(s string) ([]byte, error) {
|
||||
return hex.DecodeString(s)
|
||||
}
|
||||
e := &Encryptor{
|
||||
Header: Header{
|
||||
ChunkSize: uint32(chunkSize),
|
||||
Salt: make([]byte, _AEADNonceLen),
|
||||
},
|
||||
|
||||
func (w *WrappedKey) ToString() string {
|
||||
return fmt.Sprintf("(ed25519 to=%x, pk=%x, kek=%x)",
|
||||
hx(w.PkHash), hx(w.Pk), hx(w.Key))
|
||||
}
|
||||
|
||||
func parseErr(s string, v ...interface{}) error {
|
||||
return fmt.Errorf(s, v...)
|
||||
}
|
||||
|
||||
// Given an marshalled stream of bytes, return the PubKey, encrypted key
|
||||
func ParseWrappedKey(s string) (*WrappedKey, error) {
|
||||
s = strings.TrimSpace(s)
|
||||
if s[0] != '(' {
|
||||
return nil, parseErr("missing '(' in wrapped key")
|
||||
sender: sk,
|
||||
}
|
||||
|
||||
if s[len(s)-1] != ')' {
|
||||
return nil, parseErr("missing ')' in wrapped key")
|
||||
}
|
||||
|
||||
s = s[1 : len(s)-1]
|
||||
v := strings.Fields(s)
|
||||
if len(v) != 3 {
|
||||
return nil, parseErr("Incorrect number of elements (exp 3, saw %d) in wrapped key", len(v))
|
||||
}
|
||||
|
||||
var w WrappedKey
|
||||
|
||||
for _, z := range v {
|
||||
kw := strings.Split(z, "=")
|
||||
if len(kw) != 2 {
|
||||
return nil, parseErr("malformed key=value pair (%s) in wrapped key", z)
|
||||
}
|
||||
|
||||
var err error
|
||||
switch strings.ToLower(kw[0]) {
|
||||
case "to":
|
||||
w.PkHash, err = unhx(kw[1])
|
||||
|
||||
case "pk":
|
||||
w.Pk, err = unhx(kw[1])
|
||||
|
||||
case "kek":
|
||||
w.Key, err = unhx(kw[1])
|
||||
|
||||
default:
|
||||
return nil, parseErr("unknown keyword %s in wrapped key", kw[0])
|
||||
}
|
||||
randread(e.key[:])
|
||||
randread(e.Salt)
|
||||
|
||||
aes, err := aes.NewCipher(e.key[:])
|
||||
if err != nil {
|
||||
return nil, parseErr("can't parse value for %s in wrapped key", kw[0])
|
||||
return nil, fmt.Errorf("encrypt: %s", err)
|
||||
}
|
||||
|
||||
e.ae, err = cipher.NewGCMWithNonceSize(aes, _AEADNonceLen)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("encrypt: %s", err)
|
||||
}
|
||||
|
||||
e.buf = make([]byte, chunkSize + 4 + e.ae.Overhead())
|
||||
return e, nil
|
||||
}
|
||||
|
||||
|
||||
// Add a new recipient to this encryption context.
|
||||
func (e *Encryptor) AddRecipient(pk *PublicKey) error {
|
||||
if e.started {
|
||||
return fmt.Errorf("encrypt: can't add new recipient after encryption has started")
|
||||
}
|
||||
|
||||
var w *WrappedKey
|
||||
var err error
|
||||
|
||||
if e.sender != nil {
|
||||
w, err = e.sender.WrapKey(pk, e.key[:])
|
||||
} else {
|
||||
w, err = pk.WrapKeyEphemeral(e.key[:])
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
e.Keys = append(e.Keys, w)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
// Begin the encryption process by writing the header
|
||||
func (e *Encryptor) start(wr io.Writer) error {
|
||||
msize := e.Size()
|
||||
|
||||
// marshal the header and recipients
|
||||
hdrlen := _MagicLen + 1 + 4 + sha256.Size
|
||||
|
||||
buf := make([]byte, hdrlen + msize)
|
||||
hdrbuf := buf[hdrlen:]
|
||||
|
||||
copy(buf[:], []byte(_Magic))
|
||||
|
||||
buf[_MagicLen] = 1 // file version#
|
||||
|
||||
// The fixed header is the magic _and _ the length of the variable segment.
|
||||
// So, we capture the length of the variable portion first.
|
||||
binary.BigEndian.PutUint32(buf[_MagicLen + 1:], uint32(sha256.Size + msize))
|
||||
|
||||
// Now marshal the variable portion
|
||||
_, err := e.MarshalToSizedBuffer(hdrbuf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encrypt: can't marshal header: %s", err)
|
||||
}
|
||||
|
||||
// and calculate the header checksum
|
||||
cksum := buf[_MagicLen + 1 + 4:]
|
||||
h := sha256.New()
|
||||
h.Write(hdrbuf)
|
||||
h.Sum(cksum[:0])
|
||||
|
||||
// Finally write it out
|
||||
err = fullwrite(buf, wr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encrypt: %s", err)
|
||||
}
|
||||
|
||||
e.started = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write _all_ bytes of buffer 'buf'
|
||||
func fullwrite(buf []byte, wr io.Writer) error {
|
||||
n := len(buf)
|
||||
|
||||
for n > 0 {
|
||||
m, err := wr.Write(buf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("I/O error: %s", err)
|
||||
}
|
||||
|
||||
n -= m
|
||||
buf = buf[m:]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
// Encrypt the input stream 'rd' and write encrypted stream to 'wr'
|
||||
func (e *Encryptor) Encrypt(rd io.Reader, wr io.Writer) error {
|
||||
if !e.started {
|
||||
err := e.start(wr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(w.PkHash) != 16 {
|
||||
return nil, parseErr("invalid PkHash length (exp 16, saw %d) in wrapped key", len(w.PkHash))
|
||||
buf := make([]byte, e.ChunkSize)
|
||||
i := 0
|
||||
|
||||
for {
|
||||
n, err := io.ReadAtLeast(rd, buf, int(e.ChunkSize))
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
if n > 0 {
|
||||
err = e.encrypt(buf[:n], wr, i)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
if err != nil && err != io.EOF {
|
||||
return fmt.Errorf("encrypt: I/O read error: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// encrypt exactly _one_ block of data
|
||||
// The nonce for the block is: sha256(salt || chunkLen || block#)
|
||||
// This protects the output stream from re-ordering attacks and length
|
||||
// modification attacks. The encoded length & block number is used as
|
||||
// additional data in the AEAD construction.
|
||||
func (e *Encryptor) encrypt(buf []byte, wr io.Writer, i int) error {
|
||||
var b [8]byte
|
||||
var noncebuf [32]byte
|
||||
|
||||
binary.BigEndian.PutUint32(b[:4], uint32(e.ae.Overhead() + len(buf)))
|
||||
binary.BigEndian.PutUint32(b[4:], uint32(i))
|
||||
|
||||
h := sha256.New()
|
||||
h.Write(e.Salt)
|
||||
h.Write(b[:])
|
||||
nonce := h.Sum(noncebuf[:0])
|
||||
|
||||
copy(e.buf[:4], b[:4])
|
||||
cbuf := e.buf[4:]
|
||||
c := e.ae.Seal(cbuf[:0], nonce, buf, b[:])
|
||||
|
||||
n := len(c) + 4
|
||||
|
||||
err := fullwrite(e.buf[:n], wr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encrypt: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
// Decryptor holds the decryption context
|
||||
type Decryptor struct {
|
||||
Header
|
||||
|
||||
ae cipher.AEAD
|
||||
rd io.Reader
|
||||
buf []byte
|
||||
|
||||
// Decrypted key
|
||||
key []byte
|
||||
}
|
||||
|
||||
|
||||
// Create a new decryption context and if 'pk' is given, check that it matches
|
||||
// the sender
|
||||
func NewDecryptor(rd io.Reader, pk *PublicKey) (*Decryptor, error) {
|
||||
var b [12]byte
|
||||
|
||||
_, err := io.ReadFull(rd, b[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if bytes.Compare(b[:_MagicLen], []byte(_Magic)) != 0 {
|
||||
return nil, fmt.Errorf("decrypt: Not a sigtool encrypted file?")
|
||||
}
|
||||
|
||||
if b[_MagicLen] != 1 {
|
||||
return nil, fmt.Errorf("decrypt: Unsupported version %d", b[_MagicLen])
|
||||
}
|
||||
|
||||
hdrlen := binary.BigEndian.Uint32(b[_MagicLen+1:])
|
||||
if hdrlen > 65536 {
|
||||
return nil, fmt.Errorf("decrypt: header too large (max 65536)")
|
||||
}
|
||||
if hdrlen < 32 {
|
||||
return nil, fmt.Errorf("decrypt: header too small (min 32)")
|
||||
}
|
||||
|
||||
hdr := make([]byte, hdrlen)
|
||||
|
||||
_, err = io.ReadFull(rd, hdr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
verify := hdr[:32]
|
||||
hdr = hdr[32:]
|
||||
|
||||
cksum := sha256.Sum256(hdr)
|
||||
if subtle.ConstantTimeCompare(verify, cksum[:]) == 0 {
|
||||
return nil, fmt.Errorf("decrypt: header corrupted")
|
||||
}
|
||||
|
||||
d := &Decryptor{
|
||||
rd: rd,
|
||||
}
|
||||
|
||||
err = d.Header.Unmarshal(hdr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decrypt: decode error: %s", err)
|
||||
}
|
||||
|
||||
if d.ChunkSize == 0 || d.ChunkSize > (16 * 1048576) {
|
||||
return nil, fmt.Errorf("decrypt: invalid chunkSize %d", d.ChunkSize)
|
||||
}
|
||||
|
||||
if len(d.Salt) != 32 {
|
||||
return nil, fmt.Errorf("decrypt: invalid nonce length %d", len(d.Salt))
|
||||
}
|
||||
|
||||
if len(d.Keys) == 0 {
|
||||
return nil, fmt.Errorf("decrypt: no wrapped keys")
|
||||
}
|
||||
|
||||
// sanity check on the wrapped keys
|
||||
for i, w := range d.Keys {
|
||||
if len(w.PkHash) != PKHashLength {
|
||||
return nil, fmt.Errorf("decrypt: wrapped key %d: invalid PkHash", i)
|
||||
}
|
||||
|
||||
if len(w.Pk) != 32 {
|
||||
return nil, parseErr("invalid Public Key length (exp 32, saw %d) in wrapped key", len(w.Pk))
|
||||
return nil, fmt.Errorf("decrypt: wrapped key %d: invalid Curve25519 PK", i)
|
||||
}
|
||||
|
||||
if len(w.Key) != 32 {
|
||||
return nil, parseErr("invalid Key length (exp 32, saw %d) in wrapped key", len(w.Key))
|
||||
// XXX Default AES-256-GCM Nonce size is 12
|
||||
if len(w.Nonce) != 12 {
|
||||
return nil, fmt.Errorf("decrypt: wrapped key %d: invalid Nonce", i)
|
||||
}
|
||||
|
||||
return &w, nil
|
||||
if len(w.Key) == 0 {
|
||||
return nil, fmt.Errorf("decrypt: wrapped key %d: missing encrypted key", i)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
d.buf = make([]byte, d.ChunkSize)
|
||||
if pk != nil {
|
||||
validSender := false
|
||||
pkh := pk.Hash()
|
||||
for _, w := range d.Keys {
|
||||
if subtle.ConstantTimeCompare(pkh, w.PkHash) == 1 {
|
||||
validSender = true
|
||||
}
|
||||
}
|
||||
|
||||
if !validSender {
|
||||
return nil, fmt.Errorf("decrypt: Can't find sender's public key in the header")
|
||||
}
|
||||
}
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// Use Private Key 'sk' to decrypt the encrypted keys in the header
|
||||
func (d *Decryptor) SetPrivateKey(sk *PrivateKey) error {
|
||||
var err error
|
||||
|
||||
pkh := sk.PublicKey().Hash()
|
||||
for i, w := range d.Keys {
|
||||
if subtle.ConstantTimeCompare(pkh, w.PkHash) == 1 {
|
||||
d.key, err = w.UnwrapKey(sk)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decrypt: can't unwrap key %d: %s", i, err)
|
||||
}
|
||||
goto havekey
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("decrypt: Can't find any public key to match the given private key")
|
||||
|
||||
|
||||
havekey:
|
||||
aes, err := aes.NewCipher(d.key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decrypt: %s", err)
|
||||
}
|
||||
|
||||
d.ae, err = cipher.NewGCMWithNonceSize(aes, _AEADNonceLen)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decrypt: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return a list of Wrapped keys in the encrypted file header
|
||||
func (d *Decryptor) WrappedKeys() []*WrappedKey {
|
||||
return d.Keys
|
||||
}
|
||||
|
||||
|
||||
// Decrypt the file and write to 'wr'
|
||||
func (d *Decryptor) Decrypt(wr io.Writer) error {
|
||||
if d.key == nil {
|
||||
return fmt.Errorf("decrypt: wrapped-key not decrypted (missing SetPrivateKey()?")
|
||||
}
|
||||
|
||||
for i := 0; ; i++ {
|
||||
c, err := d.decrypt(i)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(c) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(c) > 0 {
|
||||
err = fullwrite(c, wr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decrypt: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decrypt exactly one chunk of data
|
||||
func (d *Decryptor) decrypt(i int) ([]byte, error) {
|
||||
var b [8]byte
|
||||
var nonceb [32]byte
|
||||
|
||||
n, err := io.ReadFull(d.rd, b[:4])
|
||||
if n == 0 || err == io.EOF {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decrypt: can't read chunk %d length: %s", i, err)
|
||||
}
|
||||
|
||||
|
||||
chunklen := int(binary.BigEndian.Uint32(b[:4]))
|
||||
binary.BigEndian.PutUint32(b[4:], uint32(i))
|
||||
h := sha256.New()
|
||||
h.Write(d.Salt)
|
||||
h.Write(b[:])
|
||||
nonce := h.Sum(nonceb[:0])
|
||||
|
||||
n, err = io.ReadFull(d.rd, d.buf[:chunklen])
|
||||
if n == 0 || err == io.EOF {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decrypt: can't read chunk %d: %s", i, err)
|
||||
}
|
||||
|
||||
p, err := d.ae.Open(d.buf[:0], nonce, d.buf[:chunklen], b[:])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decrypt: can't decrypt chunk %d: %s", i, err)
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// given a file-encryption-key, wrap it in the identity of the recipient 'pk' using our
|
||||
|
@ -125,6 +454,23 @@ func (sk *PrivateKey) WrapKey(pk *PublicKey, key []byte) (*WrappedKey, error) {
|
|||
|
||||
}
|
||||
|
||||
// Unwrap a wrapped key using the private key 'sk'
|
||||
func (w *WrappedKey) UnwrapKey(sk *PrivateKey) ([]byte, error) {
|
||||
var shared, theirPK, ourSK [32]byte
|
||||
|
||||
pk := sk.PublicKey()
|
||||
copy(ourSK[:], sk.toCurve25519SK())
|
||||
copy(theirPK[:], w.Pk)
|
||||
curve25519.ScalarMult(&shared, &ourSK, &theirPK)
|
||||
|
||||
key, err := aeadOpen(w.Key, w.Nonce, shared[:], pk.Pk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
|
||||
// Wrap a shared key with the recipient's public key 'pk' by generating an ephemeral
|
||||
// Curve25519 keypair. This function does not identify the sender (non-repudiation).
|
||||
func (pk *PublicKey) WrapKeyEphemeral(key []byte) (*WrappedKey, error) {
|
||||
|
@ -144,22 +490,16 @@ func (pk *PublicKey) WrapKeyEphemeral(key []byte) (*WrappedKey, error) {
|
|||
}
|
||||
|
||||
func wrapKey(pk *PublicKey, k, theirPK, shared []byte) (*WrappedKey, error) {
|
||||
|
||||
// hkdf or HMAC-sha-256
|
||||
kek, err := expand(shared[:], pk.Pk)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("wrap: %s", err)
|
||||
}
|
||||
|
||||
ek, err := aeadSeal(k, kek)
|
||||
ek, nonce, err := aeadSeal(k, shared[:], pk.Pk)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("wrap: %s", err)
|
||||
}
|
||||
|
||||
return &WrappedKey{
|
||||
Key: ek,
|
||||
Pk: theirPK,
|
||||
PkHash: pk.hash,
|
||||
Pk: theirPK,
|
||||
Nonce: nonce,
|
||||
Key: ek,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -226,67 +566,65 @@ func expand(shared, pk []byte) ([]byte, error) {
|
|||
return kek, err
|
||||
}
|
||||
|
||||
func aeadSeal(data, key []byte) ([]byte, error) {
|
||||
var salt [32]byte
|
||||
var nonceb [64]byte
|
||||
|
||||
randread(salt[:])
|
||||
|
||||
h := sha512.New()
|
||||
h.Write(salt[:])
|
||||
h.Write(key)
|
||||
nonce := h.Sum(nonceb[:0])[:32]
|
||||
|
||||
aes, err := aes.NewCipher(key)
|
||||
// seal the data via AEAD after suitably expanding 'shared'
|
||||
func aeadSeal(data, shared, pk []byte) ([]byte, []byte, error) {
|
||||
kek, err := expand(shared[:], pk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, fmt.Errorf("wrap: %s", err)
|
||||
}
|
||||
|
||||
ae, err := cipher.NewGCMWithNonceSize(aes, len(nonce))
|
||||
aes, err := aes.NewCipher(kek)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, fmt.Errorf("wrap: %s", err)
|
||||
}
|
||||
|
||||
ae, err := cipher.NewGCM(aes)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("wrap: %s", err)
|
||||
}
|
||||
|
||||
noncesize := ae.NonceSize()
|
||||
tagsize := ae.Overhead()
|
||||
|
||||
buf := make([]byte, tagsize + len(kek))
|
||||
nonce := make([]byte, noncesize)
|
||||
|
||||
randread(nonce)
|
||||
|
||||
out := ae.Seal(buf[:0], nonce, data, nil)
|
||||
return out, nonce, nil
|
||||
}
|
||||
|
||||
func aeadOpen(data, nonce, shared, pk []byte) ([]byte, error) {
|
||||
// hkdf or HMAC-sha-256
|
||||
kek, err := expand(shared, pk)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unwrap: %s", err)
|
||||
}
|
||||
aes, err := aes.NewCipher(kek)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unwrap: %s", err)
|
||||
}
|
||||
|
||||
ae, err := cipher.NewGCM(aes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unwrap: %s", err)
|
||||
}
|
||||
|
||||
want := 32 + ae.Overhead()
|
||||
if len(data) != want {
|
||||
return nil, fmt.Errorf("unwrap: incorrect decrypt bytes (need %d, saw %d)", want, len(data))
|
||||
}
|
||||
|
||||
c, err := ae.Open(data[:0], nonce, data, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unwrap: %s", err)
|
||||
}
|
||||
|
||||
c := ae.Seal(nil, nonce, data, nil)
|
||||
c = append(c, salt[:]...)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func aeadOpen(data, key []byte) ([]byte, error) {
|
||||
var nonceb [64]byte
|
||||
// GCM tag: 16 bytes
|
||||
// salt: 32 bytes
|
||||
// last 32 bytes: salt
|
||||
|
||||
n := len(data)
|
||||
if n < (32 + 16) {
|
||||
return nil, fmt.Errorf("aead: too few decrypt bytes (min 48, saw %d)", n)
|
||||
}
|
||||
|
||||
salt := data[n-32:]
|
||||
data = data[:n-32]
|
||||
|
||||
h := sha512.New()
|
||||
h.Write(salt)
|
||||
h.Write(key)
|
||||
nonce := h.Sum(nonceb[:0])[:32]
|
||||
|
||||
aes, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ae, err := cipher.NewGCMWithNonceSize(aes, len(nonce))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c, err := ae.Open(nil, nonce, data, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func clamp(k []byte) []byte {
|
||||
k[0] &= 248
|
||||
|
|
899
sign/hdr.pb.go
Normal file
899
sign/hdr.pb.go
Normal file
|
@ -0,0 +1,899 @@
|
|||
// Code generated by protoc-gen-gogo. DO NOT EDIT.
|
||||
// source: sign/hdr.proto
|
||||
|
||||
package sign
|
||||
|
||||
import (
|
||||
bytes "bytes"
|
||||
fmt "fmt"
|
||||
proto "github.com/gogo/protobuf/proto"
|
||||
io "io"
|
||||
math "math"
|
||||
math_bits "math/bits"
|
||||
reflect "reflect"
|
||||
strings "strings"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
type Header struct {
|
||||
ChunkSize uint32 `protobuf:"varint,1,opt,name=chunk_size,json=chunkSize,proto3" json:"chunk_size,omitempty"`
|
||||
Salt []byte `protobuf:"bytes,2,opt,name=salt,proto3" json:"salt,omitempty"`
|
||||
Keys []*WrappedKey `protobuf:"bytes,3,rep,name=keys,proto3" json:"keys,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Header) Reset() { *m = Header{} }
|
||||
func (*Header) ProtoMessage() {}
|
||||
func (*Header) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_85aff542c746609f, []int{0}
|
||||
}
|
||||
func (m *Header) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
}
|
||||
func (m *Header) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
if deterministic {
|
||||
return xxx_messageInfo_Header.Marshal(b, m, deterministic)
|
||||
} else {
|
||||
b = b[:cap(b)]
|
||||
n, err := m.MarshalToSizedBuffer(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b[:n], nil
|
||||
}
|
||||
}
|
||||
func (m *Header) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Header.Merge(m, src)
|
||||
}
|
||||
func (m *Header) XXX_Size() int {
|
||||
return m.Size()
|
||||
}
|
||||
func (m *Header) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Header.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_Header proto.InternalMessageInfo
|
||||
|
||||
func (m *Header) GetChunkSize() uint32 {
|
||||
if m != nil {
|
||||
return m.ChunkSize
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *Header) GetSalt() []byte {
|
||||
if m != nil {
|
||||
return m.Salt
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Header) GetKeys() []*WrappedKey {
|
||||
if m != nil {
|
||||
return m.Keys
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type WrappedKey struct {
|
||||
PkHash []byte `protobuf:"bytes,1,opt,name=pk_hash,json=pkHash,proto3" json:"pk_hash,omitempty"`
|
||||
Pk []byte `protobuf:"bytes,2,opt,name=pk,proto3" json:"pk,omitempty"`
|
||||
Nonce []byte `protobuf:"bytes,3,opt,name=nonce,proto3" json:"nonce,omitempty"`
|
||||
Key []byte `protobuf:"bytes,4,opt,name=key,proto3" json:"key,omitempty"`
|
||||
}
|
||||
|
||||
func (m *WrappedKey) Reset() { *m = WrappedKey{} }
|
||||
func (*WrappedKey) ProtoMessage() {}
|
||||
func (*WrappedKey) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_85aff542c746609f, []int{1}
|
||||
}
|
||||
func (m *WrappedKey) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
}
|
||||
func (m *WrappedKey) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
if deterministic {
|
||||
return xxx_messageInfo_WrappedKey.Marshal(b, m, deterministic)
|
||||
} else {
|
||||
b = b[:cap(b)]
|
||||
n, err := m.MarshalToSizedBuffer(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b[:n], nil
|
||||
}
|
||||
}
|
||||
func (m *WrappedKey) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_WrappedKey.Merge(m, src)
|
||||
}
|
||||
func (m *WrappedKey) XXX_Size() int {
|
||||
return m.Size()
|
||||
}
|
||||
func (m *WrappedKey) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_WrappedKey.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_WrappedKey proto.InternalMessageInfo
|
||||
|
||||
func (m *WrappedKey) GetPkHash() []byte {
|
||||
if m != nil {
|
||||
return m.PkHash
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *WrappedKey) GetPk() []byte {
|
||||
if m != nil {
|
||||
return m.Pk
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *WrappedKey) GetNonce() []byte {
|
||||
if m != nil {
|
||||
return m.Nonce
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *WrappedKey) GetKey() []byte {
|
||||
if m != nil {
|
||||
return m.Key
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Header)(nil), "sign.header")
|
||||
proto.RegisterType((*WrappedKey)(nil), "sign.wrapped_key")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("sign/hdr.proto", fileDescriptor_85aff542c746609f) }
|
||||
|
||||
var fileDescriptor_85aff542c746609f = []byte{
|
||||
// 257 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0x90, 0x31, 0x4a, 0xc4, 0x40,
|
||||
0x14, 0x86, 0xe7, 0x25, 0x31, 0xe2, 0xdb, 0x75, 0xd1, 0x41, 0x30, 0x8d, 0x8f, 0xb0, 0x20, 0xa4,
|
||||
0x8a, 0xa0, 0x9e, 0xc0, 0xca, 0x3a, 0xf6, 0x86, 0xec, 0x66, 0x70, 0xc2, 0x48, 0x32, 0x64, 0x56,
|
||||
0x24, 0x5b, 0x79, 0x04, 0x8f, 0xe1, 0x51, 0x2c, 0x53, 0x6e, 0x69, 0x26, 0x8d, 0xe5, 0x1e, 0x41,
|
||||
0x32, 0x5a, 0xd8, 0xfd, 0xff, 0xf7, 0xe0, 0x7d, 0xf0, 0xe3, 0xc2, 0x54, 0x4f, 0xf5, 0x95, 0x2c,
|
||||
0xdb, 0x54, 0xb7, 0xcd, 0xa6, 0xe1, 0xc1, 0xd4, 0x97, 0x2b, 0x0c, 0xa5, 0x28, 0x4a, 0xd1, 0xf2,
|
||||
0x0b, 0xc4, 0xb5, 0x7c, 0xa9, 0x55, 0x6e, 0xaa, 0xad, 0x88, 0x20, 0x86, 0xe4, 0x38, 0x3b, 0x72,
|
||||
0xe4, 0xa1, 0xda, 0x0a, 0xce, 0x31, 0x30, 0xc5, 0xf3, 0x26, 0xf2, 0x62, 0x48, 0xe6, 0x99, 0xcb,
|
||||
0xfc, 0x12, 0x03, 0x25, 0x3a, 0x13, 0xf9, 0xb1, 0x9f, 0xcc, 0xae, 0x4f, 0xd3, 0xe9, 0x63, 0xfa,
|
||||
0xda, 0x16, 0x5a, 0x8b, 0x32, 0x57, 0xa2, 0xcb, 0xdc, 0x79, 0xf9, 0x88, 0xb3, 0x7f, 0x90, 0x9f,
|
||||
0xe3, 0xa1, 0x56, 0xb9, 0x2c, 0x8c, 0x74, 0x96, 0x79, 0x16, 0x6a, 0x75, 0x5f, 0x18, 0xc9, 0x17,
|
||||
0xe8, 0x69, 0xf5, 0x27, 0xf0, 0xb4, 0xe2, 0x67, 0x78, 0x50, 0x37, 0xf5, 0x5a, 0x44, 0xbe, 0x43,
|
||||
0xbf, 0x85, 0x9f, 0xa0, 0xaf, 0x44, 0x17, 0x05, 0x8e, 0x4d, 0xf1, 0xee, 0xb6, 0x1f, 0x88, 0xed,
|
||||
0x06, 0x62, 0xfb, 0x81, 0xe0, 0xcd, 0x12, 0x7c, 0x58, 0x82, 0x4f, 0x4b, 0xd0, 0x5b, 0x82, 0x2f,
|
||||
0x4b, 0xf0, 0x6d, 0x89, 0xed, 0x2d, 0xc1, 0xfb, 0x48, 0xac, 0x1f, 0x89, 0xed, 0x46, 0x62, 0xab,
|
||||
0xd0, 0xcd, 0x70, 0xf3, 0x13, 0x00, 0x00, 0xff, 0xff, 0xde, 0xf2, 0x28, 0xc0, 0x18, 0x01, 0x00,
|
||||
0x00,
|
||||
}
|
||||
|
||||
func (this *Header) Equal(that interface{}) bool {
|
||||
if that == nil {
|
||||
return this == nil
|
||||
}
|
||||
|
||||
that1, ok := that.(*Header)
|
||||
if !ok {
|
||||
that2, ok := that.(Header)
|
||||
if ok {
|
||||
that1 = &that2
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if that1 == nil {
|
||||
return this == nil
|
||||
} else if this == nil {
|
||||
return false
|
||||
}
|
||||
if this.ChunkSize != that1.ChunkSize {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(this.Salt, that1.Salt) {
|
||||
return false
|
||||
}
|
||||
if len(this.Keys) != len(that1.Keys) {
|
||||
return false
|
||||
}
|
||||
for i := range this.Keys {
|
||||
if !this.Keys[i].Equal(that1.Keys[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
func (this *WrappedKey) Equal(that interface{}) bool {
|
||||
if that == nil {
|
||||
return this == nil
|
||||
}
|
||||
|
||||
that1, ok := that.(*WrappedKey)
|
||||
if !ok {
|
||||
that2, ok := that.(WrappedKey)
|
||||
if ok {
|
||||
that1 = &that2
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if that1 == nil {
|
||||
return this == nil
|
||||
} else if this == nil {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(this.PkHash, that1.PkHash) {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(this.Pk, that1.Pk) {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(this.Nonce, that1.Nonce) {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(this.Key, that1.Key) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
func (this *Header) GoString() string {
|
||||
if this == nil {
|
||||
return "nil"
|
||||
}
|
||||
s := make([]string, 0, 7)
|
||||
s = append(s, "&sign.Header{")
|
||||
s = append(s, "ChunkSize: "+fmt.Sprintf("%#v", this.ChunkSize)+",\n")
|
||||
s = append(s, "Salt: "+fmt.Sprintf("%#v", this.Salt)+",\n")
|
||||
if this.Keys != nil {
|
||||
s = append(s, "Keys: "+fmt.Sprintf("%#v", this.Keys)+",\n")
|
||||
}
|
||||
s = append(s, "}")
|
||||
return strings.Join(s, "")
|
||||
}
|
||||
func (this *WrappedKey) GoString() string {
|
||||
if this == nil {
|
||||
return "nil"
|
||||
}
|
||||
s := make([]string, 0, 8)
|
||||
s = append(s, "&sign.WrappedKey{")
|
||||
s = append(s, "PkHash: "+fmt.Sprintf("%#v", this.PkHash)+",\n")
|
||||
s = append(s, "Pk: "+fmt.Sprintf("%#v", this.Pk)+",\n")
|
||||
s = append(s, "Nonce: "+fmt.Sprintf("%#v", this.Nonce)+",\n")
|
||||
s = append(s, "Key: "+fmt.Sprintf("%#v", this.Key)+",\n")
|
||||
s = append(s, "}")
|
||||
return strings.Join(s, "")
|
||||
}
|
||||
func valueToGoStringHdr(v interface{}, typ string) string {
|
||||
rv := reflect.ValueOf(v)
|
||||
if rv.IsNil() {
|
||||
return "nil"
|
||||
}
|
||||
pv := reflect.Indirect(rv).Interface()
|
||||
return fmt.Sprintf("func(v %v) *%v { return &v } ( %#v )", typ, typ, pv)
|
||||
}
|
||||
func (m *Header) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalToSizedBuffer(dAtA[:size])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dAtA[:n], nil
|
||||
}
|
||||
|
||||
func (m *Header) MarshalTo(dAtA []byte) (int, error) {
|
||||
size := m.Size()
|
||||
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||
}
|
||||
|
||||
func (m *Header) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i := len(dAtA)
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if len(m.Keys) > 0 {
|
||||
for iNdEx := len(m.Keys) - 1; iNdEx >= 0; iNdEx-- {
|
||||
{
|
||||
size, err := m.Keys[iNdEx].MarshalToSizedBuffer(dAtA[:i])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i -= size
|
||||
i = encodeVarintHdr(dAtA, i, uint64(size))
|
||||
}
|
||||
i--
|
||||
dAtA[i] = 0x1a
|
||||
}
|
||||
}
|
||||
if len(m.Salt) > 0 {
|
||||
i -= len(m.Salt)
|
||||
copy(dAtA[i:], m.Salt)
|
||||
i = encodeVarintHdr(dAtA, i, uint64(len(m.Salt)))
|
||||
i--
|
||||
dAtA[i] = 0x12
|
||||
}
|
||||
if m.ChunkSize != 0 {
|
||||
i = encodeVarintHdr(dAtA, i, uint64(m.ChunkSize))
|
||||
i--
|
||||
dAtA[i] = 0x8
|
||||
}
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func (m *WrappedKey) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalToSizedBuffer(dAtA[:size])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dAtA[:n], nil
|
||||
}
|
||||
|
||||
func (m *WrappedKey) MarshalTo(dAtA []byte) (int, error) {
|
||||
size := m.Size()
|
||||
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||
}
|
||||
|
||||
func (m *WrappedKey) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i := len(dAtA)
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if len(m.Key) > 0 {
|
||||
i -= len(m.Key)
|
||||
copy(dAtA[i:], m.Key)
|
||||
i = encodeVarintHdr(dAtA, i, uint64(len(m.Key)))
|
||||
i--
|
||||
dAtA[i] = 0x22
|
||||
}
|
||||
if len(m.Nonce) > 0 {
|
||||
i -= len(m.Nonce)
|
||||
copy(dAtA[i:], m.Nonce)
|
||||
i = encodeVarintHdr(dAtA, i, uint64(len(m.Nonce)))
|
||||
i--
|
||||
dAtA[i] = 0x1a
|
||||
}
|
||||
if len(m.Pk) > 0 {
|
||||
i -= len(m.Pk)
|
||||
copy(dAtA[i:], m.Pk)
|
||||
i = encodeVarintHdr(dAtA, i, uint64(len(m.Pk)))
|
||||
i--
|
||||
dAtA[i] = 0x12
|
||||
}
|
||||
if len(m.PkHash) > 0 {
|
||||
i -= len(m.PkHash)
|
||||
copy(dAtA[i:], m.PkHash)
|
||||
i = encodeVarintHdr(dAtA, i, uint64(len(m.PkHash)))
|
||||
i--
|
||||
dAtA[i] = 0xa
|
||||
}
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func encodeVarintHdr(dAtA []byte, offset int, v uint64) int {
|
||||
offset -= sovHdr(v)
|
||||
base := offset
|
||||
for v >= 1<<7 {
|
||||
dAtA[offset] = uint8(v&0x7f | 0x80)
|
||||
v >>= 7
|
||||
offset++
|
||||
}
|
||||
dAtA[offset] = uint8(v)
|
||||
return base
|
||||
}
|
||||
func (m *Header) Size() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
var l int
|
||||
_ = l
|
||||
if m.ChunkSize != 0 {
|
||||
n += 1 + sovHdr(uint64(m.ChunkSize))
|
||||
}
|
||||
l = len(m.Salt)
|
||||
if l > 0 {
|
||||
n += 1 + l + sovHdr(uint64(l))
|
||||
}
|
||||
if len(m.Keys) > 0 {
|
||||
for _, e := range m.Keys {
|
||||
l = e.Size()
|
||||
n += 1 + l + sovHdr(uint64(l))
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (m *WrappedKey) Size() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
var l int
|
||||
_ = l
|
||||
l = len(m.PkHash)
|
||||
if l > 0 {
|
||||
n += 1 + l + sovHdr(uint64(l))
|
||||
}
|
||||
l = len(m.Pk)
|
||||
if l > 0 {
|
||||
n += 1 + l + sovHdr(uint64(l))
|
||||
}
|
||||
l = len(m.Nonce)
|
||||
if l > 0 {
|
||||
n += 1 + l + sovHdr(uint64(l))
|
||||
}
|
||||
l = len(m.Key)
|
||||
if l > 0 {
|
||||
n += 1 + l + sovHdr(uint64(l))
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func sovHdr(x uint64) (n int) {
|
||||
return (math_bits.Len64(x|1) + 6) / 7
|
||||
}
|
||||
func sozHdr(x uint64) (n int) {
|
||||
return sovHdr(uint64((x << 1) ^ uint64((int64(x) >> 63))))
|
||||
}
|
||||
func (this *Header) String() string {
|
||||
if this == nil {
|
||||
return "nil"
|
||||
}
|
||||
repeatedStringForKeys := "[]*WrappedKey{"
|
||||
for _, f := range this.Keys {
|
||||
repeatedStringForKeys += strings.Replace(fmt.Sprintf("%v", f), "WrappedKey", "WrappedKey", 1) + ","
|
||||
}
|
||||
repeatedStringForKeys += "}"
|
||||
s := strings.Join([]string{`&Header{`,
|
||||
`ChunkSize:` + fmt.Sprintf("%v", this.ChunkSize) + `,`,
|
||||
`Salt:` + fmt.Sprintf("%v", this.Salt) + `,`,
|
||||
`Keys:` + repeatedStringForKeys + `,`,
|
||||
`}`,
|
||||
}, "")
|
||||
return s
|
||||
}
|
||||
func (this *WrappedKey) String() string {
|
||||
if this == nil {
|
||||
return "nil"
|
||||
}
|
||||
s := strings.Join([]string{`&WrappedKey{`,
|
||||
`PkHash:` + fmt.Sprintf("%v", this.PkHash) + `,`,
|
||||
`Pk:` + fmt.Sprintf("%v", this.Pk) + `,`,
|
||||
`Nonce:` + fmt.Sprintf("%v", this.Nonce) + `,`,
|
||||
`Key:` + fmt.Sprintf("%v", this.Key) + `,`,
|
||||
`}`,
|
||||
}, "")
|
||||
return s
|
||||
}
|
||||
func valueToStringHdr(v interface{}) string {
|
||||
rv := reflect.ValueOf(v)
|
||||
if rv.IsNil() {
|
||||
return "nil"
|
||||
}
|
||||
pv := reflect.Indirect(rv).Interface()
|
||||
return fmt.Sprintf("*%v", pv)
|
||||
}
|
||||
func (m *Header) Unmarshal(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
preIndex := iNdEx
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowHdr
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
fieldNum := int32(wire >> 3)
|
||||
wireType := int(wire & 0x7)
|
||||
if wireType == 4 {
|
||||
return fmt.Errorf("proto: header: wiretype end group for non-group")
|
||||
}
|
||||
if fieldNum <= 0 {
|
||||
return fmt.Errorf("proto: header: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||
}
|
||||
switch fieldNum {
|
||||
case 1:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field ChunkSize", wireType)
|
||||
}
|
||||
m.ChunkSize = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowHdr
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.ChunkSize |= uint32(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
case 2:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Salt", wireType)
|
||||
}
|
||||
var byteLen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowHdr
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
byteLen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if byteLen < 0 {
|
||||
return ErrInvalidLengthHdr
|
||||
}
|
||||
postIndex := iNdEx + byteLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthHdr
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Salt = append(m.Salt[:0], dAtA[iNdEx:postIndex]...)
|
||||
if m.Salt == nil {
|
||||
m.Salt = []byte{}
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 3:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Keys", wireType)
|
||||
}
|
||||
var msglen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowHdr
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
msglen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if msglen < 0 {
|
||||
return ErrInvalidLengthHdr
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthHdr
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Keys = append(m.Keys, &WrappedKey{})
|
||||
if err := m.Keys[len(m.Keys)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipHdr(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthHdr
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthHdr
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx += skippy
|
||||
}
|
||||
}
|
||||
|
||||
if iNdEx > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (m *WrappedKey) Unmarshal(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
preIndex := iNdEx
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowHdr
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
fieldNum := int32(wire >> 3)
|
||||
wireType := int(wire & 0x7)
|
||||
if wireType == 4 {
|
||||
return fmt.Errorf("proto: wrapped_key: wiretype end group for non-group")
|
||||
}
|
||||
if fieldNum <= 0 {
|
||||
return fmt.Errorf("proto: wrapped_key: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||
}
|
||||
switch fieldNum {
|
||||
case 1:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field PkHash", wireType)
|
||||
}
|
||||
var byteLen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowHdr
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
byteLen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if byteLen < 0 {
|
||||
return ErrInvalidLengthHdr
|
||||
}
|
||||
postIndex := iNdEx + byteLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthHdr
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.PkHash = append(m.PkHash[:0], dAtA[iNdEx:postIndex]...)
|
||||
if m.PkHash == nil {
|
||||
m.PkHash = []byte{}
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 2:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Pk", wireType)
|
||||
}
|
||||
var byteLen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowHdr
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
byteLen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if byteLen < 0 {
|
||||
return ErrInvalidLengthHdr
|
||||
}
|
||||
postIndex := iNdEx + byteLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthHdr
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Pk = append(m.Pk[:0], dAtA[iNdEx:postIndex]...)
|
||||
if m.Pk == nil {
|
||||
m.Pk = []byte{}
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 3:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Nonce", wireType)
|
||||
}
|
||||
var byteLen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowHdr
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
byteLen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if byteLen < 0 {
|
||||
return ErrInvalidLengthHdr
|
||||
}
|
||||
postIndex := iNdEx + byteLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthHdr
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Nonce = append(m.Nonce[:0], dAtA[iNdEx:postIndex]...)
|
||||
if m.Nonce == nil {
|
||||
m.Nonce = []byte{}
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 4:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType)
|
||||
}
|
||||
var byteLen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowHdr
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
byteLen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if byteLen < 0 {
|
||||
return ErrInvalidLengthHdr
|
||||
}
|
||||
postIndex := iNdEx + byteLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthHdr
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Key = append(m.Key[:0], dAtA[iNdEx:postIndex]...)
|
||||
if m.Key == nil {
|
||||
m.Key = []byte{}
|
||||
}
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipHdr(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthHdr
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthHdr
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx += skippy
|
||||
}
|
||||
}
|
||||
|
||||
if iNdEx > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func skipHdr(dAtA []byte) (n int, err error) {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
depth := 0
|
||||
for iNdEx < l {
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowHdr
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
wireType := int(wire & 0x7)
|
||||
switch wireType {
|
||||
case 0:
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowHdr
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx++
|
||||
if dAtA[iNdEx-1] < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
case 1:
|
||||
iNdEx += 8
|
||||
case 2:
|
||||
var length int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowHdr
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
length |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if length < 0 {
|
||||
return 0, ErrInvalidLengthHdr
|
||||
}
|
||||
iNdEx += length
|
||||
case 3:
|
||||
depth++
|
||||
case 4:
|
||||
if depth == 0 {
|
||||
return 0, ErrUnexpectedEndOfGroupHdr
|
||||
}
|
||||
depth--
|
||||
case 5:
|
||||
iNdEx += 4
|
||||
default:
|
||||
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
|
||||
}
|
||||
if iNdEx < 0 {
|
||||
return 0, ErrInvalidLengthHdr
|
||||
}
|
||||
if depth == 0 {
|
||||
return iNdEx, nil
|
||||
}
|
||||
}
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidLengthHdr = fmt.Errorf("proto: negative length found during unmarshaling")
|
||||
ErrIntOverflowHdr = fmt.Errorf("proto: integer overflow")
|
||||
ErrUnexpectedEndOfGroupHdr = fmt.Errorf("proto: unexpected end of group")
|
||||
)
|
23
sign/hdr.proto
Normal file
23
sign/hdr.proto
Normal file
|
@ -0,0 +1,23 @@
|
|||
syntax="proto3";
|
||||
|
||||
//import "gogoproto/gogo.proto"
|
||||
|
||||
package sign;
|
||||
|
||||
//option (gogoproto.marshaler_all) = true;
|
||||
//option (gogoproto.sizer_all) = true;
|
||||
//option (gogoproto.unmarshaler_all) = true;
|
||||
//option (gogoproto.goproto_getters_all) = false;
|
||||
|
||||
message header {
|
||||
uint32 chunk_size = 1;
|
||||
bytes salt = 2;
|
||||
repeated wrapped_key keys = 3;
|
||||
}
|
||||
|
||||
message wrapped_key {
|
||||
bytes pk_hash = 1; // hash of Ed25519 PK
|
||||
bytes pk = 2; // curve25519 PK
|
||||
bytes nonce = 3; // AEAD nonce
|
||||
bytes key = 4; // AEAD encrypted key
|
||||
}
|
23
sign/sign.go
23
sign/sign.go
|
@ -77,6 +77,9 @@ type Signature struct {
|
|||
const sk_algo = "scrypt-sha256"
|
||||
const sig_algo = "sha512-ed25519"
|
||||
|
||||
// Length of Ed25519 Public Key Hash
|
||||
const PKHashLength = 16
|
||||
|
||||
// Scrypt parameters
|
||||
const _N = 1 << 17
|
||||
const _r = 16
|
||||
|
@ -132,7 +135,7 @@ type signature struct {
|
|||
|
||||
func pkhash(pk []byte) []byte {
|
||||
z := sha256.Sum256(pk)
|
||||
return z[:16]
|
||||
return z[:PKHashLength]
|
||||
}
|
||||
|
||||
// Generate a new Ed25519 keypair
|
||||
|
@ -220,7 +223,6 @@ func MakePrivateKey(yml []byte, pw string) (*PrivateKey, error) {
|
|||
return nil, fmt.Errorf("can't decode YAML:Verify: %s", err)
|
||||
}
|
||||
|
||||
sk := &PrivateKey{}
|
||||
|
||||
// We take short passwords and extend them
|
||||
pwb := sha512.Sum512([]byte(pw))
|
||||
|
@ -240,11 +242,24 @@ func MakePrivateKey(yml []byte, pw string) (*PrivateKey, error) {
|
|||
}
|
||||
|
||||
// Everything works. Now, decode the key
|
||||
sk.Sk = make([]byte, len(esk.Esk))
|
||||
skb := make([]byte, len(esk.Esk))
|
||||
for i := 0; i < len(esk.Esk); i++ {
|
||||
sk.Sk[i] = esk.Esk[i] ^ xork[i]
|
||||
skb[i] = esk.Esk[i] ^ xork[i]
|
||||
}
|
||||
|
||||
edsk := Ed.PrivateKey(skb)
|
||||
edpk := edsk.Public().(Ed.PublicKey)
|
||||
|
||||
pk := &PublicKey{
|
||||
Pk: []byte(edpk),
|
||||
hash: pkhash([]byte(edpk)),
|
||||
}
|
||||
sk := &PrivateKey{
|
||||
Sk: skb,
|
||||
pk: pk,
|
||||
}
|
||||
|
||||
|
||||
return sk, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -306,6 +306,8 @@ Commands:
|
|||
generate, g Generate a new Ed25519 keypair
|
||||
sign, s Sign a file with a private key
|
||||
verify, v Verify a signature against a file and a public key
|
||||
encrypt, e Encrypt an input file to one or more recipients
|
||||
decrypt, d Decrypt a file with a private key
|
||||
`, Z, Z)
|
||||
|
||||
os.Stdout.Write([]byte(x))
|
||||
|
|
1
version
Normal file
1
version
Normal file
|
@ -0,0 +1 @@
|
|||
0.2.0
|
Loading…
Add table
Reference in a new issue