Compare commits
No commits in common. "master" and "v2.1.2" have entirely different histories.
28 changed files with 1707 additions and 2031 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -31,4 +31,4 @@ sigtool
|
||||||
*.pub
|
*.pub
|
||||||
*.key
|
*.key
|
||||||
*.sig
|
*.sig
|
||||||
releases/*
|
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
[tools]
|
|
||||||
golang = "1.24"
|
|
40
README.md
40
README.md
|
@ -40,7 +40,7 @@ You need two things:
|
||||||
|
|
||||||
Next, build sigtool:
|
Next, build sigtool:
|
||||||
|
|
||||||
git clone https://git.rgst.io/homelab/sigtool/v3
|
git clone https://github.com/opencoff/sigtool
|
||||||
cd sigtool
|
cd sigtool
|
||||||
make
|
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
|
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
|
Note that signing and verifying can also work with OpenSSH ed25519
|
||||||
keys.
|
keys.
|
||||||
|
|
||||||
|
@ -156,32 +152,19 @@ recipient can decrypt using their private key.
|
||||||
|
|
||||||
### How is the file encryption done?
|
### How is the file encryption done?
|
||||||
The file encryption uses AES-GCM-256 in AEAD mode. The encryption uses
|
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
|
a random 32-byte AES-256 key. This key is mixed in with the header checksum
|
||||||
HKDF-SHA256 into:
|
as a safeguard to protect the header against accidental or malicious corruption.
|
||||||
|
|
||||||
- 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.
|
|
||||||
|
|
||||||
The input is broken into chunks and each chunk is individually AEAD encrypted.
|
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
|
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
|
its own nonce from a global salt. The nonce is calculated as follows:
|
||||||
actual chunk-length and EOF marker is used as additional data (the
|
|
||||||
"AD" of "AEAD").
|
- 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
|
The last block has its most-signficant-bit set to 1 to denote EOF. Thus, the
|
||||||
maximum chunk size is set to 1GB.
|
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?
|
### What is the public-key cryptography in sigtool?
|
||||||
`sigtool` uses ephemeral Curve25519 keys to generate shared secrets
|
`sigtool` uses ephemeral Curve25519 keys to generate shared secrets
|
||||||
between pairs of sender & one or more recipients. This pairwise shared
|
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.
|
individual encrypted key blob - that **only** they can decrypt.
|
||||||
|
|
||||||
If the sender authenticates the encryption by providing their secret
|
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
|
is encrypted (using the data-encryption key) and stored in the
|
||||||
header. If the sender opts to not authenticate, a "signature" of all
|
header. If the sender opts to not authenticate, a "signature" of all
|
||||||
zeroes is encrypted instead.
|
zeroes is encrypted instead.
|
||||||
|
@ -220,7 +203,7 @@ described as a protobuf file (sign/hdr.proto):
|
||||||
uint32 chunk_size = 1;
|
uint32 chunk_size = 1;
|
||||||
bytes salt = 2;
|
bytes salt = 2;
|
||||||
bytes pk = 3; // sender's ephemeral curve PK
|
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;
|
repeated wrapped_key keys = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,7 +213,6 @@ described as a protobuf file (sign/hdr.proto):
|
||||||
*/
|
*/
|
||||||
message wrapped_key {
|
message wrapped_key {
|
||||||
bytes d_key = 1;
|
bytes d_key = 1;
|
||||||
bytes nonce = 2;
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
256
build
256
build
|
@ -4,7 +4,7 @@
|
||||||
#
|
#
|
||||||
# - it tacks on a version number for use by the individual tools
|
# - it tacks on a version number for use by the individual tools
|
||||||
# - it supports git and mercurial version#
|
# - it supports git and mercurial version#
|
||||||
#
|
#
|
||||||
# NB:
|
# NB:
|
||||||
# o the attempt at decoding dirty repo state for mercurial is
|
# o the attempt at decoding dirty repo state for mercurial is
|
||||||
# borked. It doesn't know about untracked files
|
# borked. It doesn't know about untracked files
|
||||||
|
@ -13,13 +13,12 @@
|
||||||
#
|
#
|
||||||
# License: GPLv2
|
# License: GPLv2
|
||||||
#
|
#
|
||||||
Progs="src:sigtool"
|
Progs=".:sigtool"
|
||||||
|
|
||||||
# Relative path to protobuf sources
|
# Relative path to protobuf sources
|
||||||
# e.g. src/foo/a.proto
|
# e.g. src/foo/a.proto
|
||||||
Protobufs="internal/pb/hdr.proto"
|
Protobufs="internal/pb/hdr.proto"
|
||||||
|
|
||||||
#set -x
|
|
||||||
|
|
||||||
# -- DO NOT CHANGE ANYTHING AFTER THIS --
|
# -- DO NOT CHANGE ANYTHING AFTER THIS --
|
||||||
|
|
||||||
|
@ -29,10 +28,10 @@ PWD=`pwd`
|
||||||
Static=0
|
Static=0
|
||||||
Dryrun=0
|
Dryrun=0
|
||||||
Prodver=""
|
Prodver=""
|
||||||
Repover=""
|
|
||||||
Verbose=0
|
Verbose=0
|
||||||
Go=`which go`
|
|
||||||
Bindir=$PWD/bin
|
hostos=$(go env GOHOSTOS) || exit 1
|
||||||
|
hostcpu=$(go env GOHOSTARCH) || exit 1
|
||||||
|
|
||||||
die() {
|
die() {
|
||||||
echo "$Z: $@" 1>&2
|
echo "$Z: $@" 1>&2
|
||||||
|
@ -50,56 +49,47 @@ case $BASH_VERSION in
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
getvcs_version() {
|
|
||||||
local rev=
|
|
||||||
local prodv=
|
|
||||||
local git=`which git`
|
|
||||||
local hg=`which hg`
|
|
||||||
|
|
||||||
if [ -n "$git" ]; then
|
# build a tool that runs on the host - if needed.
|
||||||
local xrev=$(git describe --always --dirty --long --abbrev=12) || exit 1
|
hosttool() {
|
||||||
rev="git:$xrev"
|
local tool=$1
|
||||||
prodv=$(git tag --list | sort -V | tail -1)
|
local bindir=$2
|
||||||
elif [ -n "$hg" ]; then
|
local src=$3
|
||||||
local xrev=$(hg id --id) || exit 1
|
|
||||||
local brev=${xrev%+}
|
local p=$(type -P $tool)
|
||||||
if [ "$brev" != "$xrev" ]; then
|
if [ -n "$p" ]; then
|
||||||
rev="hg:${brev}-dirty"
|
echo $p
|
||||||
else
|
return 0
|
||||||
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"
|
|
||||||
fi
|
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
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
read -r Repover Prodver <<< $(getvcs_version)
|
|
||||||
|
|
||||||
|
|
||||||
usage() {
|
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
|
cat <<EOF
|
||||||
$0 - A Go production build tool that adds git-repository information,
|
$0 - A Go production build tool that adds git-repository information,
|
||||||
product version, build-timestamp etc. It supports cross-compilation,
|
product version, build-timestamp etc. It supports cross-compilation,
|
||||||
static linking and generating protobuf output.
|
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.
|
Build output is in bin/\$OS-\$CPU for a given OS, CPU combination.
|
||||||
|
|
||||||
Usage: $0
|
Usage: $0
|
||||||
|
@ -108,14 +98,13 @@ Usage: $0
|
||||||
Where OS-ARCH denotes one of the valid OS, ARCH combinations supported by 'go'.
|
Where OS-ARCH denotes one of the valid OS, ARCH combinations supported by 'go'.
|
||||||
And, PROGS is one or more go programs.
|
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
|
The repository's latest tag is used as the default version of the software being
|
||||||
built. The current repository version is $Repover.
|
built.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-h, --help Show this help message and quit
|
-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]
|
-s, --static Build a statically linked binary [False]
|
||||||
-V N, --version=N Use 'N' as the product version string [$Prodver]
|
-V N, --version=N Use 'N' as the product version string [$Prodver]
|
||||||
-a X, --arch=X Cross compile for OS-CPU 'X' [$hostos-$hostcpu]
|
-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]
|
-t, --test Run "go test" on modules named on the command line [False]
|
||||||
-v, --verbose Build verbosely (adds "-v" to go tooling) [False]
|
-v, --verbose Build verbosely (adds "-v" to go tooling) [False]
|
||||||
--vet Run "go vet" on modules named on the command line [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]
|
-x Run in debug/trace mode [False]
|
||||||
--print-arch Print the target architecture and exit
|
--print-arch Print the target architecture and exit
|
||||||
EOF
|
EOF
|
||||||
|
@ -133,6 +120,7 @@ EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
host=`uname|tr '[A-Z]' '[a-z]'`
|
host=`uname|tr '[A-Z]' '[a-z]'`
|
||||||
|
export GO15VENDOREXPERIMENT=1
|
||||||
|
|
||||||
declare -A oses
|
declare -A oses
|
||||||
declare -A cpus
|
declare -A cpus
|
||||||
|
@ -193,23 +181,13 @@ do
|
||||||
--arch=*)
|
--arch=*)
|
||||||
Arch=$ac_optarg
|
Arch=$ac_optarg
|
||||||
;;
|
;;
|
||||||
|
|
||||||
-a|--arch)
|
-a|--arch)
|
||||||
ac_prev=Arch
|
ac_prev=Arch
|
||||||
;;
|
;;
|
||||||
|
|
||||||
-b|--bindir)
|
|
||||||
ac_prev=Bindir
|
|
||||||
;;
|
|
||||||
|
|
||||||
--bindir=*)
|
|
||||||
Bindir=$ac_optarg
|
|
||||||
;;
|
|
||||||
|
|
||||||
--version=*)
|
--version=*)
|
||||||
Prodver=$ac_optarg
|
Prodver=$ac_optarg
|
||||||
;;
|
;;
|
||||||
|
|
||||||
--test|-t)
|
--test|-t)
|
||||||
Tool=test
|
Tool=test
|
||||||
;;
|
;;
|
||||||
|
@ -218,14 +196,9 @@ do
|
||||||
Tool=vet
|
Tool=vet
|
||||||
;;
|
;;
|
||||||
|
|
||||||
--mod)
|
|
||||||
Tool=mod
|
|
||||||
;;
|
|
||||||
|
|
||||||
-V|--version)
|
-V|--version)
|
||||||
ac_prev=Prodver
|
ac_prev=Prodver
|
||||||
;;
|
;;
|
||||||
|
|
||||||
-v|--verbose)
|
-v|--verbose)
|
||||||
Verbose=1
|
Verbose=1
|
||||||
;;
|
;;
|
||||||
|
@ -242,10 +215,6 @@ do
|
||||||
set -x
|
set -x
|
||||||
;;
|
;;
|
||||||
|
|
||||||
--go-root=*)
|
|
||||||
GoRoot=$ac_optarg
|
|
||||||
;;
|
|
||||||
|
|
||||||
--print-arch)
|
--print-arch)
|
||||||
Printarch=1
|
Printarch=1
|
||||||
;;
|
;;
|
||||||
|
@ -261,81 +230,11 @@ do
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
[ $Dryrun -gt 0 ] && e=echo
|
[ $Dryrun -gt 0 ] && e=echo
|
||||||
|
|
||||||
# let every error abort
|
# let every error abort
|
||||||
set -e
|
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
|
# This fragment can't be in a function - since it exports several vars
|
||||||
if [ -n "$Arch" ]; then
|
if [ -n "$Arch" ]; then
|
||||||
ox=${Arch%%-*}
|
ox=${Arch%%-*}
|
||||||
|
@ -387,25 +286,72 @@ fi
|
||||||
|
|
||||||
|
|
||||||
# This is where build outputs go
|
# This is where build outputs go
|
||||||
Outdir=$Bindir/$cross
|
Bindir=$PWD/bin/$cross
|
||||||
Hostbindir=$Bindir/$hostos-$hostcpu
|
Hostbindir=$PWD/bin/$hostos-$hostcpu
|
||||||
export PATH=$Hostbindir:$PATH
|
|
||||||
|
|
||||||
[ -d $Outdir ] || mkdir -p $Outdir
|
[ -d $Bindir ] || mkdir -p $Bindir
|
||||||
[ -d $Hostbindir ] || mkdir -p $Hostbindir
|
[ -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
|
# Do Protobufs if needed
|
||||||
if [ -n "$Protobufs" ]; then
|
if [ -n "$Protobufs" ]; then
|
||||||
set +e
|
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
|
set -e
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Get git/hg version info for the build
|
repover="main.RepoVersion=$rev"
|
||||||
repover="main.RepoVersion=$Repover"
|
|
||||||
prodver="main.ProductVersion=$Prodver"
|
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=""
|
vflag=""
|
||||||
|
|
||||||
[ $Verbose -gt 0 ] && vflag="-v"
|
[ $Verbose -gt 0 ] && vflag="-v"
|
||||||
|
@ -413,17 +359,12 @@ vflag=""
|
||||||
case $Tool in
|
case $Tool in
|
||||||
test)
|
test)
|
||||||
set -- $args
|
set -- $args
|
||||||
$e $Go test $vflag "$@"
|
$e go test $vflag "$@"
|
||||||
;;
|
;;
|
||||||
|
|
||||||
vet)
|
vet)
|
||||||
set -- $args
|
set -- $args
|
||||||
$e $Go vet $vflag "$@"
|
$e go vet $vflag "$@"
|
||||||
;;
|
|
||||||
|
|
||||||
mod)
|
|
||||||
set -- $args
|
|
||||||
$e $Go mod $vflag "$@"
|
|
||||||
;;
|
;;
|
||||||
|
|
||||||
*) # Default is to build programs
|
*) # Default is to build programs
|
||||||
|
@ -434,11 +375,9 @@ case $Tool in
|
||||||
all="$@"
|
all="$@"
|
||||||
fi
|
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
|
if echo $p | grep -q ':' ; then
|
||||||
out=${p##*:}
|
out=${p##*:}
|
||||||
dir=${p%%:*}
|
dir=${p%%:*}
|
||||||
|
@ -446,15 +385,8 @@ case $Tool in
|
||||||
out=$p
|
out=$p
|
||||||
dir=$p
|
dir=$p
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Add .exe suffix to out if needed
|
|
||||||
if [ "$GOOS" = "windows" ]; then
|
|
||||||
base=${out%%.exe}
|
|
||||||
out="${base}.exe"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo " $dir: $out .. "
|
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
|
done
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
|
@ -20,10 +20,9 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.rgst.io/homelab/sigtool/v3/sign"
|
|
||||||
"github.com/opencoff/go-fio"
|
|
||||||
"github.com/opencoff/go-utils"
|
"github.com/opencoff/go-utils"
|
||||||
flag "github.com/opencoff/pflag"
|
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]
|
// 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 outfile string
|
||||||
var keyfile string
|
var keyfile string
|
||||||
var szstr string = "128k"
|
|
||||||
var envpw string
|
var envpw string
|
||||||
var nopw, force bool
|
var nopw, force bool
|
||||||
var blksize uint64
|
var blksize uint64
|
||||||
|
@ -45,7 +43,7 @@ func encrypt(args []string) {
|
||||||
fs.StringVarP(&keyfile, "sign", "s", "", "Sign using private key `S`")
|
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.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(&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")
|
fs.BoolVarP(&force, "overwrite", "", false, "Overwrite the output file if it exists")
|
||||||
|
|
||||||
err := fs.Parse(args)
|
err := fs.Parse(args)
|
||||||
|
@ -53,10 +51,6 @@ func encrypt(args []string) {
|
||||||
Die("%s", err)
|
Die("%s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if blksize, err = utils.ParseSize(szstr); err != nil {
|
|
||||||
Die("%s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var pws, infile string
|
var pws, infile string
|
||||||
var sk *sign.PrivateKey
|
var sk *sign.PrivateKey
|
||||||
|
|
||||||
|
@ -141,11 +135,7 @@ func encrypt(args []string) {
|
||||||
mode = ist.Mode()
|
mode = ist.Mode()
|
||||||
}
|
}
|
||||||
|
|
||||||
var opts uint32
|
sf, err := sign.NewSafeFile(outfile, force, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode)
|
||||||
if force {
|
|
||||||
opts |= fio.OPT_OVERWRITE
|
|
||||||
}
|
|
||||||
sf, err := fio.NewSafeFile(outfile, opts, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Die("%s", err)
|
Die("%s", err)
|
||||||
}
|
}
|
||||||
|
@ -307,11 +297,7 @@ func decrypt(args []string) {
|
||||||
mode = ist.Mode()
|
mode = ist.Mode()
|
||||||
}
|
}
|
||||||
|
|
||||||
var opts uint32
|
sf, err := sign.NewSafeFile(outfile, force, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode)
|
||||||
if force {
|
|
||||||
opts |= fio.OPT_OVERWRITE
|
|
||||||
}
|
|
||||||
sf, err := fio.NewSafeFile(outfile, opts, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Die("%s", err)
|
Die("%s", err)
|
||||||
}
|
}
|
26
go.mod
26
go.mod
|
@ -1,25 +1,17 @@
|
||||||
module git.rgst.io/homelab/sigtool/v3
|
module github.com/opencoff/sigtool
|
||||||
|
|
||||||
go 1.24.0
|
go 1.18
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a
|
github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a
|
||||||
github.com/opencoff/go-fio v0.5.14
|
github.com/gogo/protobuf v1.3.2
|
||||||
github.com/opencoff/go-mmap v0.1.5
|
github.com/opencoff/go-utils v0.4.1
|
||||||
github.com/opencoff/go-utils v1.0.2
|
github.com/opencoff/pflag v0.5.0
|
||||||
github.com/opencoff/pflag v1.0.7
|
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd
|
||||||
github.com/planetscale/vtprotobuf v0.6.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
golang.org/x/crypto v0.36.0
|
|
||||||
google.golang.org/protobuf v1.36.5
|
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/pkg/xattr v0.4.10 // indirect
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect
|
||||||
golang.org/x/sys v0.31.0 // indirect
|
|
||||||
golang.org/x/term v0.30.0 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//replace github.com/opencoff/go-mmap => ../go-mmap
|
|
||||||
//replace github.com/opencoff/go-utils => ../go-utils
|
|
||||||
|
|
78
go.sum
78
go.sum
|
@ -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 h1:saTgr5tMLFnmy/yg3qDTft4rE5DY2uJ/cCxCe3q0XTU=
|
||||||
github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a/go.mod h1:Bw9BbhOJVNR+t0jCqx2GC6zv0TGBsShs56Y3gfSCvl0=
|
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/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
github.com/opencoff/go-fio v0.5.14 h1:PGi4XLLO4RSuc3m5exY0G2vweov6w3UThhScehBfM8c=
|
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
github.com/opencoff/go-fio v0.5.14/go.mod h1:hoSySYpavRnfQUsxzUgadk31kYiNQhMDvA2MObsXKf8=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/opencoff/go-mmap v0.1.5 h1:RKPtevC4mOW5bi9skBPPo4nFTIH4lVWAL20Tff+FjLg=
|
github.com/opencoff/go-utils v0.4.1 h1:Ke4Q1Tl2GKMI+dwleuPNHH713ngRiNMOFIkymncHqXg=
|
||||||
github.com/opencoff/go-mmap v0.1.5/go.mod h1:y/6Jk/tDUc00k3oSQpiJX++20Nw7xFSlc5kLkhGnRXw=
|
github.com/opencoff/go-utils v0.4.1/go.mod h1:c+7QUAiCCHcNH6OGvsZ0fviG7cgse8Y3ucg+xy7sGXM=
|
||||||
github.com/opencoff/go-utils v1.0.2 h1:BANRL8ZxgHpuo8gQBAzT3M9Im3aNFhaWW28jhc86LNs=
|
github.com/opencoff/pflag v0.5.0 h1:kK3cSTlGj0fHby/PoFzHkf+Jx3PdiACJwzYDWEWlEKQ=
|
||||||
github.com/opencoff/go-utils v1.0.2/go.mod h1:eZkEVQVzNfuE8uGepyhscMsqcXq7liGbBHYYwgYaoy8=
|
github.com/opencoff/pflag v0.5.0/go.mod h1:mTLzGGUGda1Av3d34iAJlh0JIlRxmFZtmc6qoWPspK0=
|
||||||
github.com/opencoff/pflag v1.0.7 h1:o5cQIuX75bDcdJ6AXl68gzpA72a3CJ2MPStaMnEuwi4=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/opencoff/pflag v1.0.7/go.mod h1:2bXtpAD/5h/2LarkbsRwiUxqnvB1nZBzn9Xjad1P41A=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/pkg/xattr v0.4.10 h1:Qe0mtiNFHQZ296vRgUjRCoPHPqH7VdTOrZx3g0T+pGA=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
github.com/pkg/xattr v0.4.10/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU=
|
golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
github.com/planetscale/vtprotobuf v0.6.0 h1:nBeETjudeJ5ZgBHUz1fVHvbqUKnYOXNhsIEabROxmNA=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
github.com/planetscale/vtprotobuf v0.6.0/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=
|
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd h1:XcWmESyNjXJMLahc3mqVQJcgSTDxFxhETVlfk9uGc38=
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
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=
|
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=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
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/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.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,8 +1,13 @@
|
||||||
syntax="proto3";
|
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
|
* Every encrypted file starts with a header describing the
|
||||||
|
@ -11,11 +16,11 @@ option go_package = "internal/pb";
|
||||||
* protobuf format before writing to disk.
|
* protobuf format before writing to disk.
|
||||||
*/
|
*/
|
||||||
message header {
|
message header {
|
||||||
uint32 chunk_size = 1; // encryption block size
|
uint32 chunk_size = 1; // encryption block size
|
||||||
bytes salt = 2; // master salt (nonces are derived from this)
|
bytes salt = 2; // master salt (nonces are derived from this)
|
||||||
bytes pk = 3; // ephemeral curve PK
|
bytes pk = 3; // ephemeral curve PK
|
||||||
bytes sender = 4; // sender signed artifacts
|
bytes sender_sign = 4; // signature block of sender
|
||||||
repeated wrapped_key keys = 5; // list of wrapped receiver blocks
|
repeated wrapped_key keys = 5; // list of wrapped receiver blocks
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -23,7 +28,5 @@ message header {
|
||||||
* key. WrappedKey describes such a wrapped key.
|
* key. WrappedKey describes such a wrapped key.
|
||||||
*/
|
*/
|
||||||
message wrapped_key {
|
message wrapped_key {
|
||||||
bytes d_key = 1; // encrypted data key
|
bytes d_key = 1; // encrypted data key
|
||||||
bytes nonce = 2; // nonce used for encryption
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
54
mk-rel.sh
54
mk-rel.sh
|
@ -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
|
|
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
|
|
||||||
}
|
|
|
@ -1,10 +1,10 @@
|
||||||
[](https://godoc.org/git.rgst.io/homelab/sigtool/v3/sign)
|
[](https://godoc.org/github.com/opencoff/sigtool/sign)
|
||||||
|
|
||||||
# sigtool/sign - Ed25519 signature calculation and verification
|
# sigtool/sign - Ed25519 signature calculation and verification
|
||||||
|
|
||||||
This is a small library that makes it easier to create and serialize Ed25519 keys, and sign,
|
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.
|
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
|
## License
|
||||||
GPL v2.0
|
GPL v2.0
|
||||||
|
|
561
sign/encrypt.go
561
sign/encrypt.go
|
@ -26,36 +26,29 @@
|
||||||
//
|
//
|
||||||
// Variable Length Segment:
|
// Variable Length Segment:
|
||||||
// - Protobuf encoded, per-recipient wrapped keys
|
// - Protobuf encoded, per-recipient wrapped keys
|
||||||
|
// - Shasum: 32 bytes (SHA256 of full header)
|
||||||
//
|
//
|
||||||
// The variable length segment consists of one or more
|
// 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:
|
// The input data is encrypted with an expanded random 32-byte key:
|
||||||
// - hkdf-sha512 of random key, salt, context
|
// - Prefix_string = "Encrypt Nonce"
|
||||||
// - the hkdf process yields a data-encryption key, nonce and hmac key.
|
// - datakey = SHA256(Prefix_string || header_checksum || random_key)
|
||||||
// - we use the header checksum as the 'salt' for HKDF; this ensures that
|
// - The header checksum is mixed in the above process to ensure we
|
||||||
// any modification of the header yields different keys
|
// catch any malicious modification of the header.
|
||||||
//
|
|
||||||
// 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).
|
|
||||||
//
|
//
|
||||||
// The input data is broken up into "chunks"; each no larger than
|
// The input data is broken up into "chunks"; each no larger than
|
||||||
// maxChunkSize. The default block size is "chunkSize". Each block
|
// maxChunkSize. The default block size is "chunkSize". Each block
|
||||||
// is AEAD encrypted:
|
// is AEAD encrypted:
|
||||||
// AEAD nonce = header.nonce || block#
|
// AEAD nonce = header.salt || block# || block-size
|
||||||
// AD of AEAD = chunk length+eof marker
|
|
||||||
//
|
//
|
||||||
// The encrypted block (includes the AEAD tag) length is written
|
// The encrypted block (includes the AEAD tag) length is written
|
||||||
// as a big-endian 4-byte prefix. The high-order bit of this length
|
// as a big-endian 4-byte prefix. The high-order bit of this length
|
||||||
// field is set for the last-block (denoting EOF).
|
// field is set for the last-block (denoting EOF).
|
||||||
//
|
//
|
||||||
|
// The encrypted blocks use an opinionated nonce length of 32 (_AEADNonceLen).
|
||||||
|
|
||||||
package sign
|
package sign
|
||||||
|
|
||||||
|
@ -64,65 +57,50 @@ import (
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
"crypto/hmac"
|
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/curve25519"
|
"golang.org/x/crypto/curve25519"
|
||||||
"golang.org/x/crypto/hkdf"
|
"golang.org/x/crypto/hkdf"
|
||||||
|
"io"
|
||||||
|
|
||||||
"git.rgst.io/homelab/sigtool/v3/internal/pb"
|
"github.com/opencoff/sigtool/internal/pb"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Encryption chunk size = 4MB
|
// Encryption chunk size = 4MB
|
||||||
const (
|
const (
|
||||||
// The latest version of the tool's output file format
|
chunkSize uint32 = 4 * 1048576
|
||||||
_SigtoolVersion = 3
|
|
||||||
|
|
||||||
chunkSize uint32 = 4 * 1048576 // 4 MB
|
|
||||||
maxChunkSize uint32 = 1 << 30
|
maxChunkSize uint32 = 1 << 30
|
||||||
_EOF uint32 = 1 << 31
|
_EOF uint32 = 1 << 31
|
||||||
|
|
||||||
_Magic = "SigTool"
|
_Magic = "SigTool"
|
||||||
_MagicLen = len(_Magic)
|
_MagicLen = len(_Magic)
|
||||||
_FixedHdrLen = _MagicLen + 1 + 4 // 1: version, 4: len of variable segment
|
_SigtoolVersion = 2
|
||||||
|
_AEADNonceLen = 32
|
||||||
|
_FixedHdrLen = _MagicLen + 1 + 4
|
||||||
|
|
||||||
_AesKeySize = 32
|
_WrapReceiverNonce = "Receiver Key Nonce"
|
||||||
_AEADNonceSize = 12
|
_WrapSenderNonce = "Sender Sig Nonce"
|
||||||
_SaltSize = 32
|
_EncryptNonce = "Encrypt Nonce"
|
||||||
_RxNonceSize = 12 // nonce size of per-recipient encrypted blocks
|
|
||||||
|
|
||||||
_WrapReceiver = "Receiver Key"
|
|
||||||
_WrapSender = "Sender Sig"
|
|
||||||
_DataKeyExpansion = "Data Key Expansion"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Encryptor holds the encryption context
|
// Encryptor holds the encryption context
|
||||||
type Encryptor struct {
|
type Encryptor struct {
|
||||||
pb.Header
|
pb.Header
|
||||||
key []byte // root key
|
key []byte // file encryption key
|
||||||
|
|
||||||
nonce []byte // nonce for the data encrypting cipher
|
ae cipher.AEAD
|
||||||
buf []byte // I/O buf (chunk-sized)
|
|
||||||
|
|
||||||
ae cipher.AEAD
|
|
||||||
hmac hash.Hash
|
|
||||||
|
|
||||||
// ephemeral key
|
// ephemeral key
|
||||||
encSK []byte
|
encSK []byte
|
||||||
|
|
||||||
// sender identity
|
|
||||||
sender *PrivateKey
|
|
||||||
|
|
||||||
auth bool // set if the sender idetity is sent
|
|
||||||
started bool
|
started bool
|
||||||
stream bool
|
|
||||||
|
hdrsum []byte
|
||||||
|
buf []byte
|
||||||
|
stream bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new Encryption context for encrypting blocks of size 'blksize'.
|
// 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)
|
return nil, fmt.Errorf("encrypt: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
key := randBuf(_AesKeySize)
|
key := make([]byte, 32)
|
||||||
salt := randBuf(_SaltSize)
|
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{
|
e := &Encryptor{
|
||||||
Header: pb.Header{
|
Header: pb.Header{
|
||||||
ChunkSize: blksz,
|
ChunkSize: blksz,
|
||||||
Salt: salt,
|
Salt: salt,
|
||||||
Pk: epk,
|
Pk: epk,
|
||||||
|
SenderSign: wSig,
|
||||||
},
|
},
|
||||||
|
|
||||||
key: key,
|
key: key,
|
||||||
encSK: esk,
|
encSK: esk,
|
||||||
sender: sk,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = e.addSenderSig(sk); err != nil {
|
|
||||||
return nil, fmt.Errorf("encrypt: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return e, nil
|
return e, nil
|
||||||
|
@ -228,8 +210,8 @@ func (e *Encryptor) start(wr io.Writer) error {
|
||||||
|
|
||||||
buffer := make([]byte, _FixedHdrLen+varSize+sha256.Size)
|
buffer := make([]byte, _FixedHdrLen+varSize+sha256.Size)
|
||||||
fixHdr := buffer[:_FixedHdrLen]
|
fixHdr := buffer[:_FixedHdrLen]
|
||||||
varHdr := buffer[_FixedHdrLen : _FixedHdrLen+varSize]
|
varHdr := buffer[_FixedHdrLen:]
|
||||||
sumHdr := buffer[_FixedHdrLen+varSize:]
|
sumHdr := varHdr[varSize:]
|
||||||
|
|
||||||
// Now assemble the fixed header
|
// Now assemble the fixed header
|
||||||
copy(fixHdr[:], []byte(_Magic))
|
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)
|
return fmt.Errorf("encrypt: can't marshal header: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Now calculate checksum of everything
|
||||||
h := sha256.New()
|
h := sha256.New()
|
||||||
h.Write(buffer[:_FixedHdrLen+varSize])
|
h.Write(buffer[:_FixedHdrLen+varSize])
|
||||||
cksum := h.Sum(sumHdr[:0])
|
h.Sum(sumHdr[:0])
|
||||||
|
|
||||||
// now make the data encryption keys, nonces etc.
|
// Finally write it out
|
||||||
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
|
|
||||||
err = fullwrite(buffer, wr)
|
err = fullwrite(buffer, wr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("encrypt: %w", err)
|
return fmt.Errorf("encrypt: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
e.hmac = hmac.New(sha256.New, hmackey)
|
// we mix the header checksum to create the encryption key
|
||||||
e.buf = make([]byte, e.ChunkSize+4+uint32(e.ae.Overhead()))
|
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
|
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
|
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
|
// This protects the output stream from re-ordering attacks and length
|
||||||
// modification attacks. The encoded length & block number is used as
|
// modification attacks. The encoded length & block number is used as
|
||||||
// additional data in the AEAD construction.
|
// additional data in the AEAD construction.
|
||||||
func (e *Encryptor) encrypt(pt []byte, wr io.Writer, i uint32, eof bool) error {
|
func (e *Encryptor) encrypt(buf []byte, wr io.Writer, i uint32, eof bool) error {
|
||||||
var z uint32 = uint32(len(pt))
|
var z uint32 = uint32(len(buf))
|
||||||
var nonce [_AEADNonceSize]byte
|
var nbuf [_AEADNonceLen]byte
|
||||||
|
|
||||||
// mark last block
|
// mark last block
|
||||||
if eof {
|
if eof {
|
||||||
z |= _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
|
nonce := makeNonceV2(nbuf[:], e.Salt, b)
|
||||||
binary.BigEndian.PutUint32(nonce[:4], i)
|
|
||||||
|
|
||||||
// put the encoded length+eof at the start of the output buf
|
cbuf := e.buf[4:]
|
||||||
b := e.buf[:4]
|
c := e.ae.Seal(cbuf[:0], nonce, buf, b[:])
|
||||||
ctbuf := e.buf[4:]
|
|
||||||
|
|
||||||
binary.BigEndian.PutUint32(b, z)
|
|
||||||
ct := e.ae.Seal(ctbuf[:0], nonce[:], pt, b)
|
|
||||||
|
|
||||||
// total number of bytes written
|
// total number of bytes written
|
||||||
n := len(ct) + 4
|
n := len(c) + 4
|
||||||
err := fullwrite(e.buf[:n], wr)
|
err := fullwrite(e.buf[:n], wr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("encrypt: %w", err)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -360,18 +295,16 @@ func (e *Encryptor) writeTrailer(wr io.Writer) error {
|
||||||
type Decryptor struct {
|
type Decryptor struct {
|
||||||
pb.Header
|
pb.Header
|
||||||
|
|
||||||
ae cipher.AEAD
|
ae cipher.AEAD
|
||||||
hmac hash.Hash
|
rd io.Reader
|
||||||
|
buf []byte
|
||||||
|
hdrsum []byte
|
||||||
|
|
||||||
sender *PublicKey
|
// flag set to true if sender signed the key
|
||||||
|
auth bool
|
||||||
|
|
||||||
rd io.Reader
|
// Decrypted key
|
||||||
buf []byte
|
key []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
|
|
||||||
eof bool
|
eof bool
|
||||||
stream 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)
|
return nil, fmt.Errorf("decrypt: error while reading header: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The checksum in the header
|
|
||||||
verify := varBuf[varSize:]
|
verify := varBuf[varSize:]
|
||||||
|
|
||||||
// the checksum we calculated
|
|
||||||
var csum [sha256.Size]byte
|
|
||||||
|
|
||||||
h := sha256.New()
|
h := sha256.New()
|
||||||
h.Write(b[:])
|
h.Write(b[:])
|
||||||
h.Write(varBuf[:varSize])
|
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
|
return nil, ErrBadHeader
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -443,7 +372,7 @@ func NewDecryptor(rd io.Reader) (*Decryptor, error) {
|
||||||
return nil, fmt.Errorf("decrypt: invalid chunkSize %d", d.ChunkSize)
|
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))
|
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
|
// sanity check on the wrapped keys
|
||||||
for i, w := range d.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)
|
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)
|
return fmt.Errorf("decrypt: can't unwrap key %d: %w", i, err)
|
||||||
}
|
}
|
||||||
if key != nil {
|
if key != nil {
|
||||||
d.key = key
|
|
||||||
d.sender = senderPk
|
|
||||||
goto havekey
|
goto havekey
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -482,37 +409,28 @@ func (d *Decryptor) SetPrivateKey(sk *PrivateKey, senderPk *PublicKey) error {
|
||||||
return ErrBadKey
|
return ErrBadKey
|
||||||
|
|
||||||
havekey:
|
havekey:
|
||||||
if err := d.verifySender(key, senderPk); err != nil {
|
if err := d.verifySender(key, sk, senderPk); err != nil {
|
||||||
return fmt.Errorf("decrypt: %w", err)
|
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
|
aes, err := aes.NewCipher(key)
|
||||||
|
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("decrypt: %w", err)
|
return fmt.Errorf("decrypt: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
d.ae, err = cipher.NewGCM(aes)
|
d.ae, err = cipher.NewGCMWithNonceSize(aes, _AEADNonceLen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("decrypt: %w", err)
|
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())
|
d.buf = make([]byte, int(d.ChunkSize)+d.ae.Overhead())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -559,16 +477,17 @@ func (d *Decryptor) Decrypt(wr io.Writer) error {
|
||||||
|
|
||||||
// Decrypt exactly one chunk of data
|
// Decrypt exactly one chunk of data
|
||||||
func (d *Decryptor) decrypt(i uint32) ([]byte, bool, error) {
|
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 ovh uint32 = uint32(d.ae.Overhead())
|
||||||
var b [4]byte
|
var p []byte
|
||||||
var nonce [_AEADNonceSize]byte
|
|
||||||
|
|
||||||
n, err := io.ReadFull(d.rd, b[:])
|
n, err := io.ReadFull(d.rd, b[:4])
|
||||||
if err != nil || n == 0 {
|
if err != nil || n == 0 {
|
||||||
return nil, false, fmt.Errorf("decrypt: premature EOF while reading header block %d", i)
|
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
|
eof := (m & _EOF) > 0
|
||||||
m &= (_EOF - 1)
|
m &= (_EOF - 1)
|
||||||
|
|
||||||
|
@ -581,14 +500,13 @@ func (d *Decryptor) decrypt(i uint32) ([]byte, bool, error) {
|
||||||
if !eof {
|
if !eof {
|
||||||
return nil, false, fmt.Errorf("decrypt: block %d: zero-sized chunk without EOF", i)
|
return nil, false, fmt.Errorf("decrypt: block %d: zero-sized chunk without EOF", i)
|
||||||
}
|
}
|
||||||
return nil, eof, nil
|
return p, eof, nil
|
||||||
|
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
// make the nonce - top 4 bytes are the counter
|
binary.BigEndian.PutUint32(b[4:], i)
|
||||||
copy(nonce[:], d.nonce)
|
nonce := makeNonceV2(nonceb[:], d.Salt, b[:])
|
||||||
binary.BigEndian.PutUint32(nonce[:4], i)
|
|
||||||
|
|
||||||
z := m + ovh
|
z := m + ovh
|
||||||
n, err = io.ReadFull(d.rd, d.buf[:z])
|
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)
|
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 {
|
if err != nil {
|
||||||
return nil, false, fmt.Errorf("decrypt: can't decrypt chunk %d: %w", i, err)
|
return nil, false, fmt.Errorf("decrypt: can't decrypt chunk %d: %w", i, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if uint32(len(pt)) != m {
|
return p[:m], eof, nil
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Decryptor) processTrailer(pt []byte, eof bool) ([]byte, bool, error) {
|
// Wrap sender's signature of the encryption key
|
||||||
var rd [ed25519.SignatureSize]byte
|
// 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.
|
||||||
_, err := io.ReadFull(d.rd, rd[:])
|
func wrapSenderSig(sk *PrivateKey, key, salt []byte) ([]byte, error) {
|
||||||
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 {
|
|
||||||
var zero [ed25519.SignatureSize]byte
|
var zero [ed25519.SignatureSize]byte
|
||||||
var auth bool
|
var sig []byte
|
||||||
sig := zero[:]
|
|
||||||
|
|
||||||
if e.sender != nil {
|
switch {
|
||||||
var csum [sha256.Size]byte
|
case sk == nil:
|
||||||
|
sig = zero[:]
|
||||||
|
|
||||||
// We capture essential meta-data from the sender; viz:
|
default:
|
||||||
// - Sender tool version
|
xsig, err := sk.SignMessage(key, "")
|
||||||
// - 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, "")
|
|
||||||
if err != nil {
|
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
|
sig = xsig.Sig
|
||||||
auth = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := make([]byte, _AesKeySize+_AEADNonceSize)
|
aes, err := aes.NewCipher(key)
|
||||||
buf = expand(buf, e.key, e.Salt, []byte(_WrapSender))
|
|
||||||
|
|
||||||
ekey, nonce := buf[:_AesKeySize], buf[_AesKeySize:]
|
|
||||||
|
|
||||||
aes, err := aes.NewCipher(ekey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("senderId: %w", err)
|
return nil, fmt.Errorf("wrap: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ae, err := cipher.NewGCM(aes)
|
ae, err := cipher.NewGCM(aes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("senderId: %w", err)
|
return nil, fmt.Errorf("wrap: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
outbuf := make([]byte, ed25519.SignatureSize+ae.Overhead())
|
tagsize := ae.Overhead()
|
||||||
buf = ae.Seal(outbuf[:0], nonce, sig, nil)
|
nonceSize := ae.NonceSize()
|
||||||
|
|
||||||
e.auth = auth
|
nonce := sha256Slices([]byte(_WrapSenderNonce), salt)[:nonceSize]
|
||||||
e.Sender = buf
|
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
|
// unwrap sender's signature using 'key' and extract the signature
|
||||||
// Optionally, verify the signature using the sender's PK (if provided).
|
// Optionally, verify the signature using the sender's PK (if provided).
|
||||||
func (d *Decryptor) verifySender(key []byte, senderPk *PublicKey) error {
|
func (d *Decryptor) verifySender(key []byte, sk *PrivateKey, senderPK *PublicKey) error {
|
||||||
outbuf := make([]byte, _AEADNonceSize+_AesKeySize)
|
aes, err := aes.NewCipher(key)
|
||||||
buf := expand(outbuf, key, d.Salt, []byte(_WrapSender))
|
|
||||||
|
|
||||||
ekey, nonce := buf[:_AesKeySize], buf[_AesKeySize:]
|
|
||||||
|
|
||||||
aes, err := aes.NewCipher(ekey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unwrap: %w", err)
|
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)
|
return fmt.Errorf("unwrap: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var sigbuf [ed25519.SignatureSize]byte
|
nonceSize := ae.NonceSize()
|
||||||
var zero [ed25519.SignatureSize]byte
|
nonce := sha256Slices([]byte(_WrapSenderNonce), d.Salt)[:nonceSize]
|
||||||
|
sig := make([]byte, ed25519.SignatureSize)
|
||||||
sig, err := ae.Open(sigbuf[:0], nonce, d.Sender, nil)
|
sig, err = ae.Open(sig[:0], nonce, d.SenderSign, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unwrap: can't open sender info: %w", err)
|
return fmt.Errorf("unwrap: can't open sender info: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var zero [ed25519.SignatureSize]byte
|
||||||
|
|
||||||
// Did the sender actually sign anything?
|
// Did the sender actually sign anything?
|
||||||
if subtle.ConstantTimeCompare(zero[:], sig) == 0 {
|
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;
|
// we set this to indicate that the sender authenticated themselves;
|
||||||
d.auth = true
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrap data encryption key 'k' with the sender's PK and our ephemeral curve SK
|
// 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) {
|
func (e *Encryptor) wrapKey(pk *PublicKey) (*pb.WrappedKey, error) {
|
||||||
rxPK := pk.ToCurve25519PK()
|
rxPK := pk.ToCurve25519PK()
|
||||||
sekrit, err := curve25519.X25519(e.encSK, rxPK)
|
dkek, err := curve25519.X25519(e.encSK, rxPK)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("wrap: %w", err)
|
return nil, fmt.Errorf("wrap: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var shasum [sha256.Size]byte
|
aes, err := aes.NewCipher(dkek)
|
||||||
|
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("wrap: %w", err)
|
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)
|
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{
|
w := &pb.WrappedKey{
|
||||||
DKey: ae.Seal(ekey[:0], nonce, e.key, pk.Pk),
|
DKey: ae.Seal(ekey[:0], nonceR, e.key, pk.Pk),
|
||||||
Nonce: rbuf,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return w, nil
|
return w, nil
|
||||||
|
@ -800,48 +638,40 @@ func (e *Encryptor) wrapKey(pk *PublicKey) (*pb.WrappedKey, error) {
|
||||||
// senders ephemeral PublicKey
|
// senders ephemeral PublicKey
|
||||||
func (d *Decryptor) unwrapKey(w *pb.WrappedKey, sk *PrivateKey) ([]byte, error) {
|
func (d *Decryptor) unwrapKey(w *pb.WrappedKey, sk *PrivateKey) ([]byte, error) {
|
||||||
ourSK := sk.ToCurve25519SK()
|
ourSK := sk.ToCurve25519SK()
|
||||||
sekrit, err := curve25519.X25519(ourSK, d.Pk)
|
dkek, err := curve25519.X25519(ourSK, d.Pk)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unwrap: %w", err)
|
return nil, fmt.Errorf("unwrap: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var shasum [sha256.Size]byte
|
aes, err := aes.NewCipher(dkek)
|
||||||
|
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("wrap: %w", err)
|
return nil, fmt.Errorf("unwrap: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ae, err := cipher.NewGCM(aes)
|
ae, err := cipher.NewGCM(aes)
|
||||||
if err != nil {
|
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 {
|
if len(w.DKey) != want {
|
||||||
return nil, fmt.Errorf("unwrap: incorrect decrypt bytes (need %d, saw %d)", want, len(w.DKey))
|
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()
|
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
|
// 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 {
|
if err != nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// we have successfully found the correct recipient
|
|
||||||
return dkey, nil
|
return dkey, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -861,14 +691,30 @@ func fullwrite(buf []byte, wr io.Writer) error {
|
||||||
return nil
|
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
|
// generate a KEK from a shared DH key and a Pub Key
|
||||||
func expand(out []byte, shared, salt, ad []byte) []byte {
|
func expand(shared, pk []byte) ([]byte, error) {
|
||||||
h := hkdf.New(sha512.New, shared, salt, ad)
|
kek := make([]byte, 32)
|
||||||
_, err := io.ReadFull(h, out)
|
h := hkdf.New(sha512.New, shared, pk, nil)
|
||||||
if err != nil {
|
_, err := io.ReadFull(h, kek)
|
||||||
panic(fmt.Sprintf("hkdf: failed to generate %d bytes: %s", len(out), err))
|
return kek, err
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSender() (sk, pk []byte, err error) {
|
func newSender() (sk, pk []byte, err error) {
|
||||||
|
@ -890,25 +736,4 @@ func sha256Slices(v ...[]byte) []byte {
|
||||||
return h.Sum(nil)[:]
|
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
|
// EOF
|
||||||
|
|
|
@ -20,19 +20,17 @@ import (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrClosed = errors.New("encrypt: stream already closed")
|
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")
|
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")
|
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")
|
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)")
|
ErrHeaderTooBig = errors.New("decrypt: header too large (max 1048576)")
|
||||||
ErrHeaderTooSmall = errors.New("decrypt: header too small (min 32)")
|
ErrHeaderTooSmall = errors.New("decrypt: header too small (min 32)")
|
||||||
ErrBadHeader = errors.New("decrypt: header corrupted")
|
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")
|
ErrBadKey = errors.New("decrypt: wrong key")
|
||||||
ErrBadTrailer = errors.New("decrypt: message integrity failed (bad trailer)")
|
|
||||||
ErrBadSender = errors.New("unwrap: sender verification failed")
|
ErrBadSender = errors.New("unwrap: sender verification failed")
|
||||||
ErrNoSenderPK = errors.New("unwrap: missing sender public key")
|
|
||||||
|
|
||||||
ErrIncorrectPassword = errors.New("ssh: invalid passphrase")
|
ErrIncorrectPassword = errors.New("ssh: invalid passphrase")
|
||||||
ErrNoPEMFound = errors.New("ssh: no PEM block found")
|
ErrNoPEMFound = errors.New("ssh: no PEM block found")
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
82
sign/keys.go
82
sign/keys.go
|
@ -25,14 +25,18 @@ import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"hash"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"os"
|
||||||
|
|
||||||
Ed "crypto/ed25519"
|
Ed "crypto/ed25519"
|
||||||
|
|
||||||
"golang.org/x/crypto/scrypt"
|
"golang.org/x/crypto/scrypt"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
|
"github.com/opencoff/go-utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Private Ed25519 key
|
// Private Ed25519 key
|
||||||
|
@ -180,14 +184,14 @@ func makePrivateKeyFromBytes(sk *PrivateKey, buf []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
// Make a private key from 64-bytes of extended Ed25519 key
|
// Make a private key from 64-bytes of extended Ed25519 key
|
||||||
func PrivateKeyFromBytes(buf []byte) (*PrivateKey, error) {
|
func PrivateKeyFromBytes(buf []byte) (*PrivateKey, error) {
|
||||||
var sk PrivateKey
|
var sk PrivateKey
|
||||||
if err := makePrivateKeyFromBytes(&sk, buf); err != nil {
|
|
||||||
return nil, err
|
return makePrivateKeyFromBytes(&sk, buf)
|
||||||
}
|
|
||||||
return &sk, nil
|
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
// Given a secret key, return the corresponding Public Key
|
// Given a secret key, return the corresponding Public Key
|
||||||
func (sk *PrivateKey) PublicKey() *PublicKey {
|
func (sk *PrivateKey) PublicKey() *PublicKey {
|
||||||
|
@ -370,29 +374,6 @@ func MakePublicKey(yml []byte) (*PublicKey, error) {
|
||||||
return &pk, nil
|
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 {
|
func makePublicKeyFromBytes(pk *PublicKey, b []byte) error {
|
||||||
if len(b) != 32 {
|
if len(b) != 32 {
|
||||||
return fmt.Errorf("public key is malformed (len %d!)", len(b))
|
return fmt.Errorf("public key is malformed (len %d!)", len(b))
|
||||||
|
@ -405,14 +386,14 @@ func makePublicKeyFromBytes(pk *PublicKey, b []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
// Make a public key from a byte string
|
// Make a public key from a byte string
|
||||||
func PublicKeyFromBytes(b []byte) (*PublicKey, error) {
|
func PublicKeyFromBytes(b []byte) (*PublicKey, error) {
|
||||||
var pk PublicKey
|
var pk PublicKey
|
||||||
if err := makePublicKeyFromBytes(&pk, b); err != nil {
|
|
||||||
return nil, err
|
makePublicKeyFromBytes(&pk, b)
|
||||||
}
|
|
||||||
return &pk, nil
|
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
// Serialize a PublicKey into file 'fn' with a human readable 'comment'.
|
// Serialize a PublicKey into file 'fn' with a human readable 'comment'.
|
||||||
// If 'ovwrite' is true, overwrite the file if it exists.
|
// If 'ovwrite' is true, overwrite the file if it exists.
|
||||||
|
@ -518,6 +499,41 @@ func (pk *PublicKey) UnmarshalBinary(yml []byte) error {
|
||||||
|
|
||||||
// -- Internal Utility Functions --
|
// -- 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 {
|
func clamp(k []byte) []byte {
|
||||||
k[0] &= 248
|
k[0] &= 248
|
||||||
k[31] &= 127
|
k[31] &= 127
|
||||||
|
|
|
@ -38,8 +38,3 @@ func randRead(b []byte) []byte {
|
||||||
}
|
}
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func randBuf(sz int) []byte {
|
|
||||||
b := make([]byte, sz)
|
|
||||||
return randRead(b)
|
|
||||||
}
|
|
||||||
|
|
124
sign/safefile.go
Normal file
124
sign/safefile.go
Normal 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
|
||||||
|
}
|
|
@ -27,8 +27,7 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
||||||
Ed "crypto/ed25519"
|
Ed "crypto/ed25519"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// An Ed25519 Signature
|
// An Ed25519 Signature
|
||||||
|
@ -39,9 +38,8 @@ type Signature struct {
|
||||||
|
|
||||||
// Sign a prehashed Message; return the signature as opaque bytes
|
// Sign a prehashed Message; return the signature as opaque bytes
|
||||||
// Signature is an YAML file:
|
// Signature is an YAML file:
|
||||||
//
|
// Comment: source file path
|
||||||
// Comment: source file path
|
// Signature: Ed25519 signature
|
||||||
// Signature: Ed25519 signature
|
|
||||||
func (sk *PrivateKey) SignMessage(ck []byte, comment string) (*Signature, error) {
|
func (sk *PrivateKey) SignMessage(ck []byte, comment string) (*Signature, error) {
|
||||||
h := sha512.New()
|
h := sha512.New()
|
||||||
h.Write([]byte("sigtool signed message"))
|
h.Write([]byte("sigtool signed message"))
|
||||||
|
|
354
sigtool.go
Normal file
354
sigtool.go
Normal 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:
|
100
src/gen.go
100
src/gen.go
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
116
src/sign.go
116
src/sign.go
|
@ -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()
|
|
||||||
}
|
|
129
src/sigtool.go
129
src/sigtool.go
|
@ -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:
|
|
|
@ -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)
|
|
||||||
}
|
|
73
tests.sh
73
tests.sh
|
@ -1,44 +1,24 @@
|
||||||
#! /usr/bin/env bash
|
#! /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`
|
Z=`basename $0`
|
||||||
|
|
||||||
|
# workdir
|
||||||
|
tmpdir=/tmp/sigtool$$
|
||||||
|
|
||||||
die() {
|
die() {
|
||||||
echo "$Z: $@" 1>&2
|
echo "$Z: $@" 1>&2
|
||||||
echo "$Z: Test output in $tmpdir .." 1>&2
|
echo "$Z: Test output in $tmpdir .." 1>&2
|
||||||
exit 1
|
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"
|
mkdir -p $tmpdir || die "can't mkdir $tmpdir"
|
||||||
|
[ -x $bin ] || ./build || die "Can't build sigtool for $arch"
|
||||||
|
|
||||||
# env name for reading the password
|
# env name for reading the password
|
||||||
passenv=FOO
|
passenv=FOO
|
||||||
|
@ -50,9 +30,9 @@ FOO=bar
|
||||||
#trap "rm -rf $tmpdir" EXIT
|
#trap "rm -rf $tmpdir" EXIT
|
||||||
|
|
||||||
bn=$tmpdir/foo
|
bn=$tmpdir/foo
|
||||||
sig=$tmpdir/$Z.sig
|
|
||||||
pk=$bn.pub
|
pk=$bn.pub
|
||||||
sk=$bn.key
|
sk=$bn.key
|
||||||
|
sig=$tmpdir/$Z.sig
|
||||||
bn2=$tmpdir/bar
|
bn2=$tmpdir/bar
|
||||||
pk2=$bn2.pub
|
pk2=$bn2.pub
|
||||||
sk2=$bn2.key
|
sk2=$bn2.key
|
||||||
|
@ -77,35 +57,26 @@ spk2=$ssk2.pub
|
||||||
$keygen -q -C 'ssk1@foo' -t ed25519 -f $ssk1 -N ""
|
$keygen -q -C 'ssk1@foo' -t ed25519 -f $ssk1 -N ""
|
||||||
$keygen -q -C 'ssk2@foo' -t ed25519 -f $ssk2 -N ""
|
$keygen -q -C 'ssk2@foo' -t ed25519 -f $ssk2 -N ""
|
||||||
|
|
||||||
# extract the pk string
|
$bin s --no-password $ssk1 -o $sig $0 || die "can't sign with $ssk1"
|
||||||
spk1_str=$(cat $spk1 | awk '{ print $2 }')
|
$bin v -q $spk1 $sig $0 || die "can't verify with $spk2"
|
||||||
|
|
||||||
$bin s --no-password $ssk1 -o $sig $0 || die "can't sign with $ssk1"
|
$bin e --no-password -o $encout $spk2 $0 || die "can't encrypt to $spk2 with $ssk1"
|
||||||
$bin v -q $spk1 $sig $0 || die "can't verify with $spk2"
|
$bin d --no-password -o $decout $ssk2 $encout || die "can't decrypt with $ssk2"
|
||||||
$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
|
# cleanup state
|
||||||
rm -f $sig $encout $decout
|
rm -f $sig $encout $decout
|
||||||
|
|
||||||
|
|
||||||
# generate keys
|
# generate keys
|
||||||
$bin g -E FOO $bn || die "can't gen keypair $pk, $sk"
|
$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 "overwrote prev keypair"
|
||||||
$bin g -E FOO --overwrite $bn || die "can't force gen keypair $pk, $sk"
|
$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"
|
$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')
|
|
||||||
|
|
||||||
# sign and verify
|
# sign and verify
|
||||||
$bin s -E FOO $sk $0 -o $sig || die "can't sign $0"
|
$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 $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 && die "bad verification with wrong $pk2"
|
||||||
$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"
|
|
||||||
|
|
||||||
# encrypt/decrypt
|
# encrypt/decrypt
|
||||||
$bin e -E FOO -o $encout $pk2 $0 || die "can't encrypt to $pk2"
|
$bin e -E FOO -o $encout $pk2 $0 || die "can't encrypt to $pk2"
|
||||||
|
@ -115,7 +86,7 @@ cmp -s $decout $0 || die "decrypted file mismatch with $0"
|
||||||
# now with sender verification
|
# now with sender verification
|
||||||
$bin e -E FOO --overwrite -o $encout -s $sk $pk2 $0 || die "can't sender-encrypt to $pk2"
|
$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"
|
$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
|
# Only delete if everything worked
|
||||||
echo "$Z: All tests pass!"
|
echo "$Z: All tests pass!"
|
||||||
|
|
Loading…
Add table
Reference in a new issue