Compare commits

..

No commits in common. "master" and "v2.1.1" have entirely different histories.

28 changed files with 1704 additions and 2056 deletions

2
.gitignore vendored
View file

@ -31,4 +31,4 @@ sigtool
*.pub
*.key
*.sig
releases/*

View file

@ -1,2 +0,0 @@
[tools]
golang = "1.24"

View file

@ -40,7 +40,7 @@ You need two things:
Next, build sigtool:
git clone https://git.rgst.io/homelab/sigtool/v3
git clone https://github.com/opencoff/sigtool
cd sigtool
make
@ -101,10 +101,6 @@ e.g., to verify the signature of *archive.tar.gz* against
sigtool verify /tmp/testkey.pub archive.sig archive.tar.gz
You can also pass a public key as a string (instead of a file):
sigtool verify iF84Dymq/bAEnUMK6DRIHWAQDRD8FwDDDfsgFfzdjWM= archive.sig archive.tar.gz
Note that signing and verifying can also work with OpenSSH ed25519
keys.
@ -156,32 +152,19 @@ recipient can decrypt using their private key.
### How is the file encryption done?
The file encryption uses AES-GCM-256 in AEAD mode. The encryption uses
a random 32-byte AES-256 key. This root key is expanded via
HKDF-SHA256 into:
- AES-GCM-256 key (32 bytes)
- AES Nonce (12 bytes)
- HMAC-SHA-256 key (32 bytes)
The input to the HKDF is the root-key, header-checksum ("salt") and
a context string.
a random 32-byte AES-256 key. This key is mixed in with the header checksum
as a safeguard to protect the header against accidental or malicious corruption.
The input is broken into chunks and each chunk is individually AEAD encrypted.
The default chunk size is 4MB (4 * 1048576 bytes). Each chunk generates
its own nonce: the top-4 bytes of the nonce is the chunk-number. The
actual chunk-length and EOF marker is used as additional data (the
"AD" of "AEAD").
its own nonce from a global salt. The nonce is calculated as follows:
- v1: SHA256 of the salt, the chunk length and the block number.
- v2: Last 8 bytes of a 32-byte salt is the big-endian encoding of
the chunk-length and block number
The last block has its most-signficant-bit set to 1 to denote EOF. Thus, the
maximum chunk size is set to 1GB.
We calculate a running hmac of the plaintext blocks; when sender
identity is present, the final HMAC is signed via the sender's
Ed25519 key. This signature is appended as the "trailer" (last 64
bytes of the encrypted file are the Ed25519 signature).
When sender identity is not present, the last bytes are random
bytes.
### What is the public-key cryptography in sigtool?
`sigtool` uses ephemeral Curve25519 keys to generate shared secrets
between pairs of sender & one or more recipients. This pairwise shared
@ -190,7 +173,7 @@ data-encryption key in AEAD mode. Thus, each recipient has their own
individual encrypted key blob - that **only** they can decrypt.
If the sender authenticates the encryption by providing their secret
key, the encryption key material is signed via Ed25519 and the signature
key, the data-encryption key is signed via Ed25519 and the signature
is encrypted (using the data-encryption key) and stored in the
header. If the sender opts to not authenticate, a "signature" of all
zeroes is encrypted instead.
@ -220,7 +203,7 @@ described as a protobuf file (sign/hdr.proto):
uint32 chunk_size = 1;
bytes salt = 2;
bytes pk = 3; // sender's ephemeral curve PK
bytes sender = 4; // ed25519 signature of key material
bytes sender_sig = 4; // ed25519 signature of the key
repeated wrapped_key keys = 5;
}
@ -230,7 +213,6 @@ described as a protobuf file (sign/hdr.proto):
*/
message wrapped_key {
bytes d_key = 1;
bytes nonce = 2;
}
```

256
build
View file

@ -4,7 +4,7 @@
#
# - 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
@ -13,13 +13,12 @@
#
# License: GPLv2
#
Progs="src:sigtool"
Progs=".:sigtool"
# Relative path to protobuf sources
# e.g. src/foo/a.proto
Protobufs="internal/pb/hdr.proto"
#set -x
# -- DO NOT CHANGE ANYTHING AFTER THIS --
@ -29,10 +28,10 @@ PWD=`pwd`
Static=0
Dryrun=0
Prodver=""
Repover=""
Verbose=0
Go=`which go`
Bindir=$PWD/bin
hostos=$(go env GOHOSTOS) || exit 1
hostcpu=$(go env GOHOSTARCH) || exit 1
die() {
echo "$Z: $@" 1>&2
@ -50,56 +49,47 @@ case $BASH_VERSION in
;;
esac
getvcs_version() {
local rev=
local prodv=
local git=`which git`
local hg=`which hg`
if [ -n "$git" ]; then
local xrev=$(git describe --always --dirty --long --abbrev=12) || exit 1
rev="git:$xrev"
prodv=$(git tag --list | sort -V | tail -1)
elif [ -n "$hg" ]; then
local xrev=$(hg id --id) || exit 1
local brev=${xrev%+}
if [ "$brev" != "$xrev" ]; then
rev="hg:${brev}-dirty"
else
rev="hg:${brev}"
fi
prodv=$(hg log -r "branch(stable) and tag()" -T "{tags}\n" | sort -V | tail -1)
else
warn "no git or hg found; can't get VCS info"
rev="UNKNOWN-VER"
# 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
[ -n "$Prodver" ] && prodv=$Prodver
# from here - we want this dir to find all build artifacts
PATH=$PATH:$bindir
export PATH
echo "$rev $prodv"
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
}
read -r Repover Prodver <<< $(getvcs_version)
usage() {
declare -a progv=($Progs)
declare n=${#progv[@]}
declare pstr=
for ((i=0; i < n; i++)); do
local ent=${progv[$i]}
local dir=${ent%%:*}
local tool=${ent##*:}
pstr=$(printf "$pstr\n\t%s $Prodver $Repover (from ./%s)" $tool $dir)
done
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
@ -108,14 +98,13 @@ Usage: $0
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: $pstr
With no arguments, $0 builds: $Progs (source in ./src/)
The repository's latest tag is used as the default version of the software being
built. The current repository version is $Repover.
built.
Options:
-h, --help Show this help message and quit
-b D, --bindir=D Put the binaries in the directory 'D' [$Bindir]
-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]
@ -123,8 +112,6 @@ Options:
-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]
--mod Run "go mod ..." [False]
--go=G Use Go in 'G' [$Go]
-x Run in debug/trace mode [False]
--print-arch Print the target architecture and exit
EOF
@ -133,6 +120,7 @@ EOF
}
host=`uname|tr '[A-Z]' '[a-z]'`
export GO15VENDOREXPERIMENT=1
declare -A oses
declare -A cpus
@ -193,23 +181,13 @@ do
--arch=*)
Arch=$ac_optarg
;;
-a|--arch)
ac_prev=Arch
;;
-b|--bindir)
ac_prev=Bindir
;;
--bindir=*)
Bindir=$ac_optarg
;;
--version=*)
Prodver=$ac_optarg
;;
--test|-t)
Tool=test
;;
@ -218,14 +196,9 @@ do
Tool=vet
;;
--mod)
Tool=mod
;;
-V|--version)
ac_prev=Prodver
;;
-v|--verbose)
Verbose=1
;;
@ -242,10 +215,6 @@ do
set -x
;;
--go-root=*)
GoRoot=$ac_optarg
;;
--print-arch)
Printarch=1
;;
@ -261,81 +230,11 @@ do
;;
esac
done
[ $Dryrun -gt 0 ] && e=echo
# let every error abort
set -e
# build a tool that runs on the host - if needed.
hosttool() {
local tool=$1
local bindir=$2
local src=$3
p=$bindir/$tool
if [ -x $p ]; then
return 0
fi
local tmpdir=/tmp/$tool.$$
mkdir $tmpdir || die "can't make $tmpdir"
# since go1.20 - install uses env vars to decide where to put
# build artifacts. Why are all the google tooling so bloody dev
# hostile! WTF is wrong with command line args?!
export GOBIN=$bindir
# build it and stash it in the hostdir
echo "Building tool $tool from $src .."
(
cd $tmpdir
$e $Go install $src@latest || die "can't install $tool"
)
$e rm -rf $tmpdir
return 0
}
# protobuf gen
buildproto() {
local pbgo=protoc-gen-go
local vtgo=protoc-gen-go-vtproto
local vtgo_src=github.com/planetscale/vtprotobuf/cmd/protoc-gen-go-vtproto
local pc
local args="$*"
local pgen=$(type -p protoc)
local gogen=$(type -p $pbgo)
local vt=$Hostbindir/$vtgo
[ -z $pgen ] && die "install protoc tools"
[ -z $gogen ] && die "install protoc-gen-go"
# now install the vtproto generator
hosttool $vtgo $Hostbindir $vtgo_src
for f in $args; do
local dn=$(dirname $f)
local bn=$(basename $f .proto)
$e $pgen \
--go_out=. --plugin protoc-gen-go=$gogen \
--go-vtproto_out=. --plugin protoc-gen-go-vtproto="$vt" \
--go-vtproto_opt=features=marshal+unmarshal+size \
$f || die "can't generate protobuf output for $f .."
done
return 0
}
# the rest has to execute in the context of main shell (not funcs)
hostos=$($Go env GOHOSTOS) || exit 1
hostcpu=$($Go env GOHOSTARCH) || exit 1
# This fragment can't be in a function - since it exports several vars
if [ -n "$Arch" ]; then
ox=${Arch%%-*}
@ -387,25 +286,72 @@ fi
# This is where build outputs go
Outdir=$Bindir/$cross
Hostbindir=$Bindir/$hostos-$hostcpu
export PATH=$Hostbindir:$PATH
Bindir=$PWD/bin/$cross
Hostbindir=$PWD/bin/$hostos-$hostcpu
[ -d $Outdir ] || mkdir -p $Outdir
[ -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
if [ -z "$Prodver" ]; then
Prodver=$(hg log -r "branch(stable) and tag()" -T "{tags}\n" | tail -1)
fi
elif [ -d "./.git" ]; then
xrev=$(git describe --always --dirty --long --abbrev=12) || exit 1
rev="git:$xrev"
if [ -z "$Prodver" ]; then
Prodver=$(git tag --list | tail -1)
fi
else
rev="UNKNOWN-VER"
echo "$0: Can't find version info" 1>&2
fi
# Do Protobufs if needed
if [ -n "$Protobufs" ]; then
set +e
buildproto $Protobufs
slick=$Hostbindir/protoc-gen-gogoslick
slicksrc=github.com/gogo/protobuf/protoc-gen-gogoslick
for pc in protoc protoc-c; do
pc=`which $pc`
[ -n "$pc" ] && break
done
[ -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
export PATH=$PATH:$Hostbindir
for f in $Protobufs; do
dn=$(dirname $f)
bn=$(basename $f .proto)
of=$dn/${bn}.pb.go
if [ $f -nt $of ]; then
echo "Running $pc .."
$e $pc --gogoslick_out=. $f || exit 1
fi
done
set -e
fi
# Get git/hg version info for the build
repover="main.RepoVersion=$Repover"
repover="main.RepoVersion=$rev"
prodver="main.ProductVersion=$Prodver"
ldflags="-ldflags \"-X $repover -X $prodver $ldflags -buildid=\""
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"
@ -413,17 +359,12 @@ vflag=""
case $Tool in
test)
set -- $args
$e $Go test $vflag "$@"
$e go test $vflag "$@"
;;
vet)
set -- $args
$e $Go vet $vflag "$@"
;;
mod)
set -- $args
$e $Go mod $vflag "$@"
$e go vet $vflag "$@"
;;
*) # Default is to build programs
@ -434,11 +375,9 @@ case $Tool in
all="$@"
fi
[ -z "$all" ] && die "No programs specified. Try '$Z --help'"
echo "Building $Prodver ($rev), $cross $msg .."
echo "Building $Prodver ($Repover), $cross $msg .."
for p in $all; do
for p in $all; do
if echo $p | grep -q ':' ; then
out=${p##*:}
dir=${p%%:*}
@ -446,15 +385,8 @@ case $Tool in
out=$p
dir=$p
fi
# Add .exe suffix to out if needed
if [ "$GOOS" = "windows" ]; then
base=${out%%.exe}
out="${base}.exe"
fi
echo " $dir: $out .. "
$e eval $Go build $vflag -trimpath -o $Outdir/$out $isuffix "$ldflags" ./$dir || exit 1
$e eval go build $vflag -o $Bindir/$out $isuffix "$ldflags" ./$dir || exit 1
done
;;
esac

View file

@ -20,10 +20,9 @@ import (
"os"
"strings"
"git.rgst.io/homelab/sigtool/v3/sign"
"github.com/opencoff/go-fio"
"github.com/opencoff/go-utils"
flag "github.com/opencoff/pflag"
"github.com/opencoff/sigtool/sign"
)
// sigtool encrypt [-i|--identity my.key] to.pub [to.pub] [ssh.pub] inputfile|- [-o output]
@ -36,7 +35,6 @@ func encrypt(args []string) {
var outfile string
var keyfile string
var szstr string = "128k"
var envpw string
var nopw, force bool
var blksize uint64
@ -45,7 +43,7 @@ func encrypt(args []string) {
fs.StringVarP(&keyfile, "sign", "s", "", "Sign using private key `S`")
fs.BoolVarP(&nopw, "no-password", "", false, "Don't ask for passphrase to decrypt the private key")
fs.StringVarP(&envpw, "env-password", "E", "", "Use passphrase from environment variable `E`")
fs.StringVarP(&szstr, "block-size", "B", szstr, "Use `S` as the encryption block size")
fs.SizeVarP(&blksize, "block-size", "B", 128*1024, "Use `S` as the encryption block size")
fs.BoolVarP(&force, "overwrite", "", false, "Overwrite the output file if it exists")
err := fs.Parse(args)
@ -53,10 +51,6 @@ func encrypt(args []string) {
Die("%s", err)
}
if blksize, err = utils.ParseSize(szstr); err != nil {
Die("%s", err)
}
var pws, infile string
var sk *sign.PrivateKey
@ -141,11 +135,7 @@ func encrypt(args []string) {
mode = ist.Mode()
}
var opts uint32
if force {
opts |= fio.OPT_OVERWRITE
}
sf, err := fio.NewSafeFile(outfile, opts, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode)
sf, err := sign.NewSafeFile(outfile, force, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode)
if err != nil {
Die("%s", err)
}
@ -307,11 +297,7 @@ func decrypt(args []string) {
mode = ist.Mode()
}
var opts uint32
if force {
opts |= fio.OPT_OVERWRITE
}
sf, err := fio.NewSafeFile(outfile, opts, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode)
sf, err := sign.NewSafeFile(outfile, force, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode)
if err != nil {
Die("%s", err)
}

View file

26
go.mod
View file

@ -1,25 +1,17 @@
module git.rgst.io/homelab/sigtool/v3
module github.com/opencoff/sigtool
go 1.24.0
go 1.18
require (
github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a
github.com/opencoff/go-fio v0.5.14
github.com/opencoff/go-mmap v0.1.5
github.com/opencoff/go-utils v1.0.2
github.com/opencoff/pflag v1.0.7
github.com/planetscale/vtprotobuf v0.6.0
golang.org/x/crypto v0.36.0
google.golang.org/protobuf v1.36.5
gopkg.in/yaml.v3 v3.0.1
github.com/gogo/protobuf v1.3.2
github.com/opencoff/go-utils v0.4.1
github.com/opencoff/pflag v0.5.0
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd
gopkg.in/yaml.v2 v2.4.0
)
require (
github.com/pkg/xattr v0.4.10 // indirect
github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/term v0.30.0 // indirect
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect
)
//replace github.com/opencoff/go-mmap => ../go-mmap
//replace github.com/opencoff/go-utils => ../go-utils

78
go.sum
View file

@ -1,39 +1,49 @@
github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a h1:saTgr5tMLFnmy/yg3qDTft4rE5DY2uJ/cCxCe3q0XTU=
github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a/go.mod h1:Bw9BbhOJVNR+t0jCqx2GC6zv0TGBsShs56Y3gfSCvl0=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/opencoff/go-fio v0.5.14 h1:PGi4XLLO4RSuc3m5exY0G2vweov6w3UThhScehBfM8c=
github.com/opencoff/go-fio v0.5.14/go.mod h1:hoSySYpavRnfQUsxzUgadk31kYiNQhMDvA2MObsXKf8=
github.com/opencoff/go-mmap v0.1.5 h1:RKPtevC4mOW5bi9skBPPo4nFTIH4lVWAL20Tff+FjLg=
github.com/opencoff/go-mmap v0.1.5/go.mod h1:y/6Jk/tDUc00k3oSQpiJX++20Nw7xFSlc5kLkhGnRXw=
github.com/opencoff/go-utils v1.0.2 h1:BANRL8ZxgHpuo8gQBAzT3M9Im3aNFhaWW28jhc86LNs=
github.com/opencoff/go-utils v1.0.2/go.mod h1:eZkEVQVzNfuE8uGepyhscMsqcXq7liGbBHYYwgYaoy8=
github.com/opencoff/pflag v1.0.7 h1:o5cQIuX75bDcdJ6AXl68gzpA72a3CJ2MPStaMnEuwi4=
github.com/opencoff/pflag v1.0.7/go.mod h1:2bXtpAD/5h/2LarkbsRwiUxqnvB1nZBzn9Xjad1P41A=
github.com/pkg/xattr v0.4.10 h1:Qe0mtiNFHQZ296vRgUjRCoPHPqH7VdTOrZx3g0T+pGA=
github.com/pkg/xattr v0.4.10/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU=
github.com/planetscale/vtprotobuf v0.6.0 h1:nBeETjudeJ5ZgBHUz1fVHvbqUKnYOXNhsIEabROxmNA=
github.com/planetscale/vtprotobuf v0.6.0/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=
github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/opencoff/go-utils v0.4.1 h1:Ke4Q1Tl2GKMI+dwleuPNHH713ngRiNMOFIkymncHqXg=
github.com/opencoff/go-utils v0.4.1/go.mod h1:c+7QUAiCCHcNH6OGvsZ0fviG7cgse8Y3ucg+xy7sGXM=
github.com/opencoff/pflag v0.5.0 h1:kK3cSTlGj0fHby/PoFzHkf+Jx3PdiACJwzYDWEWlEKQ=
github.com/opencoff/pflag v0.5.0/go.mod h1:mTLzGGUGda1Av3d34iAJlh0JIlRxmFZtmc6qoWPspK0=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd h1:XcWmESyNjXJMLahc3mqVQJcgSTDxFxhETVlfk9uGc38=
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,13 @@
syntax="proto3";
//import "gogoproto/gogo.proto"
option go_package = "internal/pb";
package pb;
//option (gogoproto.marshaler_all) = true;
//option (gogoproto.sizer_all) = true;
//option (gogoproto.unmarshaler_all) = true;
//option (gogoproto.goproto_getters_all) = false;
/*
* Every encrypted file starts with a header describing the
@ -11,11 +16,11 @@ option go_package = "internal/pb";
* protobuf format before writing to disk.
*/
message header {
uint32 chunk_size = 1; // encryption block size
bytes salt = 2; // master salt (nonces are derived from this)
bytes pk = 3; // ephemeral curve PK
bytes sender = 4; // sender signed artifacts
repeated wrapped_key keys = 5; // list of wrapped receiver blocks
uint32 chunk_size = 1; // encryption block size
bytes salt = 2; // master salt (nonces are derived from this)
bytes pk = 3; // ephemeral curve PK
bytes sender_sign = 4; // signature block of sender
repeated wrapped_key keys = 5; // list of wrapped receiver blocks
}
/*
@ -23,7 +28,5 @@ message header {
* key. WrappedKey describes such a wrapped key.
*/
message wrapped_key {
bytes d_key = 1; // encrypted data key
bytes nonce = 2; // nonce used for encryption
bytes d_key = 1; // encrypted data key
}

View file

@ -1,512 +0,0 @@
// Code generated by protoc-gen-go-vtproto. DO NOT EDIT.
// protoc-gen-go-vtproto version: v0.6.0
// source: internal/pb/hdr.proto
package pb
import (
fmt "fmt"
protohelpers "github.com/planetscale/vtprotobuf/protohelpers"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
io "io"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
func (m *Header) MarshalVT() (dAtA []byte, err error) {
if m == nil {
return nil, nil
}
size := m.SizeVT()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBufferVT(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *Header) MarshalToVT(dAtA []byte) (int, error) {
size := m.SizeVT()
return m.MarshalToSizedBufferVT(dAtA[:size])
}
func (m *Header) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
if m == nil {
return 0, nil
}
i := len(dAtA)
_ = i
var l int
_ = l
if m.unknownFields != nil {
i -= len(m.unknownFields)
copy(dAtA[i:], m.unknownFields)
}
if len(m.Keys) > 0 {
for iNdEx := len(m.Keys) - 1; iNdEx >= 0; iNdEx-- {
size, err := m.Keys[iNdEx].MarshalToSizedBufferVT(dAtA[:i])
if err != nil {
return 0, err
}
i -= size
i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
i--
dAtA[i] = 0x2a
}
}
if len(m.Sender) > 0 {
i -= len(m.Sender)
copy(dAtA[i:], m.Sender)
i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Sender)))
i--
dAtA[i] = 0x22
}
if len(m.Pk) > 0 {
i -= len(m.Pk)
copy(dAtA[i:], m.Pk)
i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Pk)))
i--
dAtA[i] = 0x1a
}
if len(m.Salt) > 0 {
i -= len(m.Salt)
copy(dAtA[i:], m.Salt)
i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Salt)))
i--
dAtA[i] = 0x12
}
if m.ChunkSize != 0 {
i = protohelpers.EncodeVarint(dAtA, i, uint64(m.ChunkSize))
i--
dAtA[i] = 0x8
}
return len(dAtA) - i, nil
}
func (m *WrappedKey) MarshalVT() (dAtA []byte, err error) {
if m == nil {
return nil, nil
}
size := m.SizeVT()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBufferVT(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *WrappedKey) MarshalToVT(dAtA []byte) (int, error) {
size := m.SizeVT()
return m.MarshalToSizedBufferVT(dAtA[:size])
}
func (m *WrappedKey) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
if m == nil {
return 0, nil
}
i := len(dAtA)
_ = i
var l int
_ = l
if m.unknownFields != nil {
i -= len(m.unknownFields)
copy(dAtA[i:], m.unknownFields)
}
if len(m.Nonce) > 0 {
i -= len(m.Nonce)
copy(dAtA[i:], m.Nonce)
i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Nonce)))
i--
dAtA[i] = 0x12
}
if len(m.DKey) > 0 {
i -= len(m.DKey)
copy(dAtA[i:], m.DKey)
i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.DKey)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func (m *Header) SizeVT() (n int) {
if m == nil {
return 0
}
var l int
_ = l
if m.ChunkSize != 0 {
n += 1 + protohelpers.SizeOfVarint(uint64(m.ChunkSize))
}
l = len(m.Salt)
if l > 0 {
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
}
l = len(m.Pk)
if l > 0 {
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
}
l = len(m.Sender)
if l > 0 {
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
}
if len(m.Keys) > 0 {
for _, e := range m.Keys {
l = e.SizeVT()
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
}
}
n += len(m.unknownFields)
return n
}
func (m *WrappedKey) SizeVT() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = len(m.DKey)
if l > 0 {
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
}
l = len(m.Nonce)
if l > 0 {
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
}
n += len(m.unknownFields)
return n
}
func (m *Header) UnmarshalVT(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 protohelpers.ErrIntOverflow
}
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 protohelpers.ErrIntOverflow
}
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 protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
byteLen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if byteLen < 0 {
return protohelpers.ErrInvalidLength
}
postIndex := iNdEx + byteLen
if postIndex < 0 {
return protohelpers.ErrInvalidLength
}
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 Pk", wireType)
}
var byteLen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
byteLen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if byteLen < 0 {
return protohelpers.ErrInvalidLength
}
postIndex := iNdEx + byteLen
if postIndex < 0 {
return protohelpers.ErrInvalidLength
}
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 4:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Sender", wireType)
}
var byteLen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
byteLen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if byteLen < 0 {
return protohelpers.ErrInvalidLength
}
postIndex := iNdEx + byteLen
if postIndex < 0 {
return protohelpers.ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Sender = append(m.Sender[:0], dAtA[iNdEx:postIndex]...)
if m.Sender == nil {
m.Sender = []byte{}
}
iNdEx = postIndex
case 5:
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 protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return protohelpers.ErrInvalidLength
}
postIndex := iNdEx + msglen
if postIndex < 0 {
return protohelpers.ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Keys = append(m.Keys, &WrappedKey{})
if err := m.Keys[len(m.Keys)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := protohelpers.Skip(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return protohelpers.ErrInvalidLength
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *WrappedKey) UnmarshalVT(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 protohelpers.ErrIntOverflow
}
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: WrappedKey: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: WrappedKey: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field DKey", wireType)
}
var byteLen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
byteLen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if byteLen < 0 {
return protohelpers.ErrInvalidLength
}
postIndex := iNdEx + byteLen
if postIndex < 0 {
return protohelpers.ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.DKey = append(m.DKey[:0], dAtA[iNdEx:postIndex]...)
if m.DKey == nil {
m.DKey = []byte{}
}
iNdEx = postIndex
case 2:
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 protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
byteLen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if byteLen < 0 {
return protohelpers.ErrInvalidLength
}
postIndex := iNdEx + byteLen
if postIndex < 0 {
return protohelpers.ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Nonce = append(m.Nonce[:0], dAtA[iNdEx:postIndex]...)
if m.Nonce == nil {
m.Nonce = []byte{}
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := protohelpers.Skip(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return protohelpers.ErrInvalidLength
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}

View file

@ -1,16 +0,0 @@
// helper routines to maintain backwards compat with gogo
package pb
func (m *Header) Size() int {
return m.SizeVT()
}
func (m *Header) MarshalTo(buf []byte) (int, error) {
return m.MarshalToVT(buf)
}
func (m *Header) Unmarshal(buf []byte) error {
return m.UnmarshalVT(buf)
}

View file

@ -1,54 +0,0 @@
#! /usr/bin/env bash
Z=`basename $0`
die() {
echo "$Z: $@" 1>&2
exit 1
}
warn() {
echo "$Z: $@" 1>&2
}
case $BASH_VERSION in
4.*|5.*) ;;
*) die "I need bash 4.x to run!"
;;
esac
Rel=$PWD/releases
Bindir=$Rel/bin
mkdir -p $Bindir || die "can't make $Bindir"
pkgit() {
local os=$1
local cpu=$2
local rev=$3
local arch="$os-$cpu"
local tgz="$Rel/sigtool-${rev}_${arch}.tar.gz"
local bindir=$Bindir/$arch
local bin=sigtool
if [ "$os" = "windows" ]; then
bin=${bin}.exe
fi
./build -V $rev -b $Bindir -s -a $arch || die "can't build $arch"
(cd $bindir && tar cf - $bin) | gzip -9 > $tgz || die "can't tar $tgz"
}
xrev=$(git describe --always --dirty --abbrev=12) || exit 1
if echo $xrev | grep -q dirty; then
die "won't build releases; repo dirty!"
true
fi
os="linux windows openbsd darwin"
arch="amd64 arm64"
for xx in $os; do
for yy in $arch; do
pkgit $xx $yy $xrev
done
done

View file

@ -1,3 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
}

View file

@ -1,10 +1,10 @@
[![GoDoc](https://godoc.org/git.rgst.io/homelab/sigtool/v3/sign?status.svg)](https://godoc.org/git.rgst.io/homelab/sigtool/v3/sign)
[![GoDoc](https://godoc.org/github.com/opencoff/sigtool/sign?status.svg)](https://godoc.org/github.com/opencoff/sigtool/sign)
# sigtool/sign - Ed25519 signature calculation and verification
This is a small library that makes it easier to create and serialize Ed25519 keys, and sign,
verify files using those keys. The library uses mmap(2) to read and process very large files.
The companion program [sigtool](https://git.rgst.io/homelab/sigtool/v3) uses this library.
The companion program [sigtool](https://github.com/opencoff/sigtool) uses this library.
## License
GPL v2.0

View file

@ -26,36 +26,29 @@
//
// Variable Length Segment:
// - Protobuf encoded, per-recipient wrapped keys
// - Shasum: 32 bytes (SHA256 of full header)
//
// The variable length segment consists of one or more
// recipients, each with their individually wrapped keys.
// recipients, each with their wrapped keys. This is encoded as
// a protobuf message. This protobuf encoded message immediately
// follows the fixed length header.
//
// The input data is encrypted with an expanded random 32-byte key:
// - hkdf-sha512 of random key, salt, context
// - the hkdf process yields a data-encryption key, nonce and hmac key.
// - we use the header checksum as the 'salt' for HKDF; this ensures that
// any modification of the header yields different keys
//
// We also calculate the cumulative hmac-sha256 of the plaintext blocks.
// - When sender identity is present, we sign the final hmac and append
// the signature as the "trailer".
// - When sender identity is NOT present, we put random bytes as the
// "signature". ie in either case, there is a trailer.
//
// Note: If the trailer is missing from a sigtool encrypted file - the
// recipient has no guarantees of content immutability (ie tampering
// from one of the _other_ recipients).
// - Prefix_string = "Encrypt Nonce"
// - datakey = SHA256(Prefix_string || header_checksum || random_key)
// - The header checksum is mixed in the above process to ensure we
// catch any malicious modification of the header.
//
// The input data is broken up into "chunks"; each no larger than
// maxChunkSize. The default block size is "chunkSize". Each block
// is AEAD encrypted:
// AEAD nonce = header.nonce || block#
// AD of AEAD = chunk length+eof marker
// AEAD nonce = header.salt || block# || block-size
//
// The encrypted block (includes the AEAD tag) length is written
// as a big-endian 4-byte prefix. The high-order bit of this length
// field is set for the last-block (denoting EOF).
//
// The encrypted blocks use an opinionated nonce length of 32 (_AEADNonceLen).
package sign
@ -64,65 +57,50 @@ import (
"crypto/aes"
"crypto/cipher"
"crypto/ed25519"
"crypto/hmac"
"crypto/sha256"
"crypto/sha512"
"crypto/subtle"
"encoding/binary"
"fmt"
"hash"
"io"
"os"
"golang.org/x/crypto/curve25519"
"golang.org/x/crypto/hkdf"
"io"
"git.rgst.io/homelab/sigtool/v3/internal/pb"
"github.com/opencoff/sigtool/internal/pb"
)
// Encryption chunk size = 4MB
const (
// The latest version of the tool's output file format
_SigtoolVersion = 3
chunkSize uint32 = 4 * 1048576 // 4 MB
chunkSize uint32 = 4 * 1048576
maxChunkSize uint32 = 1 << 30
_EOF uint32 = 1 << 31
_Magic = "SigTool"
_MagicLen = len(_Magic)
_FixedHdrLen = _MagicLen + 1 + 4 // 1: version, 4: len of variable segment
_Magic = "SigTool"
_MagicLen = len(_Magic)
_SigtoolVersion = 2
_AEADNonceLen = 32
_FixedHdrLen = _MagicLen + 1 + 4
_AesKeySize = 32
_AEADNonceSize = 12
_SaltSize = 32
_RxNonceSize = 12 // nonce size of per-recipient encrypted blocks
_WrapReceiver = "Receiver Key"
_WrapSender = "Sender Sig"
_DataKeyExpansion = "Data Key Expansion"
_WrapReceiverNonce = "Receiver Key Nonce"
_WrapSenderNonce = "Sender Sig Nonce"
_EncryptNonce = "Encrypt Nonce"
)
// Encryptor holds the encryption context
type Encryptor struct {
pb.Header
key []byte // root key
key []byte // file encryption key
nonce []byte // nonce for the data encrypting cipher
buf []byte // I/O buf (chunk-sized)
ae cipher.AEAD
hmac hash.Hash
ae cipher.AEAD
// ephemeral key
encSK []byte
// sender identity
sender *PrivateKey
auth bool // set if the sender idetity is sent
started bool
stream bool
hdrsum []byte
buf []byte
stream bool
}
// Create a new Encryption context for encrypting blocks of size 'blksize'.
@ -145,23 +123,27 @@ func NewEncryptor(sk *PrivateKey, blksize uint64) (*Encryptor, error) {
return nil, fmt.Errorf("encrypt: %w", err)
}
key := randBuf(_AesKeySize)
salt := randBuf(_SaltSize)
key := make([]byte, 32)
salt := make([]byte, _AEADNonceLen)
randRead(key)
randRead(salt)
wSig, err := wrapSenderSig(sk, key, salt)
if err != nil {
return nil, fmt.Errorf("encrypt: %w", err)
}
e := &Encryptor{
Header: pb.Header{
ChunkSize: blksz,
Salt: salt,
Pk: epk,
ChunkSize: blksz,
Salt: salt,
Pk: epk,
SenderSign: wSig,
},
key: key,
encSK: esk,
sender: sk,
}
if err = e.addSenderSig(sk); err != nil {
return nil, fmt.Errorf("encrypt: %w", err)
key: key,
encSK: esk,
}
return e, nil
@ -228,8 +210,8 @@ func (e *Encryptor) start(wr io.Writer) error {
buffer := make([]byte, _FixedHdrLen+varSize+sha256.Size)
fixHdr := buffer[:_FixedHdrLen]
varHdr := buffer[_FixedHdrLen : _FixedHdrLen+varSize]
sumHdr := buffer[_FixedHdrLen+varSize:]
varHdr := buffer[_FixedHdrLen:]
sumHdr := varHdr[varSize:]
// Now assemble the fixed header
copy(fixHdr[:], []byte(_Magic))
@ -242,45 +224,38 @@ func (e *Encryptor) start(wr io.Writer) error {
return fmt.Errorf("encrypt: can't marshal header: %w", err)
}
// Now calculate checksum of everything
h := sha256.New()
h.Write(buffer[:_FixedHdrLen+varSize])
cksum := h.Sum(sumHdr[:0])
h.Sum(sumHdr[:0])
// now make the data encryption keys, nonces etc.
outbuf := make([]byte, sha256.Size+_AesKeySize+_AEADNonceSize)
// we mix the header checksum (and it captures the sigtool version, sender
// identity, etc.)
buf := expand(outbuf, e.key, cksum, []byte(_DataKeyExpansion))
var dkey, hmackey []byte
e.nonce, buf = buf[:_AEADNonceSize], buf[_AEADNonceSize:]
dkey, buf = buf[:_AesKeySize], buf[_AesKeySize:]
hmackey = buf
aes, err := aes.NewCipher(dkey)
if err != nil {
return fmt.Errorf("encrypt: %w", err)
}
if e.ae, err = cipher.NewGCM(aes); err != nil {
return fmt.Errorf("encrypt: %w", err)
}
// Finally write out the header
// Finally write it out
err = fullwrite(buffer, wr)
if err != nil {
return fmt.Errorf("encrypt: %w", err)
}
e.hmac = hmac.New(sha256.New, hmackey)
e.buf = make([]byte, e.ChunkSize+4+uint32(e.ae.Overhead()))
// we mix the header checksum to create the encryption key
h = sha256.New()
h.Write([]byte(_EncryptNonce))
h.Write(e.key)
h.Write(sumHdr)
key := h.Sum(nil)
aes, err := aes.NewCipher(key)
if err != nil {
return fmt.Errorf("encrypt: %w", err)
}
ae, err := cipher.NewGCMWithNonceSize(aes, _AEADNonceLen)
if err != nil {
return fmt.Errorf("encrypt: %w", err)
}
e.buf = make([]byte, e.ChunkSize+4+uint32(ae.Overhead()))
e.ae = ae
e.started = true
debug("encrypt:\n\thdr-cksum: %x\n\taes-key: %x\n\tnonce: %x\n\thmac-key: %x\n",
cksum, dkey, e.nonce, hmackey)
return nil
}
@ -289,70 +264,30 @@ func (e *Encryptor) start(wr io.Writer) error {
// 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(pt []byte, wr io.Writer, i uint32, eof bool) error {
var z uint32 = uint32(len(pt))
var nonce [_AEADNonceSize]byte
func (e *Encryptor) encrypt(buf []byte, wr io.Writer, i uint32, eof bool) error {
var z uint32 = uint32(len(buf))
var nbuf [_AEADNonceLen]byte
// mark last block
if eof {
z |= _EOF
}
copy(nonce[:], e.nonce)
b := e.buf[:8]
binary.BigEndian.PutUint32(b[:4], z)
binary.BigEndian.PutUint32(b[4:], i)
// now change the upper bytes to track the block#; we use the len+eof as AD
binary.BigEndian.PutUint32(nonce[:4], i)
nonce := makeNonceV2(nbuf[:], e.Salt, b)
// put the encoded length+eof at the start of the output buf
b := e.buf[:4]
ctbuf := e.buf[4:]
binary.BigEndian.PutUint32(b, z)
ct := e.ae.Seal(ctbuf[:0], nonce[:], pt, b)
cbuf := e.buf[4:]
c := e.ae.Seal(cbuf[:0], nonce, buf, b[:])
// total number of bytes written
n := len(ct) + 4
n := len(c) + 4
err := fullwrite(e.buf[:n], wr)
if err != nil {
return fmt.Errorf("encrypt: %w", err)
}
e.hmac.Write(b)
e.hmac.Write(pt)
if eof {
return e.writeTrailer(wr)
}
return nil
}
// Write a trailer:
// - if authenticating sender, sign the hmac and put the signature in the trailer
// - if not authenticating sender, write random bytes to the trailer
func (e *Encryptor) writeTrailer(wr io.Writer) error {
var tr []byte
switch e.auth {
case true:
var hmac [sha256.Size]byte
e.hmac.Sum(hmac[:0])
// We know sender is non null.
sig, err := e.sender.SignMessage(hmac[:], "")
if err != nil {
return fmt.Errorf("encrypt: trailer: %w", err)
}
tr = sig.Sig
case false:
tr = randBuf(ed25519.SignatureSize)
}
if err := fullwrite(tr, wr); err != nil {
return fmt.Errorf("encrypt: trailer %w", err)
}
return nil
}
@ -360,18 +295,16 @@ func (e *Encryptor) writeTrailer(wr io.Writer) error {
type Decryptor struct {
pb.Header
ae cipher.AEAD
hmac hash.Hash
ae cipher.AEAD
rd io.Reader
buf []byte
hdrsum []byte
sender *PublicKey
// flag set to true if sender signed the key
auth bool
rd io.Reader
buf []byte
nonce []byte // nonce for the data decrypting cipher
key []byte // Decrypted root key
hdrsum []byte // cached header checksum
auth bool // flag set to true if sender signed the key
// Decrypted key
key []byte
eof bool
stream bool
}
@ -414,18 +347,14 @@ func NewDecryptor(rd io.Reader) (*Decryptor, error) {
return nil, fmt.Errorf("decrypt: error while reading header: %w", err)
}
// The checksum in the header
verify := varBuf[varSize:]
// the checksum we calculated
var csum [sha256.Size]byte
h := sha256.New()
h.Write(b[:])
h.Write(varBuf[:varSize])
cksum := h.Sum(csum[:0])
cksum := h.Sum(nil)
if subtle.ConstantTimeCompare(verify, cksum) == 0 {
if subtle.ConstantTimeCompare(verify, cksum[:]) == 0 {
return nil, ErrBadHeader
}
@ -443,7 +372,7 @@ func NewDecryptor(rd io.Reader) (*Decryptor, error) {
return nil, fmt.Errorf("decrypt: invalid chunkSize %d", d.ChunkSize)
}
if len(d.Salt) != _SaltSize {
if len(d.Salt) != _AEADNonceLen {
return nil, fmt.Errorf("decrypt: invalid nonce length %d", len(d.Salt))
}
@ -453,7 +382,7 @@ func NewDecryptor(rd io.Reader) (*Decryptor, error) {
// sanity check on the wrapped keys
for i, w := range d.Keys {
if len(w.DKey) <= _AesKeySize {
if len(w.DKey) <= 32 {
return nil, fmt.Errorf("decrypt: wrapped key %d: wrong-size encrypted key", i)
}
}
@ -473,8 +402,6 @@ func (d *Decryptor) SetPrivateKey(sk *PrivateKey, senderPk *PublicKey) error {
return fmt.Errorf("decrypt: can't unwrap key %d: %w", i, err)
}
if key != nil {
d.key = key
d.sender = senderPk
goto havekey
}
}
@ -482,37 +409,28 @@ func (d *Decryptor) SetPrivateKey(sk *PrivateKey, senderPk *PublicKey) error {
return ErrBadKey
havekey:
if err := d.verifySender(key, senderPk); err != nil {
if err := d.verifySender(key, sk, senderPk); err != nil {
return fmt.Errorf("decrypt: %w", err)
}
outbuf := make([]byte, sha256.Size+_AesKeySize+_AEADNonceSize)
d.key = key
buf := expand(outbuf, d.key, d.hdrsum, []byte(_DataKeyExpansion))
// we mix the header checksum into the key
h := sha256.New()
h.Write([]byte(_EncryptNonce))
h.Write(d.key)
h.Write(d.hdrsum)
key = h.Sum(nil)
var dkey, hmackey []byte
d.nonce, buf = buf[:_AEADNonceSize], buf[_AEADNonceSize:]
dkey, buf = buf[:_AesKeySize], buf[_AesKeySize:]
hmackey = buf
d.hmac = hmac.New(sha256.New, hmackey)
aes, err := aes.NewCipher(dkey)
aes, err := aes.NewCipher(key)
if err != nil {
return fmt.Errorf("decrypt: %w", err)
}
d.ae, err = cipher.NewGCM(aes)
d.ae, err = cipher.NewGCMWithNonceSize(aes, _AEADNonceLen)
if err != nil {
return fmt.Errorf("decrypt: %w", err)
}
debug("decrypt:\n\thdr-cksum: %x\n\taes-key: %x\n\tnonce: %x\n\thmac-key: %x\n",
d.hdrsum, dkey, d.nonce, hmackey)
// We have a separate on-stack buffer for reading the header (4 bytes).
// Thus, the actual I/O buf will never be larger than the chunksize + AEAD Overhead
d.buf = make([]byte, int(d.ChunkSize)+d.ae.Overhead())
return nil
}
@ -559,16 +477,17 @@ func (d *Decryptor) Decrypt(wr io.Writer) error {
// Decrypt exactly one chunk of data
func (d *Decryptor) decrypt(i uint32) ([]byte, bool, error) {
var b [8]byte
var nonceb [32]byte
var ovh uint32 = uint32(d.ae.Overhead())
var b [4]byte
var nonce [_AEADNonceSize]byte
var p []byte
n, err := io.ReadFull(d.rd, b[:])
n, err := io.ReadFull(d.rd, b[:4])
if err != nil || n == 0 {
return nil, false, fmt.Errorf("decrypt: premature EOF while reading header block %d", i)
}
m := binary.BigEndian.Uint32(b[:])
m := binary.BigEndian.Uint32(b[:4])
eof := (m & _EOF) > 0
m &= (_EOF - 1)
@ -581,14 +500,13 @@ func (d *Decryptor) decrypt(i uint32) ([]byte, bool, error) {
if !eof {
return nil, false, fmt.Errorf("decrypt: block %d: zero-sized chunk without EOF", i)
}
return nil, eof, nil
return p, eof, nil
default:
}
// make the nonce - top 4 bytes are the counter
copy(nonce[:], d.nonce)
binary.BigEndian.PutUint32(nonce[:4], i)
binary.BigEndian.PutUint32(b[4:], i)
nonce := makeNonceV2(nonceb[:], d.Salt, b[:])
z := m + ovh
n, err = io.ReadFull(d.rd, d.buf[:z])
@ -596,115 +514,57 @@ func (d *Decryptor) decrypt(i uint32) ([]byte, bool, error) {
return nil, false, fmt.Errorf("decrypt: premature EOF while reading block %d: %w", i, err)
}
pt, err := d.ae.Open(d.buf[:0], nonce[:], d.buf[:n], b[:])
p, err = d.ae.Open(d.buf[:0], nonce, d.buf[:n], b[:])
if err != nil {
return nil, false, fmt.Errorf("decrypt: can't decrypt chunk %d: %w", i, err)
}
if uint32(len(pt)) != m {
return nil, false, fmt.Errorf("decrypt: partial unsealed bytes; exp %d, saw %d", m, len(pt))
}
d.hmac.Write(b[:])
d.hmac.Write(pt)
if eof {
return d.processTrailer(pt, eof)
}
return pt, eof, nil
return p[:m], eof, nil
}
func (d *Decryptor) processTrailer(pt []byte, eof bool) ([]byte, bool, error) {
var rd [ed25519.SignatureSize]byte
_, err := io.ReadFull(d.rd, rd[:])
if err != nil {
return nil, false, fmt.Errorf("decrypt: premature EOF while reading trailer: %w", err)
}
if !d.auth {
// these are random bytes; ignore em
return pt, eof, nil
}
var hmac [sha256.Size]byte
cksum := d.hmac.Sum(hmac[:0])
ss := &Signature{
Sig: rd[:],
}
if ok := d.sender.VerifyMessage(cksum, ss); !ok {
return nil, eof, ErrBadTrailer
}
return pt, eof, nil
}
// optionally sign the checksum and encrypt everything
func (e *Encryptor) addSenderSig(sk *PrivateKey) error {
// Wrap sender's signature of the encryption key
// if sender has provided their identity to authenticate, we sign the data-enc key
// and encrypt the signature. At no point will we send the sender's identity.
func wrapSenderSig(sk *PrivateKey, key, salt []byte) ([]byte, error) {
var zero [ed25519.SignatureSize]byte
var auth bool
sig := zero[:]
var sig []byte
if e.sender != nil {
var csum [sha256.Size]byte
switch {
case sk == nil:
sig = zero[:]
// We capture essential meta-data from the sender; viz:
// - Sender tool version
// - Sender generated curve25519 PK
// - session salt, root key
h := sha256.New()
h.Write([]byte(_Magic))
h.Write([]byte{_SigtoolVersion})
h.Write(e.Pk)
h.Write(e.Salt)
h.Write(e.key)
cksum := h.Sum(csum[:0])
xsig, err := e.sender.SignMessage(cksum, "")
default:
xsig, err := sk.SignMessage(key, "")
if err != nil {
return fmt.Errorf("wrap: can't sign: %w", err)
return nil, fmt.Errorf("wrap: can't sign: %w", err)
}
sig = xsig.Sig
auth = true
}
buf := make([]byte, _AesKeySize+_AEADNonceSize)
buf = expand(buf, e.key, e.Salt, []byte(_WrapSender))
ekey, nonce := buf[:_AesKeySize], buf[_AesKeySize:]
aes, err := aes.NewCipher(ekey)
aes, err := aes.NewCipher(key)
if err != nil {
return fmt.Errorf("senderId: %w", err)
return nil, fmt.Errorf("wrap: %w", err)
}
ae, err := cipher.NewGCM(aes)
if err != nil {
return fmt.Errorf("senderId: %w", err)
return nil, fmt.Errorf("wrap: %w", err)
}
outbuf := make([]byte, ed25519.SignatureSize+ae.Overhead())
buf = ae.Seal(outbuf[:0], nonce, sig, nil)
tagsize := ae.Overhead()
nonceSize := ae.NonceSize()
e.auth = auth
e.Sender = buf
nonce := sha256Slices([]byte(_WrapSenderNonce), salt)[:nonceSize]
esig := make([]byte, tagsize+len(sig))
return nil
return ae.Seal(esig[:0], nonce, sig, nil), nil
}
// unwrap sender's signature using 'key' and extract the signature
// Optionally, verify the signature using the sender's PK (if provided).
func (d *Decryptor) verifySender(key []byte, senderPk *PublicKey) error {
outbuf := make([]byte, _AEADNonceSize+_AesKeySize)
buf := expand(outbuf, key, d.Salt, []byte(_WrapSender))
ekey, nonce := buf[:_AesKeySize], buf[_AesKeySize:]
aes, err := aes.NewCipher(ekey)
func (d *Decryptor) verifySender(key []byte, sk *PrivateKey, senderPK *PublicKey) error {
aes, err := aes.NewCipher(key)
if err != nil {
return fmt.Errorf("unwrap: %w", err)
}
@ -714,70 +574,44 @@ func (d *Decryptor) verifySender(key []byte, senderPk *PublicKey) error {
return fmt.Errorf("unwrap: %w", err)
}
var sigbuf [ed25519.SignatureSize]byte
var zero [ed25519.SignatureSize]byte
sig, err := ae.Open(sigbuf[:0], nonce, d.Sender, nil)
nonceSize := ae.NonceSize()
nonce := sha256Slices([]byte(_WrapSenderNonce), d.Salt)[:nonceSize]
sig := make([]byte, ed25519.SignatureSize)
sig, err = ae.Open(sig[:0], nonce, d.SenderSign, nil)
if err != nil {
return fmt.Errorf("unwrap: can't open sender info: %w", err)
}
var zero [ed25519.SignatureSize]byte
// Did the sender actually sign anything?
if subtle.ConstantTimeCompare(zero[:], sig) == 0 {
if senderPk == nil {
return ErrNoSenderPK
}
var csum [sha256.Size]byte
h := sha256.New()
h.Write([]byte(_Magic))
h.Write([]byte{_SigtoolVersion})
h.Write(d.Pk)
h.Write(d.Salt)
h.Write(key)
cksum := h.Sum(csum[:0])
ss := &Signature{
Sig: sig,
}
if ok := senderPk.VerifyMessage(cksum, ss); !ok {
return ErrBadSender
}
// we set this to indicate that the sender authenticated themselves;
d.auth = true
}
if senderPK != nil {
ss := &Signature{
Sig: sig,
}
if ok := senderPK.VerifyMessage(key, ss); !ok {
return fmt.Errorf("unwrap: sender verification failed")
}
}
}
return nil
}
// Wrap data encryption key 'k' with the sender's PK and our ephemeral curve SK
//
// basically, we do a scalarmult: Ephemeral encryption/decryption SK x receiver PK
// basically, we do a scalarmult: Ephemeral encryption/decryption SK x receiver PK
func (e *Encryptor) wrapKey(pk *PublicKey) (*pb.WrappedKey, error) {
rxPK := pk.ToCurve25519PK()
sekrit, err := curve25519.X25519(e.encSK, rxPK)
dkek, err := curve25519.X25519(e.encSK, rxPK)
if err != nil {
return nil, fmt.Errorf("wrap: %w", err)
}
var shasum [sha256.Size]byte
rbuf := randBuf(_RxNonceSize)
h := sha256.New()
h.Write(e.Salt)
h.Write(rbuf[:])
h.Sum(shasum[:0])
out := make([]byte, _AesKeySize+_RxNonceSize)
buf := expand(out[:], sekrit, shasum[:], []byte(_WrapReceiver))
kek, nonce := buf[:_AesKeySize], buf[_AesKeySize:]
aes, err := aes.NewCipher(kek)
aes, err := aes.NewCipher(dkek)
if err != nil {
return nil, fmt.Errorf("wrap: %w", err)
}
@ -787,10 +621,14 @@ func (e *Encryptor) wrapKey(pk *PublicKey) (*pb.WrappedKey, error) {
return nil, fmt.Errorf("wrap: %w", err)
}
ekey := make([]byte, ae.Overhead()+len(e.key))
tagsize := ae.Overhead()
nonceSize := ae.NonceSize()
nonceR := sha256Slices([]byte(_WrapReceiverNonce), e.Salt)[:nonceSize]
ekey := make([]byte, tagsize+len(e.key))
w := &pb.WrappedKey{
DKey: ae.Seal(ekey[:0], nonce, e.key, pk.Pk),
Nonce: rbuf,
DKey: ae.Seal(ekey[:0], nonceR, e.key, pk.Pk),
}
return w, nil
@ -800,48 +638,40 @@ func (e *Encryptor) wrapKey(pk *PublicKey) (*pb.WrappedKey, error) {
// senders ephemeral PublicKey
func (d *Decryptor) unwrapKey(w *pb.WrappedKey, sk *PrivateKey) ([]byte, error) {
ourSK := sk.ToCurve25519SK()
sekrit, err := curve25519.X25519(ourSK, d.Pk)
dkek, err := curve25519.X25519(ourSK, d.Pk)
if err != nil {
return nil, fmt.Errorf("unwrap: %w", err)
}
var shasum [sha256.Size]byte
h := sha256.New()
h.Write(d.Salt)
h.Write(w.Nonce)
h.Sum(shasum[:0])
out := make([]byte, _AesKeySize+_RxNonceSize)
buf := expand(out[:], sekrit, shasum[:], []byte(_WrapReceiver))
kek, nonce := buf[:_AesKeySize], buf[_AesKeySize:]
aes, err := aes.NewCipher(kek)
aes, err := aes.NewCipher(dkek)
if err != nil {
return nil, fmt.Errorf("wrap: %w", err)
return nil, fmt.Errorf("unwrap: %w", err)
}
ae, err := cipher.NewGCM(aes)
if err != nil {
return nil, fmt.Errorf("wrap: %w", err)
return nil, fmt.Errorf("unwrap: %w", err)
}
want := _AesKeySize + ae.Overhead()
// 32 == AES-256 key size
want := 32 + ae.Overhead()
if len(w.DKey) != want {
return nil, fmt.Errorf("unwrap: incorrect decrypt bytes (need %d, saw %d)", want, len(w.DKey))
}
nonceSize := ae.NonceSize()
nonceR := sha256Slices([]byte(_WrapReceiverNonce), d.Salt)[:nonceSize]
pk := sk.PublicKey()
dkey := make([]byte, _AesKeySize) // decrypted data decryption key
dkey := make([]byte, 32) // decrypted data decryption key
// we indicate incorrect receiver SK by returning a nil key
dkey, err = ae.Open(dkey[:0], nonce, w.DKey, pk.Pk)
dkey, err = ae.Open(dkey[:0], nonceR, w.DKey, pk.Pk)
if err != nil {
return nil, nil
}
// we have successfully found the correct recipient
return dkey, nil
}
@ -861,14 +691,30 @@ func fullwrite(buf []byte, wr io.Writer) error {
return nil
}
// make aead nonce from salt, chunk-size and block#
// First 8 bytes are chunk-size and nonce (in 'ad')
func makeNonceV2(dest []byte, salt []byte, ad []byte) []byte {
n := len(ad)
copy(dest, ad)
copy(dest[n:], salt)
return dest
}
// make aead nonce from salt, chunk-size and block# for v1
// This is here for historical documentation purposes
func makeNonceV1(dest []byte, salt []byte, ad []byte) []byte {
h := sha256.New()
h.Write(salt)
h.Write(ad)
return h.Sum(dest[:0])
}
// generate a KEK from a shared DH key and a Pub Key
func expand(out []byte, shared, salt, ad []byte) []byte {
h := hkdf.New(sha512.New, shared, salt, ad)
_, err := io.ReadFull(h, out)
if err != nil {
panic(fmt.Sprintf("hkdf: failed to generate %d bytes: %s", len(out), err))
}
return out
func expand(shared, pk []byte) ([]byte, error) {
kek := make([]byte, 32)
h := hkdf.New(sha512.New, shared, pk, nil)
_, err := io.ReadFull(h, kek)
return kek, err
}
func newSender() (sk, pk []byte, err error) {
@ -890,25 +736,4 @@ func sha256Slices(v ...[]byte) []byte {
return h.Sum(nil)[:]
}
var _debug int = 0
// Enable debugging of this module;
// level > 0 elicits debug messages on os.Stderr
func Debug(level int) {
_debug = level
}
func debug(s string, v ...interface{}) {
if _debug <= 0 {
return
}
z := fmt.Sprintf(s, v...)
if n := len(z); z[n-1] != '\n' {
z += "\n"
}
os.Stderr.WriteString(z)
os.Stderr.Sync()
}
// EOF

View file

@ -20,19 +20,17 @@ import (
var (
ErrClosed = errors.New("encrypt: stream already closed")
ErrNoKey = errors.New("decrypt: no private key set for decryption")
ErrNoKey = errors.New("decrypt: No private key set for decryption")
ErrEncStarted = errors.New("encrypt: can't add new recipient after encryption has started")
ErrDecStarted = errors.New("decrypt: can't add new recipient after decryption has started")
ErrEncIsStream = errors.New("encrypt: can't use Encrypt() after using streaming I/O")
ErrNotSigTool = errors.New("decrypt: not a sigtool encrypted file?")
ErrNotSigTool = errors.New("decrypt: Not a sigtool encrypted file?")
ErrHeaderTooBig = errors.New("decrypt: header too large (max 1048576)")
ErrHeaderTooSmall = errors.New("decrypt: header too small (min 32)")
ErrBadHeader = errors.New("decrypt: header corrupted")
ErrNoWrappedKeys = errors.New("decrypt: no wrapped keys in encrypted file")
ErrNoWrappedKeys = errors.New("decrypt: No wrapped keys in encrypted file")
ErrBadKey = errors.New("decrypt: wrong key")
ErrBadTrailer = errors.New("decrypt: message integrity failed (bad trailer)")
ErrBadSender = errors.New("unwrap: sender verification failed")
ErrNoSenderPK = errors.New("unwrap: missing sender public key")
ErrIncorrectPassword = errors.New("ssh: invalid passphrase")
ErrNoPEMFound = errors.New("ssh: no PEM block found")

View file

@ -1,67 +0,0 @@
// iomisc.go -- misc i/o functions
//
// (c) 2016 Sudhi Herle <sudhi@herle.net>
//
// Licensing Terms: GPLv2
//
// If you need a commercial license for this work, please contact
// the author.
//
// This software does not come with any express or implied
// warranty; it is provided "as is". No claim is made to its
// suitability for any purpose.
package sign
import (
"encoding/binary"
"fmt"
"github.com/opencoff/go-fio"
"github.com/opencoff/go-mmap"
"hash"
"os"
)
// Simple function to reliably write data to a file.
// Does MORE than ioutil.WriteFile() - in that it doesn't trash the
// existing file with an incomplete write.
func writeFile(fn string, b []byte, ovwrite bool, mode uint32) error {
var opts uint32
if ovwrite {
opts |= fio.OPT_OVERWRITE
}
sf, err := fio.NewSafeFile(fn, opts, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.FileMode(mode))
if err != nil {
return err
}
defer sf.Abort()
if _, err = sf.Write(b); err != nil {
return err
}
return sf.Close()
}
// Generate file checksum out of hash function h
func fileCksum(fn string, h hash.Hash) ([]byte, error) {
fd, err := os.Open(fn)
if err != nil {
return nil, fmt.Errorf("can't open %s: %s", fn, err)
}
defer fd.Close()
sz, err := mmap.Reader(fd, func(b []byte) error {
h.Write(b)
return nil
})
if err != nil {
return nil, err
}
var b [8]byte
binary.BigEndian.PutUint64(b[:], uint64(sz))
h.Write(b[:])
return h.Sum(nil)[:], nil
}

View file

@ -25,14 +25,18 @@ import (
"crypto/sha256"
"crypto/sha512"
"encoding/base64"
"encoding/binary"
"fmt"
"hash"
"io/ioutil"
"math/big"
"os"
Ed "crypto/ed25519"
"golang.org/x/crypto/scrypt"
"gopkg.in/yaml.v3"
"gopkg.in/yaml.v2"
"github.com/opencoff/go-utils"
)
// Private Ed25519 key
@ -180,14 +184,14 @@ func makePrivateKeyFromBytes(sk *PrivateKey, buf []byte) error {
return nil
}
/*
// Make a private key from 64-bytes of extended Ed25519 key
func PrivateKeyFromBytes(buf []byte) (*PrivateKey, error) {
var sk PrivateKey
if err := makePrivateKeyFromBytes(&sk, buf); err != nil {
return nil, err
}
return &sk, nil
return makePrivateKeyFromBytes(&sk, buf)
}
*/
// Given a secret key, return the corresponding Public Key
func (sk *PrivateKey) PublicKey() *PublicKey {
@ -370,29 +374,6 @@ func MakePublicKey(yml []byte) (*PublicKey, error) {
return &pk, nil
}
// Make a public key from a string
func MakePublicKeyFromString(s string) (*PublicKey, error) {
// first try to decode it as a openssh key
if pk2, err := parseEncPubKey([]byte(s), "command-line-pk"); err == nil {
return pk2, nil
}
// Now try to decode as an sigtool key
b64 := base64.StdEncoding.DecodeString
pkb, err := b64(s)
if err != nil {
return nil, err
}
var pk PublicKey
err = makePublicKeyFromBytes(&pk, pkb)
if err != nil {
return nil, err
}
return &pk, nil
}
func makePublicKeyFromBytes(pk *PublicKey, b []byte) error {
if len(b) != 32 {
return fmt.Errorf("public key is malformed (len %d!)", len(b))
@ -405,14 +386,14 @@ func makePublicKeyFromBytes(pk *PublicKey, b []byte) error {
return nil
}
/*
// Make a public key from a byte string
func PublicKeyFromBytes(b []byte) (*PublicKey, error) {
var pk PublicKey
if err := makePublicKeyFromBytes(&pk, b); err != nil {
return nil, err
}
return &pk, nil
makePublicKeyFromBytes(&pk, b)
}
*/
// Serialize a PublicKey into file 'fn' with a human readable 'comment'.
// If 'ovwrite' is true, overwrite the file if it exists.
@ -518,6 +499,41 @@ func (pk *PublicKey) UnmarshalBinary(yml []byte) error {
// -- Internal Utility Functions --
// Simple function to reliably write data to a file.
// Does MORE than ioutil.WriteFile() - in that it doesn't trash the
// existing file with an incomplete write.
func writeFile(fn string, b []byte, ovwrite bool, mode uint32) error {
sf, err := NewSafeFile(fn, ovwrite, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.FileMode(mode))
if err != nil {
return err
}
defer sf.Abort() // always cleanup on error
sf.Write(b)
return sf.Close()
}
// Generate file checksum out of hash function h
func fileCksum(fn string, h hash.Hash) ([]byte, error) {
fd, err := os.Open(fn)
if err != nil {
return nil, fmt.Errorf("can't open %s: %s", fn, err)
}
defer fd.Close()
sz, err := utils.MmapReader(fd, 0, 0, h)
if err != nil {
return nil, err
}
var b [8]byte
binary.BigEndian.PutUint64(b[:], uint64(sz))
h.Write(b[:])
return h.Sum(nil)[:], nil
}
func clamp(k []byte) []byte {
k[0] &= 248
k[31] &= 127

View file

@ -38,8 +38,3 @@ func randRead(b []byte) []byte {
}
return b
}
func randBuf(sz int) []byte {
b := make([]byte, sz)
return randRead(b)
}

124
sign/safefile.go Normal file
View file

@ -0,0 +1,124 @@
// safefile.go - safe file creation and unwinding on error
//
// (c) 2021 Sudhi Herle <sudhi@herle.net>
//
// Licensing Terms: GPLv2
//
// If you need a commercial license for this work, please contact
// the author.
//
// This software does not come with any express or implied
// warranty; it is provided "as is". No claim is made to its
// suitability for any purpose.
package sign
import (
"fmt"
"io"
"os"
)
// SafeFile is an io.WriteCloser which uses a temporary file that
// will be atomically renamed when there are no errors and
// caller invokes Close(). Callers are advised to call
// Abort() in the appropriate error handling (defer) context
// so that the temporary file is properly deleted.
type SafeFile struct {
*os.File
// error for writes recorded once
err error
name string // actual filename
closed bool // set if the file is closed properly
}
var _ io.WriteCloser = &SafeFile{}
// NewSafeFile creates a new temporary file that would either be
// aborted or safely renamed to the correct name.
// 'nm' is the name of the final file; if 'ovwrite' is true,
// then the file is overwritten if it exists.
func NewSafeFile(nm string, ovwrite bool, flag int, perm os.FileMode) (*SafeFile, error) {
if _, err := os.Stat(nm); err == nil && !ovwrite {
return nil, fmt.Errorf("safefile: won't overwrite existing %s", nm)
}
// forcibly unlink the old file - so previous artifacts don't exist
os.Remove(nm)
tmp := fmt.Sprintf("%s.tmp.%d.%x", nm, os.Getpid(), randu32())
fd, err := os.OpenFile(tmp, flag, perm)
if err != nil {
return nil, err
}
sf := &SafeFile{
File: fd,
name: nm,
}
return sf, nil
}
// Attempt to write everything in 'b' and don't proceed if there was
// a previous error or the file was already closed.
func (sf *SafeFile) Write(b []byte) (int, error) {
if sf.err != nil {
return 0, sf.err
}
if sf.closed {
return 0, fmt.Errorf("safefile: %s is closed", sf.Name())
}
var z, nw int
n := len(b)
for n > 0 {
if nw, sf.err = sf.File.Write(b); sf.err != nil {
return z, sf.err
}
z += nw
n -= nw
b = b[nw:]
}
return z, nil
}
// Abort the file write and remove any temporary artifacts
func (sf *SafeFile) Abort() {
// if we've successfully closed, nothing to do!
if sf.closed {
return
}
sf.closed = true
sf.File.Close()
os.Remove(sf.Name())
}
// Close flushes all file data & metadata to disk, closes the file and atomically renames
// the temp file to the actual file - ONLY if there were no intervening errors.
func (sf *SafeFile) Close() error {
if sf.err != nil {
sf.Abort()
return sf.err
}
// mark this file as closed!
sf.closed = true
if sf.err = sf.Sync(); sf.err != nil {
return sf.err
}
if sf.err = sf.File.Close(); sf.err != nil {
return sf.err
}
if sf.err = os.Rename(sf.Name(), sf.name); sf.err != nil {
return sf.err
}
return nil
}

View file

@ -27,8 +27,7 @@ import (
"io/ioutil"
Ed "crypto/ed25519"
"gopkg.in/yaml.v3"
"gopkg.in/yaml.v2"
)
// An Ed25519 Signature
@ -39,9 +38,8 @@ type Signature struct {
// Sign a prehashed Message; return the signature as opaque bytes
// Signature is an YAML file:
//
// Comment: source file path
// Signature: Ed25519 signature
// Comment: source file path
// Signature: Ed25519 signature
func (sk *PrivateKey) SignMessage(ck []byte, comment string) (*Signature, error) {
h := sha512.New()
h.Write([]byte("sigtool signed message"))

354
sigtool.go Normal file
View file

@ -0,0 +1,354 @@
// sigtool.go -- Tool to generate, manage Ed25519 keys and
// signatures.
//
// (c) 2016 Sudhi Herle <sudhi@herle.net>
//
// Licensing Terms: GPLv2
//
// If you need a commercial license for this work, please contact
// the author.
//
// This software does not come with any express or implied
// warranty; it is provided "as is". No claim is made to its
// suitability for any purpose.
package main
import (
"fmt"
"io"
"os"
"path"
"strings"
"github.com/opencoff/go-utils"
flag "github.com/opencoff/pflag"
"github.com/opencoff/sigtool/sign"
)
var Z string = path.Base(os.Args[0])
func main() {
var ver, help bool
mf := flag.NewFlagSet(Z, flag.ExitOnError)
mf.SetInterspersed(false)
mf.BoolVarP(&ver, "version", "v", false, "Show version info and exit")
mf.BoolVarP(&help, "help", "h", false, "Show help info exit")
mf.Parse(os.Args[1:])
if ver {
fmt.Printf("%s - %s [%s; %s]\n", Z, ProductVersion, RepoVersion, Buildtime)
os.Exit(0)
}
if help {
usage(0)
}
args := mf.Args()
if len(args) < 1 {
Die("Insufficient arguments. Try '%s -h'", Z)
}
cmds := map[string]func(args []string){
"generate": gen,
"sign": signify,
"verify": verify,
"encrypt": encrypt,
"decrypt": decrypt,
"help": func(_ []string) {
usage(0)
},
}
words := make([]string, 0, len(cmds))
for k := range cmds {
words = append(words, k)
}
ab := utils.Abbrev(words)
canon, ok := ab[strings.ToLower(args[0])]
if !ok {
Die("Unknown command %s", args[0])
}
cmd := cmds[canon]
if cmd == nil {
Die("can't map command %s", canon)
}
cmd(args[1:])
// always call Exit so that at-exit handlers are called.
Exit(0)
}
// Run the generate command
func gen(args []string) {
var nopw, help, force bool
var comment string
var envpw string
fs := flag.NewFlagSet("generate", flag.ExitOnError)
fs.BoolVarP(&help, "help", "h", false, "Show this help and exit")
fs.BoolVarP(&nopw, "no-password", "", false, "Don't ask for a password for the private key")
fs.StringVarP(&comment, "comment", "c", "", "Use `C` as the text comment for the keys")
fs.StringVarP(&envpw, "env-password", "E", "", "Use passphrase from environment variable `E`")
fs.BoolVarP(&force, "overwrite", "", false, "Overwrite the output file if it exists")
fs.Parse(args)
if help {
fs.SetOutput(os.Stdout)
fmt.Printf(`%s generate|gen|g [options] file-prefix
Generate a new Ed25519 public+private key pair and write public key to
FILE-PREFIX.pub and private key to FILE-PREFIX.key.
Options:
`, Z)
fs.PrintDefaults()
os.Exit(0)
}
args = fs.Args()
if len(args) < 1 {
Die("Insufficient arguments to 'generate'. Try '%s generate -h' ..", Z)
}
bn := args[0]
pkn := fmt.Sprintf("%s.pub", path.Clean(bn))
skn := fmt.Sprintf("%s.key", path.Clean(bn))
if !force {
if exists(pkn) || exists(skn) {
Die("Public/Private key files (%s, %s) exist. won't overwrite!", skn, pkn)
}
}
var err error
var pw []byte
if !nopw {
var pws string
if len(envpw) > 0 {
pws = os.Getenv(envpw)
} else {
pws, err = utils.Askpass("Enter passphrase for private key", true)
if err != nil {
Die("%s", err)
}
}
pw = []byte(pws)
}
sk, err := sign.NewPrivateKey()
if err != nil {
Die("%s", err)
}
if err = sk.Serialize(skn, comment, force, pw); err != nil {
Die("%s", err)
}
pk := sk.PublicKey()
if err = pk.Serialize(pkn, comment, force); err != nil {
Die("%s", err)
}
}
// Run the 'sign' command.
func signify(args []string) {
var nopw, help, force bool
var output string
var envpw string
fs := flag.NewFlagSet("sign", flag.ExitOnError)
fs.BoolVarP(&help, "help", "h", false, "Show this help and exit")
fs.BoolVarP(&nopw, "no-password", "", false, "Don't ask for a password for the private key")
fs.StringVarP(&envpw, "env-password", "E", "", "Use passphrase from environment variable `E`")
fs.StringVarP(&output, "output", "o", "", "Write signature to file `F`")
fs.BoolVarP(&force, "overwrite", "", false, "Overwrite previous signature file if it exists")
fs.Parse(args)
if help {
fs.SetOutput(os.Stdout)
fmt.Printf(`%s sign|s [options] privkey file
Sign FILE with a Ed25519 private key PRIVKEY and write signature to FILE.sig
Options:
`, Z)
fs.PrintDefaults()
os.Exit(0)
}
args = fs.Args()
if len(args) < 2 {
Die("Insufficient arguments to 'sign'. Try '%s sign -h' ..", Z)
}
kn := args[0]
fn := args[1]
outf := fmt.Sprintf("%s.sig", fn)
var err error
if len(output) > 0 {
outf = output
}
var fd io.WriteCloser = os.Stdout
if outf != "-" {
sf, err := sign.NewSafeFile(outf, force, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
if err != nil {
Die("can't create sig file: %s", err)
}
// we unlink and remove temp on any error
AtExit(sf.Abort)
defer sf.Abort()
fd = sf
}
sk, err := sign.ReadPrivateKey(kn, func() ([]byte, error) {
if nopw {
return nil, nil
}
var pws string
if len(envpw) > 0 {
pws = os.Getenv(envpw)
} else {
pws, err = utils.Askpass("Enter passphrase for private key", false)
if err != nil {
Die("%s", err)
}
}
return []byte(pws), nil
})
if err != nil {
Die("%s", err)
}
sig, err := sk.SignFile(fn)
if err != nil {
Die("%s", err)
}
sigbytes, err := sig.MarshalBinary(fmt.Sprintf("input=%s", fn))
fd.Write(sigbytes)
fd.Close()
}
// Verify signature on a given file
func verify(args []string) {
var help, quiet bool
fs := flag.NewFlagSet("verify", flag.ExitOnError)
fs.BoolVarP(&help, "help", "h", false, "Show this help and exit")
fs.BoolVarP(&quiet, "quiet", "q", false, "Don't show any output; exit with status code only")
fs.Parse(args)
if help {
fs.SetOutput(os.Stdout)
fmt.Printf(`%s verify|v [options] pubkey sig file
Verify an Ed25519 signature in SIG of FILE using a public key PUBKEY.
Options:
`, Z)
fs.PrintDefaults()
os.Exit(0)
}
args = fs.Args()
if len(args) < 3 {
Die("Insufficient arguments to 'verify'. Try '%s verify -h' ..", Z)
}
pn := args[0]
sn := args[1]
fn := args[2]
sig, err := sign.ReadSignature(sn)
if err != nil {
Die("Can't read signature '%s': %s", sn, err)
}
pk, err := sign.ReadPublicKey(pn)
if err != nil {
Die("%s", err)
}
if !sig.IsPKMatch(pk) {
Die("Wrong public key '%s' for verifying '%s'", pn, sn)
}
ok, err := pk.VerifyFile(fn, sig)
if err != nil {
Die("%s", err)
}
exit := 0
if !ok {
exit = 1
}
if !quiet {
if ok {
fmt.Printf("%s: Signature %s verified\n", fn, sn)
} else {
fmt.Printf("%s: Signature %s verification failure\n", fn, sn)
}
}
os.Exit(exit)
}
func usage(c int) {
x := fmt.Sprintf(`%s is a tool to generate, sign and verify files with Ed25519 signatures.
Usage: %s [global-options] command [options] arg [args..]
Global options:
-h, --help Show help and exit
-v, --version Show version info and exit.
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))
os.Exit(c)
}
// Return true if $bn.key or $bn.pub exist; false otherwise
func exists(nm string) bool {
if _, err := os.Stat(nm); err == nil {
return true
}
return false
}
// This will be filled in by "build"
var RepoVersion string = "UNDEFINED"
var Buildtime string = "UNDEFINED"
var ProductVersion string = "UNDEFINED"
// vim: ft=go:sw=8:ts=8:noexpandtab:tw=98:

View file

@ -1,100 +0,0 @@
// gen.go -- generate keys
//
// (c) 2016 Sudhi Herle <sudhi@herle.net>
//
// Licensing Terms: GPLv2
//
// If you need a commercial license for this work, please contact
// the author.
//
// This software does not come with any express or implied
// warranty; it is provided "as is". No claim is made to its
// suitability for any purpose.
package main
import (
"fmt"
"os"
"path"
"git.rgst.io/homelab/sigtool/v3/sign"
"github.com/opencoff/go-utils"
flag "github.com/opencoff/pflag"
)
// Run the generate command
func gen(args []string) {
var nopw, help, force bool
var comment string
var envpw string
fs := flag.NewFlagSet("generate", flag.ExitOnError)
fs.BoolVarP(&help, "help", "h", false, "Show this help and exit")
fs.BoolVarP(&nopw, "no-password", "", false, "Don't ask for a password for the private key")
fs.StringVarP(&comment, "comment", "c", "", "Use `C` as the text comment for the keys")
fs.StringVarP(&envpw, "env-password", "E", "", "Use passphrase from environment variable `E`")
fs.BoolVarP(&force, "overwrite", "", false, "Overwrite the output file if it exists")
fs.Parse(args)
if help {
fs.SetOutput(os.Stdout)
fmt.Printf(`%s generate|gen|g [options] file-prefix
Generate a new Ed25519 public+private key pair and write public key to
FILE-PREFIX.pub and private key to FILE-PREFIX.key.
Options:
`, Z)
fs.PrintDefaults()
os.Exit(0)
}
args = fs.Args()
if len(args) < 1 {
Die("Insufficient arguments to 'generate'. Try '%s generate -h' ..", Z)
}
bn := args[0]
pkn := fmt.Sprintf("%s.pub", path.Clean(bn))
skn := fmt.Sprintf("%s.key", path.Clean(bn))
if !force {
if exists(pkn) || exists(skn) {
Die("Public/Private key files (%s, %s) exist. won't overwrite!", skn, pkn)
}
}
var err error
var pw []byte
if !nopw {
var pws string
if len(envpw) > 0 {
pws = os.Getenv(envpw)
} else {
pws, err = utils.Askpass("Enter passphrase for private key", true)
if err != nil {
Die("%s", err)
}
}
pw = []byte(pws)
}
sk, err := sign.NewPrivateKey()
if err != nil {
Die("%s", err)
}
if err = sk.Serialize(skn, comment, force, pw); err != nil {
Die("%s", err)
}
pk := sk.PublicKey()
if err = pk.Serialize(pkn, comment, force); err != nil {
Die("%s", err)
}
}

View file

@ -1,116 +0,0 @@
// sign.go -- 'sign' command implementation
//
// (c) 2016 Sudhi Herle <sudhi@herle.net>
//
// Licensing Terms: GPLv2
//
// If you need a commercial license for this work, please contact
// the author.
//
// This software does not come with any express or implied
// warranty; it is provided "as is". No claim is made to its
// suitability for any purpose.
package main
import (
"fmt"
"io"
"os"
"git.rgst.io/homelab/sigtool/v3/sign"
"github.com/opencoff/go-fio"
"github.com/opencoff/go-utils"
flag "github.com/opencoff/pflag"
)
// Run the 'sign' command.
func signify(args []string) {
var nopw, help, force bool
var output string
var envpw string
fs := flag.NewFlagSet("sign", flag.ExitOnError)
fs.BoolVarP(&help, "help", "h", false, "Show this help and exit")
fs.BoolVarP(&nopw, "no-password", "", false, "Don't ask for a password for the private key")
fs.StringVarP(&envpw, "env-password", "E", "", "Use passphrase from environment variable `E`")
fs.StringVarP(&output, "output", "o", "", "Write signature to file `F`")
fs.BoolVarP(&force, "overwrite", "", false, "Overwrite previous signature file if it exists")
fs.Parse(args)
if help {
fs.SetOutput(os.Stdout)
fmt.Printf(`%s sign|s [options] privkey file
Sign FILE with a Ed25519 private key PRIVKEY and write signature to FILE.sig
Options:
`, Z)
fs.PrintDefaults()
os.Exit(0)
}
args = fs.Args()
if len(args) < 2 {
Die("Insufficient arguments to 'sign'. Try '%s sign -h' ..", Z)
}
kn := args[0]
fn := args[1]
outf := fmt.Sprintf("%s.sig", fn)
var err error
if len(output) > 0 {
outf = output
}
var fd io.WriteCloser = os.Stdout
if outf != "-" {
var opts uint32
if force {
opts |= fio.OPT_OVERWRITE
}
sf, err := fio.NewSafeFile(outf, opts, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
if err != nil {
Die("can't create sig file: %s", err)
}
// we unlink and remove temp on any error
AtExit(sf.Abort)
defer sf.Abort()
fd = sf
}
sk, err := sign.ReadPrivateKey(kn, func() ([]byte, error) {
if nopw {
return nil, nil
}
var pws string
if len(envpw) > 0 {
pws = os.Getenv(envpw)
} else {
pws, err = utils.Askpass("Enter passphrase for private key", false)
if err != nil {
Die("%s", err)
}
}
return []byte(pws), nil
})
if err != nil {
Die("%s", err)
}
sig, err := sk.SignFile(fn)
if err != nil {
Die("%s", err)
}
sigbytes, err := sig.MarshalBinary(fmt.Sprintf("input=%s", fn))
fd.Write(sigbytes)
fd.Close()
}

View file

@ -1,129 +0,0 @@
// sigtool.go -- Tool to generate, manage Ed25519 keys and
// signatures.
//
// (c) 2016 Sudhi Herle <sudhi@herle.net>
//
// Licensing Terms: GPLv2
//
// If you need a commercial license for this work, please contact
// the author.
//
// This software does not come with any express or implied
// warranty; it is provided "as is". No claim is made to its
// suitability for any purpose.
package main
import (
"fmt"
"os"
"path"
"strings"
"git.rgst.io/homelab/sigtool/v3/sign"
"github.com/opencoff/go-utils"
flag "github.com/opencoff/pflag"
)
var Z string = path.Base(os.Args[0])
func main() {
var ver, help, debug bool
mf := flag.NewFlagSet(Z, flag.ExitOnError)
mf.SetInterspersed(false)
mf.BoolVarP(&ver, "version", "v", false, "Show version info and exit")
mf.BoolVarP(&help, "help", "h", false, "Show help info exit")
mf.BoolVarP(&debug, "debug", "", false, "Enable debug mode")
mf.Parse(os.Args[1:])
if ver {
fmt.Printf("%s - %s [%s]\n", Z, ProductVersion, RepoVersion)
os.Exit(0)
}
if help {
usage(0)
}
args := mf.Args()
if len(args) < 1 {
Die("Insufficient arguments. Try '%s -h'", Z)
}
cmds := map[string]func(args []string){
"generate": gen,
"sign": signify,
"verify": verify,
"encrypt": encrypt,
"decrypt": decrypt,
"help": func(_ []string) {
usage(0)
},
}
words := make([]string, 0, len(cmds))
for k := range cmds {
words = append(words, k)
}
ab := utils.Abbrev(words)
canon, ok := ab[strings.ToLower(args[0])]
if !ok {
Die("Unknown command %s", args[0])
}
cmd := cmds[canon]
if cmd == nil {
Die("can't map command %s", canon)
}
if debug {
sign.Debug(1)
}
cmd(args[1:])
// always call Exit so that at-exit handlers are called.
Exit(0)
}
// Verify signature on a given file
func usage(c int) {
x := fmt.Sprintf(`%s is a tool to generate, sign and verify files with Ed25519 signatures.
Usage: %s [global-options] command [options] arg [args..]
Global options:
-h, --help Show help and exit
-v, --version Show version info and exit
--debug Enable debug (DANGEROUS)
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))
os.Exit(c)
}
// Return true if $bn.key or $bn.pub exist; false otherwise
func exists(nm string) bool {
if _, err := os.Stat(nm); err == nil {
return true
}
return false
}
// This will be filled in by "build"
var RepoVersion string = "UNDEFINED"
var ProductVersion string = "UNDEFINED"
// vim: ft=go:sw=8:ts=8:noexpandtab:tw=98:

View file

@ -1,96 +0,0 @@
// verify.go -- Verify signatures
//
// (c) 2016 Sudhi Herle <sudhi@herle.net>
//
// Licensing Terms: GPLv2
//
// If you need a commercial license for this work, please contact
// the author.
//
// This software does not come with any express or implied
// warranty; it is provided "as is". No claim is made to its
// suitability for any purpose.
package main
import (
"fmt"
"os"
"git.rgst.io/homelab/sigtool/v3/sign"
flag "github.com/opencoff/pflag"
)
func verify(args []string) {
var help, quiet bool
fs := flag.NewFlagSet("verify", flag.ExitOnError)
fs.BoolVarP(&help, "help", "h", false, "Show this help and exit")
fs.BoolVarP(&quiet, "quiet", "q", false, "Don't show any output; exit with status code only")
fs.Parse(args)
if help {
fs.SetOutput(os.Stdout)
fmt.Printf(`%s verify|v [options] pubkey sig file
Verify an Ed25519 signature in SIG of FILE using a public key PUBKEY.
The pubkey can be one of:
- a file: either OpenSSH ed25519 pubkey or a sigtool pubkey
- a string: the raw OpenSSH or sigtool pubkey
%s will first parse it as a string before trying to parse it as a file.
Options:
`, Z, Z)
fs.PrintDefaults()
os.Exit(0)
}
args = fs.Args()
if len(args) < 3 {
Die("Insufficient arguments to 'verify'. Try '%s verify -h' ..", Z)
}
pn := args[0]
sn := args[1]
fn := args[2]
// We first try to read the public key as a base64/openssh string
pk, err := sign.MakePublicKeyFromString(pn)
if err != nil {
pk, err = sign.ReadPublicKey(pn)
if err != nil {
Die("%s", err)
}
}
sig, err := sign.ReadSignature(sn)
if err != nil {
Die("Can't read signature '%s': %s", sn, err)
}
if !sig.IsPKMatch(pk) {
Die("Wrong public key '%s' for verifying '%s'", pn, sn)
}
ok, err := pk.VerifyFile(fn, sig)
if err != nil {
Die("%s", err)
}
exit := 0
if !ok {
exit = 1
}
if !quiet {
if ok {
fmt.Printf("%s: Signature %s verified\n", fn, sn)
} else {
fmt.Printf("%s: Signature %s verification failure\n", fn, sn)
}
}
os.Exit(exit)
}

View file

@ -1,44 +1,19 @@
#! /usr/bin/env bash
# simple round-trip tests to verify the tool
# Usage:
# $0 [bin=/path/to/sigtool] [tmpdir=/path/to/workdir]
# simple round-trip tests to verify the tool
arch=`./build --print-arch`
bin=./bin/$arch/sigtool
Z=`basename $0`
die() {
echo "$Z: $@" 1>&2
echo "$Z: Test output in $tmpdir .." 1>&2
exit 1
}
# cmd line args processing
for a in $*; do
key=${a%=*}
val=${a#*=}
case $key in
bin)
bin=$val
;;
tmpdir)
tmpdir=$val
;;
*)
echo "Ignoring $key .."
;;
esac
done
if [ -z "$bin" ]; then
arch=`./build --print-arch`
bin=./bin/$arch/sigtool
[ -x $bin ] || ./build || die "can't find & build sigtool"
fi
[ -z "$tmpdir" ] && tmpdir=/tmp/sigtool$$
mkdir -p $tmpdir || die "can't mkdir $tmpdir"
[ -x $bin ] || ./build || die "Can't build sigtool for $arch"
# env name for reading the password
passenv=FOO
@ -46,13 +21,16 @@ passenv=FOO
# this is the password for SKs
FOO=bar
# basename of keyfile
tmpdir=/tmp/sigtool$$
mkdir -p $tmpdir || die "can't mkdir $tmpdir"
#trap "rm -rf $tmpdir" EXIT
bn=$tmpdir/foo
sig=$tmpdir/$Z.sig
pk=$bn.pub
sk=$bn.key
sig=$tmpdir/$Z.sig
bn2=$tmpdir/bar
pk2=$bn2.pub
sk2=$bn2.key
@ -63,49 +41,16 @@ decout=$tmpdir/$Z.dec
# exit on any failure
set -e
# Now try with ssh ed25519 keys
keygen=`which ssh-keygen`
[ -z "$keygen" ] && die "can't find ssh-keygen"
ssk1=$tmpdir/ssk1
spk1=$ssk1.pub
ssk2=$tmpdir/ssk2
spk2=$ssk2.pub
# first generate two ssh keys
$keygen -q -C 'ssk1@foo' -t ed25519 -f $ssk1 -N ""
$keygen -q -C 'ssk2@foo' -t ed25519 -f $ssk2 -N ""
# extract the pk string
spk1_str=$(cat $spk1 | awk '{ print $2 }')
$bin s --no-password $ssk1 -o $sig $0 || die "can't sign with $ssk1"
$bin v -q $spk1 $sig $0 || die "can't verify with $spk2"
$bin v -q $spk1_str $sig $0 || die "can't verify with $spk2_str"
$bin e --no-password -o $encout $spk2 $0 || die "can't encrypt to $spk2 with $ssk1"
$bin d --no-password -o $decout $ssk2 $encout || die "can't decrypt with $ssk2"
# cleanup state
rm -f $sig $encout $decout
# generate keys
$bin g -E FOO $bn || die "can't gen keypair $pk, $sk"
$bin g -E FOO $bn 2>/dev/null && die "overwrote prev keypair"
$bin g -E FOO $bn || die "can't gen keypair $pk, $sk"
$bin g -E FOO $bn && die "overwrote prev keypair"
$bin g -E FOO --overwrite $bn || die "can't force gen keypair $pk, $sk"
$bin g -E FOO $bn2 || die "can't force gen keypair $pk2, $sk2"
# extract pk string
pk_str=$(cat $pk | grep 'pk:' | sed -e 's/^pk: //g')
pk2_str=$(cat $pk2 | grep 'pk:' | sed -e 's/^pk: //g')
$bin g -E FOO $bn2 || die "can't force gen keypair $pk2, $sk2"
# sign and verify
$bin s -E FOO $sk $0 -o $sig || die "can't sign $0"
$bin v -q $pk $sig $0 || die "can't verify signature of $0"
$bin v -q $pk_str $sig $0 || die "can't verify signature of $0"
$bin v -q $pk2 $sig $0 2>/dev/null && die "bad verification with wrong $pk2"
$bin v -q $pk2_str $sig $0 2>/dev/null && die "bad verification with wrong $pk2"
$bin s -E FOO $sk $0 -o $sig || die "can't sign $0"
$bin v -q $pk $sig $0 || die "can't verify signature of $0"
$bin v -q $pk2 $sig $0 && die "bad verification with wrong $pk2"
# encrypt/decrypt
$bin e -E FOO -o $encout $pk2 $0 || die "can't encrypt to $pk2"
@ -115,10 +60,8 @@ cmp -s $decout $0 || die "decrypted file mismatch with $0"
# now with sender verification
$bin e -E FOO --overwrite -o $encout -s $sk $pk2 $0 || die "can't sender-encrypt to $pk2"
$bin d -E FOO --overwrite -o $decout -v $pk $sk2 $encout || die "can't decrypt with $sk2"
cmp -s $decout $0 || die "decrypted file mismatch with $0"
cmp -s $decout $0 || die "decrypted file mismatch with $0"
# Only delete if everything worked
echo "$Z: All tests pass!"
rm -rf $tmpdir
# vim: tw=100 sw=4 ts=4 expandtab