Compare commits

..

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

37 changed files with 2855 additions and 5013 deletions

5
.gitignore vendored
View file

@ -24,11 +24,12 @@ _testmain.go
*.prof
vendor/*
# vendor management
vendor/src/*
vendor/pkg/*
bin/*
sigtool
.??*.sw?
*.pub
*.key
*.sig
releases/*

View file

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

230
README.md
View file

@ -7,40 +7,25 @@
`sigtool` is an opinionated tool to generate keys, sign, verify, encrypt &
decrypt files using Ed25519 signature scheme. In many ways, it is like
like OpenBSD's [signify][1] -- except written in Golang and definitely
easier to use. It can use SSH ed25519 public and private keys.
easier to use.
It can sign and verify very large files - it prehashes the files
with SHA-512 and then signs the SHA-512 checksum. The keys and signatures
are human readable YAML files.
are YAML files and so, human readable.
It can encrypt files for multiple recipients - each of whom is identified
by their Ed25519 public key. The encryption generates ephmeral
by their Ed25519 public key. The encryption by default generates ephmeral
Curve25519 keys and creates pair-wise shared secret for each recipient of
the encrypted file. The caller can optionally use a specific private key
the encrypted file. The caller can optionally use a specific secret key
during the encryption process - this has the benefit of also authenticating
the sender (and the receiver can verify the sender if they possess the
corresponding sender's public key).
corresponding public key).
The sign, verify, encrypt, decrypt operations can use OpenSSH Ed25519 keys
*or* the keys generated by sigtool. This means, you can send encrypted
files to any recipient identified by their comment in `~/.ssh/authorized_keys`.
## How do I build it?
You need two things:
With Go 1.5 and later:
1. Protobuf compiler:
On Debian based systems: `apt install protobuf-compiler`
Consult your OS's package manager to install protobuf tools;
these are typically named 'protobuf' or 'protoc'.
2. go 1.13+ toolchain
Next, build sigtool:
git clone https://git.rgst.io/homelab/sigtool/v3
git clone https://github.com/opencoff/sigtool
cd sigtool
make
@ -76,15 +61,15 @@ e.g., to sign `archive.tar.gz` with private key `/tmp/testkey.key`:
sigtool sign /tmp/testkey.key archive.tar.gz
If *testkey.key* was encrypted without a user pass phrase:
If *testkey.key* was encrypted with a user pass phrase:
sigtool sign --no-password /tmp/testkey.key archive.tar.gz
sigtool sign -p /tmp/testkey.key archive.tar.gz
The signature can also be written directly to a user supplied output
file.
sigtool sign -o archive.sig /tmp/testkey.key archive.tar.gz
sigtool sign -p -o archive.sig /tmp/testkey.key archive.tar.gz
### Verify a signature against a file
@ -100,19 +85,11 @@ e.g., to verify the signature of *archive.tar.gz* against
sigtool verify /tmp/testkey.pub archive.sig archive.tar.gz
You can also pass a public key as a string (instead of a file):
sigtool verify iF84Dymq/bAEnUMK6DRIHWAQDRD8FwDDDfsgFfzdjWM= archive.sig archive.tar.gz
Note that signing and verifying can also work with OpenSSH ed25519
keys.
### Encrypt a file by authenticating the sender
If the sender wishes to prove to the recipient that they encrypted
a file:
sigtool encrypt -s sender.key to.pub -o archive.tar.gz.enc archive.tar.gz
sigtool encrypt -s sender.key to.pub -o archive.tar.gz.enc archive.tar.gz
This will create an encrypted file *archive.tar.gz.enc* such that the
@ -124,7 +101,7 @@ who they expect.
If the receiver has the public key of the sender, they can verify that
they indeed sent the file by cryptographically checking the output:
sigtool decrypt -o archive.tar.gz -v sender.pub to.key archive.tar.gz.enc
sigtool decrypt -o archive.tar.gz -v sender.pub to.key archive.tar.gz.enc
Note that the verification is optional and if the `-v` option is not
used, then decryption will proceed without verifying the sender.
@ -133,157 +110,87 @@ used, then decryption will proceed without verifying the sender.
`sigtool` can generate ephemeral keys for encrypting a file such that
the receiver doesn't need to authenticate the sender:
sigtool encrypt to.pub -o archive.tar.gz.enc archive.tar.gz
sigtool encrypt to.pub -o archive.tar.gz.enc archive.tar.gz
This will create an encrypted file *archive.tar.gz.enc* such that the
recipient in possession of *to.key* can decrypt it.
### Encrypt a file to an OpenSSH recipient *without* authenticating the sender
Suppose you want to send an encrypted file where the recipient's
public key is in `~/.ssh/authorized_keys`. Such a recipient is identified
by their OpenSSH key comment (typically `name@domain`):
sigtool encrypt user@domain -o archive.tar.gz.enc archive.tar.gz
If you have their public key in file "name-domain.pub", you can do:
sigtool encrypt name-domain.pub -o archive.tar.gz.enc archive.tar.gz
This will create an encrypted file *archive.tar.gz.enc* such that the
recipient can decrypt using their private key.
## Technical Details
### How is the file encryption done?
### How is the private key protected?
The Ed25519 private key is encrypted using a key derived from the
user supplied pass phrase. This pass phrase is used to derive an
encryption key using the Scrypt key derivation algorithm. The
resulting derived key is XOR'd with the Ed25519 private key before
being committed to disk. To protect the integrity of the process,
the essential parameters used for deriving the key, and the derived
key are hashed via SHA256 and stored along with the encrypted key.
As an additional security measure, the user supplied pass phrase is
hashed with SHA512.
### How is the Encryption done?
The file encryption uses AES-GCM-256 in AEAD mode. The encryption uses
a random 32-byte AES-256 key. This root key is expanded via
HKDF-SHA256 into:
a random 32-byte AES-256 key. The input is broken into chunks and
each chunk is individually AEAD encrypted. The default chunk size
is 4MB (4 * 1048576 bytes). Each chunk generates its own nonce
from a global salt. The nonce is calculated as a SHA256 hash of
the salt, the chunk length and the block number.
- AES-GCM-256 key (32 bytes)
- AES Nonce (12 bytes)
- HMAC-SHA-256 key (32 bytes)
### What is the public-key cryptography used?
`sigtool` uses Curve25519 ECC to generate shared secrets between
pairs of sender & recipients. This pairwise shared secret is expanded
using HKDF to generate a key-encryption-key. The file-encryption key
is AEAD encrypted with this key-encryption-key. Thus, each recipient
has their own individual encrypted key blob.
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 default chunk size is 4MB (4 * 1048576 bytes). Each chunk generates
its own nonce: the top-4 bytes of the nonce is the chunk-number. The
actual chunk-length and EOF marker is used as additional data (the
"AD" of "AEAD").
The last block has its most-signficant-bit set to 1 to denote EOF. Thus, the
maximum chunk size is set to 1GB.
We calculate a running hmac of the plaintext blocks; when sender
identity is present, the final HMAC is signed via the sender's
Ed25519 key. This signature is appended as the "trailer" (last 64
bytes of the encrypted file are the Ed25519 signature).
When sender identity is not present, the last bytes are random
bytes.
### What is the public-key cryptography in sigtool?
`sigtool` uses ephemeral Curve25519 keys to generate shared secrets
between pairs of sender & one or more recipients. This pairwise shared
secret is used as a key-encryption-key (KEK) to wrap the
data-encryption key in AEAD mode. Thus, each recipient has their own
individual encrypted key blob - that **only** they can decrypt.
If the sender authenticates the encryption by providing their secret
key, the encryption key material is signed via Ed25519 and the signature
is encrypted (using the data-encryption key) and stored in the
header. If the sender opts to not authenticate, a "signature" of all
zeroes is encrypted instead.
The Ed25519 keys generated by `sigtool` or OpenSSH are transformed to their
corresponding Curve25519 points in order to generate the pair-wise shared secret.
The Ed25519 keys generated by `sigtool` are transformed to their
corresponding Curve25519 points in order to generate the shared secret.
This elliptic co-ordinate transform follows [FiloSottile's writeup][2].
### Format of the Encrypted File
Every encrypted file starts with a header and the header-checksum:
* Fixed-size header
* Variable-length header
* SHA256 sum of both of the above
The fixed length header is:
Every encrypted file starts with a header:
7 byte magic ("SigTool")
1 byte version number
4 byte header length (big endian encoding)
32 byte SHA256 of the encryption-header
The variable length header has the per-recipient wrapped keys. This is
described as a protobuf file (sign/hdr.proto):
The encryption-header is described as a protobuf file (sign/hdr.proto):
```protobuf
message header {
uint32 chunk_size = 1;
bytes salt = 2;
bytes pk = 3; // sender's ephemeral curve PK
bytes sender = 4; // ed25519 signature of key material
repeated wrapped_key keys = 5;
bytes salt = 2;
repeated wrapped_key keys = 3;
}
/*
* A file encryption key is wrapped by a recipient specific public
* key. WrappedKey describes such a wrapped key.
*/
message wrapped_key {
bytes d_key = 1;
bytes nonce = 2;
bytes pk_hash = 1; // hash of Ed25519 PK
bytes pk = 2; // curve25519 PK
bytes nonce = 3; // AEAD nonce
bytes key = 4; // AEAD encrypted key
}
```
The SHA256 sum covers the fixed-length and variable-length headers.
The encrypted data immediately follows the headers above. Each encrypted
chunk is encoded the same way:
```C
4 byte chunk length (big endian encoding)
AEAD encrypted chunk data
chunk data
AEAD tag
```
The chunk length does _not_ include the AEAD tag length; it is implicitly
computed. The chunk data and AEAD tag are treated as an atomic unit for AEAD
The chunk data and AEAD tag are treated as an atomic unit for AEAD
decryption.
### How is the private key protected?
The Ed25519 private key is encrypted in AES-GCM-256 mode using a key
derived from the user's pass-phrase. The user pass phrase is expanded via
SHA256; this expanded pass phrase is fed to `scrypt()` to
generate a key-encryption-key. In pseudo code, this operation looks
like below:
passphrase = get_user_passphrase()
expanded = SHA512(passphrase)
salt = randombytes(32)
key = Scrypt(expanded, salt, N, r, p)
esk = AES256_GCM(ed25519_private_key, key)
Where, ```N```, ```r```, ```p``` are Scrypt parameters. In our
implementation:
N = 2^19 (1 << 19)
r = 8
p = 1
## Understanding the Code
The core logic is in `src/sign`: it is a library that exposes all the
functionality: key generation, key parsing, signing, encryption, decryption
etc.
* `src/encrypt.go` contains the core encryption, decryption code
* `src/sign.go` contains the Ed25519 signing, verification code
* `src/keys.go` contains key generation, serialization, de-serialization
* `src/ssh.go` contains code to parse SSH Ed25519 key files
* `src/stream.go` contains code that provides an `io.Reader` and `io.WriteCloser` interface
for encryption and decryption.
* `tests.sh` simple round trip test using the tool; this is in addition to the tests in
`sign/`.
`src/sign` is a library to generate, verify and store Ed25519 keys
and signatures. It uses the extended library (golang.org/x/crypto)
for the underlying operations.
`src/crypt.go` contains the encryption & decryption code.
The generated keys and signatures are proper YAML files and human
readable.
@ -296,11 +203,6 @@ Signatures on large files are calculated efficiently by reading them
in memory mapped mode (```mmap(2)```) and hashing the file contents
using SHA-512. The Ed25519 signature is calculated on the file-hash.
### Tests
The core library in `sign/` has extensive tests to verify signing and encryption.
Additionally, a simple shell script `tests.sh` does a full roundtrip of tests
using `sigtool`.
## Example of Keys, Signature
### Ed25519 Public Key
@ -316,11 +218,35 @@ And, a serialized Ed25519 private key looks like so:
esk: t3vfqHbgUiA733KKPymFjWT8DdnBEkiMfsDHolPUdQWpvVn/F1Z4J6KYV3M5rGO9xgKxh5RAmqt+6LKgOiJAMQ==
salt: pPHKG55UJYtJ5wU0G9hBvNQJ0DvT0a7T4Fmj4aPB84s=
algo: scrypt-sha256
verify: JvjRjJMKhJhBmZngC3Pvq7x3KCLKt7gar1AAz7HB4qM=
Z: 131072
r: 16
p: 1
```
The Ed25519 private key is encrypted using Scrypt password hashing
mechanism. A user supplied passphrase to protect the private key
is first pre-hashed using SHA-512 before being used in
```scrypt()```. In pseudo code, this operation looks like below:
passphrase = get_user_passphrase()
hpass = SHA512(passphrase)
salt = randombytes(32)
xorkey = Scrypt(hpass, salt, N, r, p)
verify = SHA256(salt, xorkey)
esk = ed25519_private_key ^ xorkey
Where, ```N```, ```r```, ```p``` are Scrypt parameters. In our
implementation:
N = 131072
r = 16
p = 1
```verify``` is used during the decryption of the Ed25519 private
key - *before* actually doing the "xor" operation. This check
ensures that the supplied passphrase yields the same value as
```verify```.
### Ed25519 Signature
A generated signature looks like below after serialization:

267
build
View file

@ -13,13 +13,12 @@
#
# License: GPLv2
#
Progs="src:sigtool"
Progs=".:sigtool"
# Relative path to protobuf sources
# e.g. src/foo/a.proto
Protobufs="internal/pb/hdr.proto"
Protobufs="sign/hdr.proto"
#set -x
# -- DO NOT CHANGE ANYTHING AFTER THIS --
@ -28,11 +27,13 @@ PWD=`pwd`
Static=0
Dryrun=0
Prodver=""
Repover=""
Prodver=0.1
Verbose=0
Go=`which go`
Bindir=$PWD/bin
hostos=$(go env GOHOSTOS) || exit 1
hostcpu=$(go env GOHOSTARCH) || exit 1
[ -f ./version ] && Prodver=$(cat ./version)
die() {
echo "$Z: $@" 1>&2
@ -50,56 +51,47 @@ case $BASH_VERSION in
;;
esac
getvcs_version() {
local rev=
local prodv=
local git=`which git`
local hg=`which hg`
if [ -n "$git" ]; then
local xrev=$(git describe --always --dirty --long --abbrev=12) || exit 1
rev="git:$xrev"
prodv=$(git tag --list | sort -V | tail -1)
elif [ -n "$hg" ]; then
local xrev=$(hg id --id) || exit 1
local brev=${xrev%+}
if [ "$brev" != "$xrev" ]; then
rev="hg:${brev}-dirty"
else
rev="hg:${brev}"
fi
prodv=$(hg log -r "branch(stable) and tag()" -T "{tags}\n" | sort -V | tail -1)
else
warn "no git or hg found; can't get VCS info"
rev="UNKNOWN-VER"
# build a tool that runs on the host - if needed.
hosttool() {
local tool=$1
local bindir=$2
local src=$3
local p=$(type -P $tool)
if [ -n "$p" ]; then
echo $p
return 0
fi
[ -n "$Prodver" ] && prodv=$Prodver
# from here - we want this dir to find all build artifacts
PATH=$PATH:$bindir
export PATH
echo "$rev $prodv"
p=$bindir/$tool
if [ -x $p ]; then
echo $p
return 0
fi
# build it and stash it in the hostdir
echo "Building tool $tool from $src .."
$e go get -d $src || exit 1
$e go build -o $p $src || exit 1
echo $p
return 0
}
read -r Repover Prodver <<< $(getvcs_version)
usage() {
declare -a progv=($Progs)
declare n=${#progv[@]}
declare pstr=
for ((i=0; i < n; i++)); do
local ent=${progv[$i]}
local dir=${ent%%:*}
local tool=${ent##*:}
pstr=$(printf "$pstr\n\t%s $Prodver $Repover (from ./%s)" $tool $dir)
done
cat <<EOF
$0 - A Go production build tool that adds git-repository information,
product version, build-timestamp etc. It supports cross-compilation,
static linking and generating protobuf output.
If needed, it uses the gogo-slick protobuf compiler [github.com/gogo/protobuf].
Build output is in bin/\$OS-\$CPU for a given OS, CPU combination.
Usage: $0
@ -108,14 +100,12 @@ Usage: $0
Where OS-ARCH denotes one of the valid OS, ARCH combinations supported by 'go'.
And, PROGS is one or more go programs.
With no arguments, $0 builds: $pstr
With no arguments, $0 builds: $Progs (source in ./src/)
The repository's latest tag is used as the default version of the software being
built. The current repository version is $Repover.
If ./version is present, its content are used as version number for the binary.
Options:
-h, --help Show this help message and quit
-b D, --bindir=D Put the binaries in the directory 'D' [$Bindir]
-s, --static Build a statically linked binary [False]
-V N, --version=N Use 'N' as the product version string [$Prodver]
-a X, --arch=X Cross compile for OS-CPU 'X' [$hostos-$hostcpu]
@ -123,16 +113,14 @@ Options:
-t, --test Run "go test" on modules named on the command line [False]
-v, --verbose Build verbosely (adds "-v" to go tooling) [False]
--vet Run "go vet" on modules named on the command line [False]
--mod Run "go mod ..." [False]
--go=G Use Go in 'G' [$Go]
-x Run in debug/trace mode [False]
--print-arch Print the target architecture and exit
EOF
exit 0
}
host=`uname|tr '[A-Z]' '[a-z]'`
export GO15VENDOREXPERIMENT=1
declare -A oses
declare -A cpus
@ -165,7 +153,6 @@ done
Tool=
doinit=0
args=
Printarch=0
#set -x
ac_prev=
@ -193,23 +180,13 @@ do
--arch=*)
Arch=$ac_optarg
;;
-a|--arch)
ac_prev=Arch
;;
-b|--bindir)
ac_prev=Bindir
;;
--bindir=*)
Bindir=$ac_optarg
;;
--version=*)
Prodver=$ac_optarg
;;
--test|-t)
Tool=test
;;
@ -218,14 +195,9 @@ do
Tool=vet
;;
--mod)
Tool=mod
;;
-V|--version)
ac_prev=Prodver
;;
-v|--verbose)
Verbose=1
;;
@ -242,14 +214,6 @@ do
set -x
;;
--go-root=*)
GoRoot=$ac_optarg
;;
--print-arch)
Printarch=1
;;
*) # first non option terminates option processing.
# we gather all remaining args and bundle them up.
args="$args $ac_option"
@ -261,80 +225,10 @@ do
;;
esac
done
[ $Dryrun -gt 0 ] && e=echo
# let every error abort
set -e
# build a tool that runs on the host - if needed.
hosttool() {
local tool=$1
local bindir=$2
local src=$3
p=$bindir/$tool
if [ -x $p ]; then
return 0
fi
local tmpdir=/tmp/$tool.$$
mkdir $tmpdir || die "can't make $tmpdir"
# since go1.20 - install uses env vars to decide where to put
# build artifacts. Why are all the google tooling so bloody dev
# hostile! WTF is wrong with command line args?!
export GOBIN=$bindir
# build it and stash it in the hostdir
echo "Building tool $tool from $src .."
(
cd $tmpdir
$e $Go install $src@latest || die "can't install $tool"
)
$e rm -rf $tmpdir
return 0
}
# protobuf gen
buildproto() {
local pbgo=protoc-gen-go
local vtgo=protoc-gen-go-vtproto
local vtgo_src=github.com/planetscale/vtprotobuf/cmd/protoc-gen-go-vtproto
local pc
local args="$*"
local pgen=$(type -p protoc)
local gogen=$(type -p $pbgo)
local vt=$Hostbindir/$vtgo
[ -z $pgen ] && die "install protoc tools"
[ -z $gogen ] && die "install protoc-gen-go"
# now install the vtproto generator
hosttool $vtgo $Hostbindir $vtgo_src
for f in $args; do
local dn=$(dirname $f)
local bn=$(basename $f .proto)
$e $pgen \
--go_out=. --plugin protoc-gen-go=$gogen \
--go-vtproto_out=. --plugin protoc-gen-go-vtproto="$vt" \
--go-vtproto_opt=features=marshal+unmarshal+size \
$f || die "can't generate protobuf output for $f .."
done
return 0
}
# the rest has to execute in the context of main shell (not funcs)
hostos=$($Go env GOHOSTOS) || exit 1
hostcpu=$($Go env GOHOSTARCH) || exit 1
#set -e
# This fragment can't be in a function - since it exports several vars
if [ -n "$Arch" ]; then
@ -380,32 +274,63 @@ else
fi
fi
if [ $Printarch -gt 0 ]; then
echo "$hostos-$hostcpu"
exit 0
fi
# This is where build outputs go
Outdir=$Bindir/$cross
Hostbindir=$Bindir/$hostos-$hostcpu
export PATH=$Hostbindir:$PATH
Bindir=$PWD/bin/$cross
Hostbindir=$PWD/bin/$hostos-$hostcpu
[ -d $Outdir ] || mkdir -p $Outdir
[ -d $Bindir ] || mkdir -p $Bindir
[ -d $Hostbindir ] || mkdir -p $Hostbindir
# Get git/hg version info for the build
if [ -d "./.hg" ]; then
xrev=$(hg id --id) || exit 1
brev=${xrev%+}
if [ "$brev" != "$xrev" ]; then
rev="hg:${brev}-dirty"
else
rev="hg:${brev}"
fi
elif [ -d "./.git" ]; then
xrev=$(git describe --always --dirty --long --abbrev=12) || exit 1
rev="git:$xrev"
else
rev="UNKNOWN-VER"
echo "$0: Can't find version info" 1>&2
fi
# Do Protobufs if needed
if [ -n "$Protobufs" ]; then
set +e
buildproto $Protobufs
set -e
slick=$Hostbindir/protoc-gen-gogoslick
slicksrc=github.com/gogo/protobuf/protoc-gen-gogoslick
pc=$(type -p protoc)
[ -z "$pc" ] && die "Please install protobuf-tools .."
slick=$(hosttool protoc-gen-gogoslick $Hostbindir $slicksrc) || exit 1
#if [ ! -f $slick ]; then
# echo "Building $slick .."
# $e go build -o $slick github.com/gogo/protobuf/protoc-gen-gogoslick || exit 1
#i
PATH=$Hostbindir:$PATH
export PATH
for f in $Protobufs; do
dn=$(dirname $f)
bn=$(basename $f .proto)
of=$dn/${bn}.pb.go
if [ $f -nt $of ]; then
echo "gogoslick: $f -> $of ..."
$e $pc --gogoslick_out=. $f || exit 1
$e gofmt -w $of
fi
done
fi
# Get git/hg version info for the build
repover="main.RepoVersion=$Repover"
repover="main.RepoVersion=$rev"
prodver="main.ProductVersion=$Prodver"
ldflags="-ldflags \"-X $repover -X $prodver $ldflags -buildid=\""
date="main.Buildtime=`date -u '+%Y-%m-%dT%H:%M.%SZ'`"
ldflags="-ldflags \"-X $repover -X $prodver -X $date $ldflags\""
vflag=""
[ $Verbose -gt 0 ] && vflag="-v"
@ -413,17 +338,12 @@ vflag=""
case $Tool in
test)
set -- $args
$e $Go test $vflag "$@"
$e go test $vflag "$@"
;;
vet)
set -- $args
$e $Go vet $vflag "$@"
;;
mod)
set -- $args
$e $Go mod $vflag "$@"
$e go vet $vflag "$@"
;;
*) # Default is to build programs
@ -434,9 +354,7 @@ case $Tool in
all="$@"
fi
[ -z "$all" ] && die "No programs specified. Try '$Z --help'"
echo "Building $Prodver ($Repover), $cross $msg .."
echo "Building $msg $Prodver ($rev) for $cross .."
for p in $all; do
if echo $p | grep -q ':' ; then
@ -446,15 +364,8 @@ case $Tool in
out=$p
dir=$p
fi
# Add .exe suffix to out if needed
if [ "$GOOS" = "windows" ]; then
base=${out%%.exe}
out="${base}.exe"
fi
echo " $dir: $out .. "
$e eval $Go build $vflag -trimpath -o $Outdir/$out $isuffix "$ldflags" ./$dir || exit 1
$e eval go build $vflag -o $Bindir/$out $isuffix "$ldflags" ./$dir || exit 1
done
;;
esac

279
crypt.go Normal file
View file

@ -0,0 +1,279 @@
// crypt.go -- Encrypt/decrypt command handling
//
// (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"
"github.com/opencoff/go-utils"
flag "github.com/opencoff/pflag"
"github.com/opencoff/sigtool/sign"
)
// sigtool encrypt [-i|--identity my.key] to.pub [to.pub] [ssh.pub] inputfile|- [-o output]
func encrypt(args []string) {
fs := flag.NewFlagSet("encrypt", flag.ExitOnError)
fs.Usage = func() {
encryptUsage(fs)
}
var outfile string
var keyfile string
var envpw string
var pw bool
fs.StringVarP(&outfile, "outfile", "o", "", "Write the output to file `F`")
fs.StringVarP(&keyfile, "sign", "s", "", "Sign using private key `S`")
fs.BoolVarP(&pw, "password", "p", false, "Ask for passphrase to decrypt the private key")
fs.StringVarP(&envpw, "env-password", "", "", "Use passphrase from environment variable `E`")
err := fs.Parse(args)
if err != nil {
die("%s", err)
}
var pws, infile string
if len(envpw) > 0 {
pws = os.Getenv(envpw)
} else if pw {
pws, err = utils.Askpass("Enter passphrase for private key", false)
if err != nil {
die("%s", err)
}
}
var sk *sign.PrivateKey
if len(keyfile) > 0 {
sk, err = sign.ReadPrivateKey(keyfile, pws)
if err != nil {
die("%s", err)
}
}
args = fs.Args()
if len(args) < 2 {
die("Insufficient args. Try '%s --help'")
}
var infd io.Reader = os.Stdin
var outfd io.Writer = os.Stdout
var inf *os.File
if len(args) > 1 {
infile = args[len(args)-1]
if infile != "-" {
inf := mustOpen(infile, os.O_RDONLY)
defer inf.Close()
infd = inf
}
}
if len(outfile) > 0 && outfile != "-" {
if inf != nil {
ost, err := os.Stat(outfile)
if err != nil {
die("can't stat %s: %s", outfile, err)
}
ist, err := inf.Stat()
if err != nil {
die("can't stat %s: %s", infile, err)
}
if os.SameFile(ist, ost) {
die("won't create output file: same as input file!")
}
}
outf := mustOpen(outfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC)
defer outf.Close()
outfd = outf
}
en, err := sign.NewEncryptor(sk)
if err != nil {
die("%s", err)
}
for i := 0; i < len(args)-1; i++ {
fn := args[i]
pk, err := sign.ReadPublicKey(fn)
if err != nil {
die("%s", err)
}
err = en.AddRecipient(pk)
if err != nil {
die("%s", err)
}
}
err = en.Encrypt(infd, outfd)
if err != nil {
die("%s", err)
}
}
// sigtool decrypt a.key [file] [-o output]
func decrypt(args []string) {
fs := flag.NewFlagSet("decrypt", flag.ExitOnError)
fs.Usage = func() {
decryptUsage(fs)
}
var envpw string
var outfile string
var pubkey string
var pw bool
fs.StringVarP(&outfile, "outfile", "o", "", "Write the output to file `F`")
fs.BoolVarP(&pw, "password", "p", false, "Ask for passphrase to decrypt the private key")
fs.StringVarP(&envpw, "env-password", "", "", "Use passphrase from environment variable `E`")
fs.StringVarP(&pubkey, "verify-sender", "v", "", "Verify that the sender matches public key in `F`")
err := fs.Parse(args)
if err != nil {
die("%s", err)
}
args = fs.Args()
if len(args) < 1 {
die("Insufficient args. Try '%s --help'")
}
var infd io.Reader = os.Stdin
var outfd io.Writer = os.Stdout
var inf *os.File
var pws, infile string
if len(envpw) > 0 {
pws = os.Getenv(envpw)
} else if pw {
pws, err = utils.Askpass("Enter passphrase for private key", false)
if err != nil {
die("%s", err)
}
}
keyfile := args[0]
sk, err := sign.ReadPrivateKey(keyfile, pws)
if err != nil {
die("%s", err)
}
var pk *sign.PublicKey
if len(pubkey) > 0 {
pk, err = sign.ReadPublicKey(pubkey)
if err != nil {
die("%s", err)
}
}
if len(args) > 1 {
infile = args[1]
if infile != "-" {
inf := mustOpen(infile, os.O_RDONLY)
defer inf.Close()
infd = inf
}
}
if len(outfile) > 0 && outfile != "-" {
if inf != nil {
ost, err := os.Stat(outfile)
if err != nil {
die("can't stat %s: %s", outfile, err)
}
ist, err := inf.Stat()
if err != nil {
die("can't stat %s: %s", infile, err)
}
if os.SameFile(ist, ost) {
die("won't create output file: same as input file!")
}
}
outf := mustOpen(outfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC)
defer outf.Close()
outfd = outf
}
d, err := sign.NewDecryptor(infd)
if err != nil {
die("%s", err)
}
err = d.SetPrivateKey(sk, pk)
if err != nil {
die("%s", err)
}
err = d.Decrypt(outfd)
if err != nil {
die("%s", err)
}
}
func encryptUsage(fs *flag.FlagSet) {
fmt.Printf(`%s encrypt: Encrypt a file to one or more recipients.
Usage: %s encrypt [options] to [to ...] infile|-
Where TO is the public key of the recipient and INFILE is an input file.
If the input file is '-' then %s reads from STDIN. Unless '-o' is used,
%s writes the encrypted output to STDOUT.
Options:
`, Z, Z, Z, Z)
fs.PrintDefaults()
os.Exit(0)
}
func decryptUsage(fs *flag.FlagSet) {
fmt.Printf(`%s decrypt: Decrypt a file.
Usage: %s decrypt [options] key [infile]
Where KEY is the private key to be used for decryption and INFILE is
the encrypted input file. If INFILE is not provided, %s reads
from STDIN. Unless '-o' is used, %s writes the decrypted output to STDOUT.
Options:
`, Z, Z, Z, Z)
fs.PrintDefaults()
os.Exit(0)
}
func mustOpen(fn string, flag int) *os.File {
fdk, err := os.OpenFile(fn, flag, 0600)
if err != nil {
die("can't open file %s: %s", fn, err)
}
return fdk
}

28
go.mod
View file

@ -1,25 +1,11 @@
module git.rgst.io/homelab/sigtool/v3
module github.com/opencoff/sigtool
go 1.24.0
go 1.13
require (
github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a
github.com/opencoff/go-fio v0.5.14
github.com/opencoff/go-mmap v0.1.5
github.com/opencoff/go-utils v1.0.2
github.com/opencoff/pflag v1.0.7
github.com/planetscale/vtprotobuf v0.6.0
golang.org/x/crypto v0.36.0
google.golang.org/protobuf v1.36.5
gopkg.in/yaml.v3 v3.0.1
github.com/gogo/protobuf v1.3.1
github.com/opencoff/go-utils v0.3.0
github.com/opencoff/pflag v0.3.3
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc
gopkg.in/yaml.v2 v2.2.4
)
require (
github.com/pkg/xattr v0.4.10 // indirect
github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/term v0.30.0 // indirect
)
//replace github.com/opencoff/go-mmap => ../go-mmap
//replace github.com/opencoff/go-utils => ../go-utils

58
go.sum
View file

@ -1,39 +1,21 @@
github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a h1:saTgr5tMLFnmy/yg3qDTft4rE5DY2uJ/cCxCe3q0XTU=
github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a/go.mod h1:Bw9BbhOJVNR+t0jCqx2GC6zv0TGBsShs56Y3gfSCvl0=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/opencoff/go-fio v0.5.14 h1:PGi4XLLO4RSuc3m5exY0G2vweov6w3UThhScehBfM8c=
github.com/opencoff/go-fio v0.5.14/go.mod h1:hoSySYpavRnfQUsxzUgadk31kYiNQhMDvA2MObsXKf8=
github.com/opencoff/go-mmap v0.1.5 h1:RKPtevC4mOW5bi9skBPPo4nFTIH4lVWAL20Tff+FjLg=
github.com/opencoff/go-mmap v0.1.5/go.mod h1:y/6Jk/tDUc00k3oSQpiJX++20Nw7xFSlc5kLkhGnRXw=
github.com/opencoff/go-utils v1.0.2 h1:BANRL8ZxgHpuo8gQBAzT3M9Im3aNFhaWW28jhc86LNs=
github.com/opencoff/go-utils v1.0.2/go.mod h1:eZkEVQVzNfuE8uGepyhscMsqcXq7liGbBHYYwgYaoy8=
github.com/opencoff/pflag v1.0.7 h1:o5cQIuX75bDcdJ6AXl68gzpA72a3CJ2MPStaMnEuwi4=
github.com/opencoff/pflag v1.0.7/go.mod h1:2bXtpAD/5h/2LarkbsRwiUxqnvB1nZBzn9Xjad1P41A=
github.com/pkg/xattr v0.4.10 h1:Qe0mtiNFHQZ296vRgUjRCoPHPqH7VdTOrZx3g0T+pGA=
github.com/pkg/xattr v0.4.10/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU=
github.com/planetscale/vtprotobuf v0.6.0 h1:nBeETjudeJ5ZgBHUz1fVHvbqUKnYOXNhsIEabROxmNA=
github.com/planetscale/vtprotobuf v0.6.0/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=
github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/opencoff/go-utils v0.3.0 h1:/TQXjf50o3GSB9MItog5L8Gf4GWJ4B5+rmqjB4g2RZQ=
github.com/opencoff/go-utils v0.3.0/go.mod h1:c+7QUAiCCHcNH6OGvsZ0fviG7cgse8Y3ucg+xy7sGXM=
github.com/opencoff/pflag v0.3.3 h1:yohZkwYGPkB34WXvUQzU5GyLhImnjfePDARUaE8me3U=
github.com/opencoff/pflag v0.3.3/go.mod h1:mTLzGGUGda1Av3d34iAJlh0JIlRxmFZtmc6qoWPspK0=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc h1:c0o/qxkaO2LF5t6fQrT4b5hzyggAkLLlCUjqfRxd8Q4=
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View file

@ -1,258 +0,0 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.32.0
// protoc v3.21.12
// source: internal/pb/hdr.proto
package pb
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
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)
)
// Every encrypted file starts with a header describing the
// Block Size, Salt, Recipient keys etc. Header represents a
// decoded version of this information. It is encoded in
// protobuf format before writing to disk.
type Header struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ChunkSize uint32 `protobuf:"varint,1,opt,name=chunk_size,json=chunkSize,proto3" json:"chunk_size,omitempty"` // encryption block size
Salt []byte `protobuf:"bytes,2,opt,name=salt,proto3" json:"salt,omitempty"` // master salt (nonces are derived from this)
Pk []byte `protobuf:"bytes,3,opt,name=pk,proto3" json:"pk,omitempty"` // ephemeral curve PK
Sender []byte `protobuf:"bytes,4,opt,name=sender,proto3" json:"sender,omitempty"` // sender signed artifacts
Keys []*WrappedKey `protobuf:"bytes,5,rep,name=keys,proto3" json:"keys,omitempty"` // list of wrapped receiver blocks
}
func (x *Header) Reset() {
*x = Header{}
if protoimpl.UnsafeEnabled {
mi := &file_internal_pb_hdr_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Header) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Header) ProtoMessage() {}
func (x *Header) ProtoReflect() protoreflect.Message {
mi := &file_internal_pb_hdr_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Header.ProtoReflect.Descriptor instead.
func (*Header) Descriptor() ([]byte, []int) {
return file_internal_pb_hdr_proto_rawDescGZIP(), []int{0}
}
func (x *Header) GetChunkSize() uint32 {
if x != nil {
return x.ChunkSize
}
return 0
}
func (x *Header) GetSalt() []byte {
if x != nil {
return x.Salt
}
return nil
}
func (x *Header) GetPk() []byte {
if x != nil {
return x.Pk
}
return nil
}
func (x *Header) GetSender() []byte {
if x != nil {
return x.Sender
}
return nil
}
func (x *Header) GetKeys() []*WrappedKey {
if x != nil {
return x.Keys
}
return nil
}
// A file encryption key is wrapped by a recipient specific public
// key. WrappedKey describes such a wrapped key.
type WrappedKey struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
DKey []byte `protobuf:"bytes,1,opt,name=d_key,json=dKey,proto3" json:"d_key,omitempty"` // encrypted data key
Nonce []byte `protobuf:"bytes,2,opt,name=nonce,proto3" json:"nonce,omitempty"` // nonce used for encryption
}
func (x *WrappedKey) Reset() {
*x = WrappedKey{}
if protoimpl.UnsafeEnabled {
mi := &file_internal_pb_hdr_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *WrappedKey) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*WrappedKey) ProtoMessage() {}
func (x *WrappedKey) ProtoReflect() protoreflect.Message {
mi := &file_internal_pb_hdr_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use WrappedKey.ProtoReflect.Descriptor instead.
func (*WrappedKey) Descriptor() ([]byte, []int) {
return file_internal_pb_hdr_proto_rawDescGZIP(), []int{1}
}
func (x *WrappedKey) GetDKey() []byte {
if x != nil {
return x.DKey
}
return nil
}
func (x *WrappedKey) GetNonce() []byte {
if x != nil {
return x.Nonce
}
return nil
}
var File_internal_pb_hdr_proto protoreflect.FileDescriptor
var file_internal_pb_hdr_proto_rawDesc = []byte{
0x0a, 0x15, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x62, 0x2f, 0x68, 0x64,
0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x85, 0x01, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64,
0x65, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x5f, 0x73, 0x69, 0x7a, 0x65,
0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x53, 0x69, 0x7a,
0x65, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x61, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52,
0x04, 0x73, 0x61, 0x6c, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x70, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28,
0x0c, 0x52, 0x02, 0x70, 0x6b, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18,
0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x20, 0x0a,
0x04, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x77, 0x72,
0x61, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x52, 0x04, 0x6b, 0x65, 0x79, 0x73, 0x22,
0x38, 0x0a, 0x0b, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x12, 0x13,
0x0a, 0x05, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64,
0x4b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01,
0x28, 0x0c, 0x52, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x42, 0x0d, 0x5a, 0x0b, 0x69, 0x6e, 0x74,
0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_internal_pb_hdr_proto_rawDescOnce sync.Once
file_internal_pb_hdr_proto_rawDescData = file_internal_pb_hdr_proto_rawDesc
)
func file_internal_pb_hdr_proto_rawDescGZIP() []byte {
file_internal_pb_hdr_proto_rawDescOnce.Do(func() {
file_internal_pb_hdr_proto_rawDescData = protoimpl.X.CompressGZIP(file_internal_pb_hdr_proto_rawDescData)
})
return file_internal_pb_hdr_proto_rawDescData
}
var file_internal_pb_hdr_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_internal_pb_hdr_proto_goTypes = []interface{}{
(*Header)(nil), // 0: header
(*WrappedKey)(nil), // 1: wrapped_key
}
var file_internal_pb_hdr_proto_depIdxs = []int32{
1, // 0: header.keys:type_name -> wrapped_key
1, // [1:1] is the sub-list for method output_type
1, // [1:1] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_internal_pb_hdr_proto_init() }
func file_internal_pb_hdr_proto_init() {
if File_internal_pb_hdr_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_internal_pb_hdr_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Header); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_internal_pb_hdr_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*WrappedKey); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_internal_pb_hdr_proto_rawDesc,
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_internal_pb_hdr_proto_goTypes,
DependencyIndexes: file_internal_pb_hdr_proto_depIdxs,
MessageInfos: file_internal_pb_hdr_proto_msgTypes,
}.Build()
File_internal_pb_hdr_proto = out.File
file_internal_pb_hdr_proto_rawDesc = nil
file_internal_pb_hdr_proto_goTypes = nil
file_internal_pb_hdr_proto_depIdxs = nil
}

View file

@ -1,29 +0,0 @@
syntax="proto3";
option go_package = "internal/pb";
/*
* Every encrypted file starts with a header describing the
* Block Size, Salt, Recipient keys etc. Header represents a
* decoded version of this information. It is encoded in
* protobuf format before writing to disk.
*/
message header {
uint32 chunk_size = 1; // encryption block size
bytes salt = 2; // master salt (nonces are derived from this)
bytes pk = 3; // ephemeral curve PK
bytes sender = 4; // sender signed artifacts
repeated wrapped_key keys = 5; // list of wrapped receiver blocks
}
/*
* A file encryption key is wrapped by a recipient specific public
* key. WrappedKey describes such a wrapped key.
*/
message wrapped_key {
bytes d_key = 1; // encrypted data key
bytes nonce = 2; // nonce used for encryption
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,33 +0,0 @@
// doc.go -- Documentation for sign & encrypt
//
// (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 implements Ed25519 signing, verification on files.
// It builds upon golang.org/x/crypto/ed25519 by adding methods
// for serializing and deserializing Ed25519 private & public keys.
//
// It can sign and verify very large files - it prehashes the files
// with SHA-512 and then signs the SHA-512 checksum. The keys and signatures
// are YAML files and so, human readable.
//
// It can encrypt files for multiple recipients - each of whom is identified
// by their Ed25519 public key. The encryption by default generates ephmeral
// Curve25519 keys and creates pair-wise shared secret for each recipient of
// the encrypted file. The caller can optionally use a specific secret key
// during the encryption process - this has the benefit of also authenticating
// the sender (and the receiver can verify the sender if they possess the
// corresponding public key).
//
// The sign, verify, encrypt, decrypt operations can use OpenSSH Ed25519 keys
// *or* the keys generated by sigtool. This means, you can send encrypted
// files to any recipient identified by their comment in `~/.ssh/authorized_keys`.
package sign

File diff suppressed because it is too large Load diff

View file

@ -1,477 +0,0 @@
// crypt_test.go -- Test harness for encrypt/decrypt bits
//
// (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 (
"bytes"
"crypto/rand"
"encoding/binary"
"fmt"
"io"
"testing"
)
type Buffer struct {
bytes.Buffer
}
func (b *Buffer) Close() error {
return nil
}
// one sender, one receiver no verification of sender
func TestEncryptSimple(t *testing.T) {
assert := newAsserter(t)
sk, err := NewPrivateKey()
assert(err == nil, "SK gen failed: %s", err)
pk := sk.PublicKey()
var blkSize int = 1024
var size int = (blkSize * 10)
// cleartext
buf := make([]byte, size)
for i := 0; i < len(buf); i++ {
buf[i] = byte(i & 0xff)
}
ee, err := NewEncryptor(nil, uint64(blkSize))
assert(err == nil, "encryptor create fail: %s", err)
err = ee.AddRecipient(pk)
assert(err == nil, "can't add recipient: %s", err)
rd := bytes.NewBuffer(buf)
wr := Buffer{}
err = ee.Encrypt(rd, &wr)
assert(err == nil, "encrypt fail: %s", err)
rd = bytes.NewBuffer(wr.Bytes())
dd, err := NewDecryptor(rd)
assert(err == nil, "decryptor create fail: %s", err)
err = dd.SetPrivateKey(sk, nil)
assert(err == nil, "decryptor can't add SK: %s", err)
wr = Buffer{}
err = dd.Decrypt(&wr)
assert(err == nil, "decrypt fail: %s", err)
b := wr.Bytes()
assert(len(b) == len(buf), "decrypt length mismatch: exp %d, saw %d", len(buf), len(b))
assert(byteEq(b, buf), "decrypt content mismatch")
}
// one sender, one receiver - small blocks
func TestEncryptSmallSizes(t *testing.T) {
assert := newAsserter(t)
sk, err := NewPrivateKey()
assert(err == nil, "SK gen failed: %s", err)
pk := sk.PublicKey()
var blkSize int = 8
var size int = (blkSize * 4)
// cleartext
bigbuf := make([]byte, size)
for i := 0; i < len(bigbuf); i++ {
bigbuf[i] = byte(i & 0xff)
}
// encrypt progressively larger bufs
for i := 1; i < len(bigbuf); i++ {
buf := bigbuf[:i]
ee, err := NewEncryptor(nil, uint64(blkSize))
assert(err == nil, "encryptor-%d create fail: %s", i, err)
err = ee.AddRecipient(pk)
assert(err == nil, "encryptor-%d: can't add recipient: %s", i, err)
rd := bytes.NewBuffer(buf)
wr := Buffer{}
err = ee.Encrypt(rd, &wr)
assert(err == nil, "encrypt-%d fail: %s", i, err)
rd = bytes.NewBuffer(wr.Bytes())
dd, err := NewDecryptor(rd)
assert(err == nil, "decryptor-%d create fail: %s", i, err)
err = dd.SetPrivateKey(sk, nil)
assert(err == nil, "decryptor-%d can't add SK: %s", i, err)
wr = Buffer{}
err = dd.Decrypt(&wr)
assert(err == nil, "decrypt-%d fail: %s", i, err)
b := wr.Bytes()
assert(len(b) == len(buf), "decrypt-%d length mismatch: exp %d, saw %d", i, len(buf), len(b))
assert(byteEq(b, buf), "decrypt-%d content mismatch", i)
}
}
// test corrupted header or corrupted input
func TestEncryptCorrupted(t *testing.T) {
assert := newAsserter(t)
sk, err := NewPrivateKey()
assert(err == nil, "SK gen failed: %s", err)
pk := sk.PublicKey()
var blkSize int = 1024
var size int = (blkSize * 23) + randmod(blkSize)
// cleartext
buf := make([]byte, size)
for i := 0; i < len(buf); i++ {
buf[i] = byte(i & 0xff)
}
ee, err := NewEncryptor(nil, uint64(blkSize))
assert(err == nil, "encryptor create fail: %s", err)
err = ee.AddRecipient(pk)
assert(err == nil, "can't add recipient: %s", err)
rd := bytes.NewReader(buf)
wr := Buffer{}
err = ee.Encrypt(rd, &wr)
assert(err == nil, "encrypt fail: %s", err)
rb := wr.Bytes()
n := len(rb)
// corrupt the input
for i := 0; i < n; i++ {
j := randint() % n
rb[j] = byte(randint() & 0xff)
}
rd = bytes.NewReader(rb)
dd, err := NewDecryptor(rd)
assert(err != nil, "decryptor works on bad input")
assert(dd == nil, "decryptor not nil for bad input")
}
// one sender, one receiver with verification of sender
func TestEncryptSenderVerified(t *testing.T) {
assert := newAsserter(t)
sender, err := NewPrivateKey()
assert(err == nil, "sender SK gen failed: %s", err)
receiver, err := NewPrivateKey()
assert(err == nil, "receiver SK gen failed: %s", err)
var blkSize int = 1024
var size int = (blkSize * 23) + randmod(blkSize)
// cleartext
buf := make([]byte, size)
for i := 0; i < len(buf); i++ {
buf[i] = byte(i & 0xff)
}
ee, err := NewEncryptor(sender, uint64(blkSize))
assert(err == nil, "encryptor create fail: %s", err)
err = ee.AddRecipient(receiver.PublicKey())
assert(err == nil, "can't add recipient: %s", err)
rd := bytes.NewBuffer(buf)
wr := Buffer{}
err = ee.Encrypt(rd, &wr)
assert(err == nil, "encrypt fail: %s", err)
rd = bytes.NewBuffer(wr.Bytes())
dd, err := NewDecryptor(rd)
assert(err == nil, "decryptor create fail: %s", err)
randkey, err := NewPrivateKey()
assert(err == nil, "rand SK gen failed: %s", err)
// first send a wrong sender PK
err = dd.SetPrivateKey(receiver, randkey.PublicKey())
assert(err != nil, "decryptor failed to verify sender")
// then the correct sender PK
err = dd.SetPrivateKey(receiver, sender.PublicKey())
assert(err == nil, "decryptor can't add SK: %s", err)
wr = Buffer{}
err = dd.Decrypt(&wr)
assert(err == nil, "decrypt fail: %s", err)
b := wr.Bytes()
assert(len(b) == len(buf), "decrypt length mismatch: exp %d, saw %d", len(buf), len(b))
assert(byteEq(b, buf), "decrypt content mismatch")
}
// one sender, multiple receivers, each decrypting the blob
func TestEncryptMultiReceiver(t *testing.T) {
assert := newAsserter(t)
sender, err := NewPrivateKey()
assert(err == nil, "sender SK gen failed: %s", err)
var blkSize int = 1024
var size int = (blkSize * 23) + randmod(blkSize)
// cleartext
buf := make([]byte, size)
for i := 0; i < len(buf); i++ {
buf[i] = byte(i & 0xff)
}
ee, err := NewEncryptor(sender, uint64(blkSize))
assert(err == nil, "encryptor create fail: %s", err)
n := 4
rx := make([]*PrivateKey, n)
for i := 0; i < n; i++ {
r, err := NewPrivateKey()
assert(err == nil, "can't make receiver SK %d: %s", i, err)
rx[i] = r
err = ee.AddRecipient(r.PublicKey())
assert(err == nil, "can't add recipient %d: %s", i, err)
}
rd := bytes.NewBuffer(buf)
wr := Buffer{}
err = ee.Encrypt(rd, &wr)
assert(err == nil, "encrypt fail: %s", err)
encBytes := wr.Bytes()
for i := 0; i < n; i++ {
rd = bytes.NewBuffer(encBytes)
dd, err := NewDecryptor(rd)
assert(err == nil, "decryptor %d create fail: %s", i, err)
err = dd.SetPrivateKey(rx[i], sender.PublicKey())
assert(err == nil, "decryptor can't add SK %d: %s", i, err)
wr = Buffer{}
err = dd.Decrypt(&wr)
assert(err == nil, "decrypt %d fail: %s", i, err)
b := wr.Bytes()
assert(len(b) == len(buf), "decrypt %d length mismatch: exp %d, saw %d", i, len(buf), len(b))
assert(byteEq(b, buf), "decrypt %d content mismatch", i)
}
}
// Test stream write and read
func TestStreamIO(t *testing.T) {
assert := newAsserter(t)
receiver, err := NewPrivateKey()
assert(err == nil, "receiver keypair gen failed: %s", err)
var blkSize int = 1024
var size int = (blkSize * 10)
// cleartext
buf := make([]byte, size)
for i := 0; i < len(buf); i++ {
buf[i] = byte(i & 0xff)
}
ee, err := NewEncryptor(nil, uint64(blkSize))
assert(err == nil, "encryptor create fail: %s", err)
err = ee.AddRecipient(receiver.PublicKey())
assert(err == nil, "can't add recipient: %s", err)
wr := Buffer{}
wio, err := ee.NewStreamWriter(&wr)
assert(err == nil, "can't start stream writer: %s", err)
// chunksize for writing to stream
csize := 19
rbuf := buf
for len(rbuf) > 0 {
m := csize
if len(rbuf) < m {
m = len(rbuf)
}
n, err := wio.Write(rbuf[:m])
assert(err == nil, "stream write failed: %s", err)
assert(n == m, "stream write mismatch: exp %d, saw %d", m, n)
rbuf = rbuf[m:]
}
err = wio.Close()
assert(err == nil, "stream close failed: %s", err)
_, err = wio.Write(buf[:csize])
assert(err != nil, "stream write accepted I/O after close: %s", err)
rd := bytes.NewBuffer(wr.Bytes())
dd, err := NewDecryptor(rd)
assert(err == nil, "decryptor create fail: %s", err)
err = dd.SetPrivateKey(receiver, nil)
assert(err == nil, "decryptor can't add SK: %s", err)
rio, err := dd.NewStreamReader()
assert(err == nil, "stream reader failed: %s", err)
rbuf = make([]byte, csize)
wr = Buffer{}
n := 0
for {
m, err := rio.Read(rbuf)
assert(err == nil || err == io.EOF, "streamread fail: %s", err)
if m > 0 {
wr.Write(rbuf[:m])
n += m
}
if err == io.EOF || m == 0 {
break
}
}
b := wr.Bytes()
assert(n == len(b), "streamread: bad buflen; exp %d, saw %d", n, len(b))
assert(n == len(buf), "streamread: decrypt len mismatch; exp %d, saw %d", len(buf), n)
assert(byteEq(b, buf), "decrypt content mismatch")
}
// Test stream write and read with small sizes
func TestSmallSizeStreamIO(t *testing.T) {
assert := newAsserter(t)
receiver, err := NewPrivateKey()
assert(err == nil, "receiver SK gen failed: %s", err)
var blkSize int = 8
var size int = blkSize * 10
// cleartext
bigbuf := make([]byte, size)
for i := 0; i < len(bigbuf); i++ {
bigbuf[i] = byte(i & 0xff)
}
for i := 1; i < len(bigbuf); i++ {
buf := bigbuf[:i]
t.Logf("small-size-stream: size %d, chunksize %d\n", i, blkSize)
ee, err := NewEncryptor(nil, uint64(blkSize))
assert(err == nil, "encryptor create fail: %s", err)
err = ee.AddRecipient(receiver.PublicKey())
assert(err == nil, "can't add recipient: %s", err)
wr := Buffer{}
wio, err := ee.NewStreamWriter(&wr)
assert(err == nil, "can't start stream writer: %s", err)
// chunksize for writing to stream
csize := blkSize - 1
rbuf := buf
for len(rbuf) > 0 {
m := csize
if len(rbuf) < m {
m = len(rbuf)
}
n, err := wio.Write(rbuf[:m])
assert(err == nil, "stream write failed: %s", err)
assert(n == m, "stream write mismatch: exp %d, saw %d", m, n)
rbuf = rbuf[m:]
}
err = wio.Close()
assert(err == nil, "stream close failed: %s", err)
_, err = wio.Write(buf[:csize])
assert(err != nil, "stream write accepted I/O after close: %s", err)
rd := bytes.NewBuffer(wr.Bytes())
dd, err := NewDecryptor(rd)
assert(err == nil, "decryptor create fail: %s", err)
err = dd.SetPrivateKey(receiver, nil)
assert(err == nil, "decryptor can't add SK: %s", err)
rio, err := dd.NewStreamReader()
assert(err == nil, "stream reader failed: %s", err)
rbuf = make([]byte, csize)
wr = Buffer{}
n := 0
for {
m, err := rio.Read(rbuf)
assert(err == nil || err == io.EOF, "streamread fail: %s", err)
if m > 0 {
wr.Write(rbuf[:m])
n += m
}
if err == io.EOF || m == 0 {
break
}
}
b := wr.Bytes()
assert(n == len(b), "streamread: bad buflen; exp %d, saw %d", n, len(b))
assert(n == len(buf), "streamread: decrypt len mismatch; exp %d, saw %d", len(buf), n)
assert(byteEq(b, buf), "decrypt content mismatch")
}
}
func randint() int {
var b [4]byte
_, err := io.ReadFull(rand.Reader, b[:])
if err != nil {
panic(fmt.Sprintf("can't read 4 rand bytes: %s", err))
}
u := binary.BigEndian.Uint32(b[:])
return int(u & 0x7fffffff)
}
func randmod(m int) int {
return randint() % m
}

View file

@ -1,45 +0,0 @@
// errors.go - list of all exportable errors in this module
//
// (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 (
"errors"
)
var (
ErrClosed = errors.New("encrypt: stream already closed")
ErrNoKey = errors.New("decrypt: no private key set for decryption")
ErrEncStarted = errors.New("encrypt: can't add new recipient after encryption has started")
ErrDecStarted = errors.New("decrypt: can't add new recipient after decryption has started")
ErrEncIsStream = errors.New("encrypt: can't use Encrypt() after using streaming I/O")
ErrNotSigTool = errors.New("decrypt: not a sigtool encrypted file?")
ErrHeaderTooBig = errors.New("decrypt: header too large (max 1048576)")
ErrHeaderTooSmall = errors.New("decrypt: header too small (min 32)")
ErrBadHeader = errors.New("decrypt: header corrupted")
ErrNoWrappedKeys = errors.New("decrypt: no wrapped keys in encrypted file")
ErrBadKey = errors.New("decrypt: wrong key")
ErrBadTrailer = errors.New("decrypt: message integrity failed (bad trailer)")
ErrBadSender = errors.New("unwrap: sender verification failed")
ErrNoSenderPK = errors.New("unwrap: missing sender public key")
ErrIncorrectPassword = errors.New("ssh: invalid passphrase")
ErrNoPEMFound = errors.New("ssh: no PEM block found")
ErrBadPublicKey = errors.New("ssh: malformed public key")
ErrKeyTooShort = errors.New("ssh: public key too short")
ErrBadTrailers = errors.New("ssh: trailing junk in public key")
ErrBadFormat = errors.New("ssh: invalid openssh private key format")
ErrBadLength = errors.New("ssh: private key unexpected length")
ErrBadPadding = errors.New("ssh: padding not as expected")
)

899
sign/hdr.pb.go Normal file
View file

@ -0,0 +1,899 @@
// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: sign/hdr.proto
package sign
import (
bytes "bytes"
fmt "fmt"
proto "github.com/gogo/protobuf/proto"
io "io"
math "math"
math_bits "math/bits"
reflect "reflect"
strings "strings"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
type Header struct {
ChunkSize uint32 `protobuf:"varint,1,opt,name=chunk_size,json=chunkSize,proto3" json:"chunk_size,omitempty"`
Salt []byte `protobuf:"bytes,2,opt,name=salt,proto3" json:"salt,omitempty"`
Keys []*WrappedKey `protobuf:"bytes,3,rep,name=keys,proto3" json:"keys,omitempty"`
}
func (m *Header) Reset() { *m = Header{} }
func (*Header) ProtoMessage() {}
func (*Header) Descriptor() ([]byte, []int) {
return fileDescriptor_85aff542c746609f, []int{0}
}
func (m *Header) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *Header) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_Header.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (m *Header) XXX_Merge(src proto.Message) {
xxx_messageInfo_Header.Merge(m, src)
}
func (m *Header) XXX_Size() int {
return m.Size()
}
func (m *Header) XXX_DiscardUnknown() {
xxx_messageInfo_Header.DiscardUnknown(m)
}
var xxx_messageInfo_Header proto.InternalMessageInfo
func (m *Header) GetChunkSize() uint32 {
if m != nil {
return m.ChunkSize
}
return 0
}
func (m *Header) GetSalt() []byte {
if m != nil {
return m.Salt
}
return nil
}
func (m *Header) GetKeys() []*WrappedKey {
if m != nil {
return m.Keys
}
return nil
}
type WrappedKey struct {
PkHash []byte `protobuf:"bytes,1,opt,name=pk_hash,json=pkHash,proto3" json:"pk_hash,omitempty"`
Pk []byte `protobuf:"bytes,2,opt,name=pk,proto3" json:"pk,omitempty"`
Nonce []byte `protobuf:"bytes,3,opt,name=nonce,proto3" json:"nonce,omitempty"`
Key []byte `protobuf:"bytes,4,opt,name=key,proto3" json:"key,omitempty"`
}
func (m *WrappedKey) Reset() { *m = WrappedKey{} }
func (*WrappedKey) ProtoMessage() {}
func (*WrappedKey) Descriptor() ([]byte, []int) {
return fileDescriptor_85aff542c746609f, []int{1}
}
func (m *WrappedKey) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *WrappedKey) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_WrappedKey.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (m *WrappedKey) XXX_Merge(src proto.Message) {
xxx_messageInfo_WrappedKey.Merge(m, src)
}
func (m *WrappedKey) XXX_Size() int {
return m.Size()
}
func (m *WrappedKey) XXX_DiscardUnknown() {
xxx_messageInfo_WrappedKey.DiscardUnknown(m)
}
var xxx_messageInfo_WrappedKey proto.InternalMessageInfo
func (m *WrappedKey) GetPkHash() []byte {
if m != nil {
return m.PkHash
}
return nil
}
func (m *WrappedKey) GetPk() []byte {
if m != nil {
return m.Pk
}
return nil
}
func (m *WrappedKey) GetNonce() []byte {
if m != nil {
return m.Nonce
}
return nil
}
func (m *WrappedKey) GetKey() []byte {
if m != nil {
return m.Key
}
return nil
}
func init() {
proto.RegisterType((*Header)(nil), "sign.header")
proto.RegisterType((*WrappedKey)(nil), "sign.wrapped_key")
}
func init() { proto.RegisterFile("sign/hdr.proto", fileDescriptor_85aff542c746609f) }
var fileDescriptor_85aff542c746609f = []byte{
// 257 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0x90, 0x31, 0x4a, 0xc4, 0x40,
0x14, 0x86, 0xe7, 0x25, 0x31, 0xe2, 0xdb, 0x75, 0xd1, 0x41, 0x30, 0x8d, 0x8f, 0xb0, 0x20, 0xa4,
0x8a, 0xa0, 0x9e, 0xc0, 0xca, 0x3a, 0xf6, 0x86, 0xec, 0x66, 0x70, 0xc2, 0x48, 0x32, 0x64, 0x56,
0x24, 0x5b, 0x79, 0x04, 0x8f, 0xe1, 0x51, 0x2c, 0x53, 0x6e, 0x69, 0x26, 0x8d, 0xe5, 0x1e, 0x41,
0x32, 0x5a, 0xd8, 0xfd, 0xff, 0xf7, 0xe0, 0x7d, 0xf0, 0xe3, 0xc2, 0x54, 0x4f, 0xf5, 0x95, 0x2c,
0xdb, 0x54, 0xb7, 0xcd, 0xa6, 0xe1, 0xc1, 0xd4, 0x97, 0x2b, 0x0c, 0xa5, 0x28, 0x4a, 0xd1, 0xf2,
0x0b, 0xc4, 0xb5, 0x7c, 0xa9, 0x55, 0x6e, 0xaa, 0xad, 0x88, 0x20, 0x86, 0xe4, 0x38, 0x3b, 0x72,
0xe4, 0xa1, 0xda, 0x0a, 0xce, 0x31, 0x30, 0xc5, 0xf3, 0x26, 0xf2, 0x62, 0x48, 0xe6, 0x99, 0xcb,
0xfc, 0x12, 0x03, 0x25, 0x3a, 0x13, 0xf9, 0xb1, 0x9f, 0xcc, 0xae, 0x4f, 0xd3, 0xe9, 0x63, 0xfa,
0xda, 0x16, 0x5a, 0x8b, 0x32, 0x57, 0xa2, 0xcb, 0xdc, 0x79, 0xf9, 0x88, 0xb3, 0x7f, 0x90, 0x9f,
0xe3, 0xa1, 0x56, 0xb9, 0x2c, 0x8c, 0x74, 0x96, 0x79, 0x16, 0x6a, 0x75, 0x5f, 0x18, 0xc9, 0x17,
0xe8, 0x69, 0xf5, 0x27, 0xf0, 0xb4, 0xe2, 0x67, 0x78, 0x50, 0x37, 0xf5, 0x5a, 0x44, 0xbe, 0x43,
0xbf, 0x85, 0x9f, 0xa0, 0xaf, 0x44, 0x17, 0x05, 0x8e, 0x4d, 0xf1, 0xee, 0xb6, 0x1f, 0x88, 0xed,
0x06, 0x62, 0xfb, 0x81, 0xe0, 0xcd, 0x12, 0x7c, 0x58, 0x82, 0x4f, 0x4b, 0xd0, 0x5b, 0x82, 0x2f,
0x4b, 0xf0, 0x6d, 0x89, 0xed, 0x2d, 0xc1, 0xfb, 0x48, 0xac, 0x1f, 0x89, 0xed, 0x46, 0x62, 0xab,
0xd0, 0xcd, 0x70, 0xf3, 0x13, 0x00, 0x00, 0xff, 0xff, 0xde, 0xf2, 0x28, 0xc0, 0x18, 0x01, 0x00,
0x00,
}
func (this *Header) Equal(that interface{}) bool {
if that == nil {
return this == nil
}
that1, ok := that.(*Header)
if !ok {
that2, ok := that.(Header)
if ok {
that1 = &that2
} else {
return false
}
}
if that1 == nil {
return this == nil
} else if this == nil {
return false
}
if this.ChunkSize != that1.ChunkSize {
return false
}
if !bytes.Equal(this.Salt, that1.Salt) {
return false
}
if len(this.Keys) != len(that1.Keys) {
return false
}
for i := range this.Keys {
if !this.Keys[i].Equal(that1.Keys[i]) {
return false
}
}
return true
}
func (this *WrappedKey) Equal(that interface{}) bool {
if that == nil {
return this == nil
}
that1, ok := that.(*WrappedKey)
if !ok {
that2, ok := that.(WrappedKey)
if ok {
that1 = &that2
} else {
return false
}
}
if that1 == nil {
return this == nil
} else if this == nil {
return false
}
if !bytes.Equal(this.PkHash, that1.PkHash) {
return false
}
if !bytes.Equal(this.Pk, that1.Pk) {
return false
}
if !bytes.Equal(this.Nonce, that1.Nonce) {
return false
}
if !bytes.Equal(this.Key, that1.Key) {
return false
}
return true
}
func (this *Header) GoString() string {
if this == nil {
return "nil"
}
s := make([]string, 0, 7)
s = append(s, "&sign.Header{")
s = append(s, "ChunkSize: "+fmt.Sprintf("%#v", this.ChunkSize)+",\n")
s = append(s, "Salt: "+fmt.Sprintf("%#v", this.Salt)+",\n")
if this.Keys != nil {
s = append(s, "Keys: "+fmt.Sprintf("%#v", this.Keys)+",\n")
}
s = append(s, "}")
return strings.Join(s, "")
}
func (this *WrappedKey) GoString() string {
if this == nil {
return "nil"
}
s := make([]string, 0, 8)
s = append(s, "&sign.WrappedKey{")
s = append(s, "PkHash: "+fmt.Sprintf("%#v", this.PkHash)+",\n")
s = append(s, "Pk: "+fmt.Sprintf("%#v", this.Pk)+",\n")
s = append(s, "Nonce: "+fmt.Sprintf("%#v", this.Nonce)+",\n")
s = append(s, "Key: "+fmt.Sprintf("%#v", this.Key)+",\n")
s = append(s, "}")
return strings.Join(s, "")
}
func valueToGoStringHdr(v interface{}, typ string) string {
rv := reflect.ValueOf(v)
if rv.IsNil() {
return "nil"
}
pv := reflect.Indirect(rv).Interface()
return fmt.Sprintf("func(v %v) *%v { return &v } ( %#v )", typ, typ, pv)
}
func (m *Header) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *Header) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *Header) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
if len(m.Keys) > 0 {
for iNdEx := len(m.Keys) - 1; iNdEx >= 0; iNdEx-- {
{
size, err := m.Keys[iNdEx].MarshalToSizedBuffer(dAtA[:i])
if err != nil {
return 0, err
}
i -= size
i = encodeVarintHdr(dAtA, i, uint64(size))
}
i--
dAtA[i] = 0x1a
}
}
if len(m.Salt) > 0 {
i -= len(m.Salt)
copy(dAtA[i:], m.Salt)
i = encodeVarintHdr(dAtA, i, uint64(len(m.Salt)))
i--
dAtA[i] = 0x12
}
if m.ChunkSize != 0 {
i = encodeVarintHdr(dAtA, i, uint64(m.ChunkSize))
i--
dAtA[i] = 0x8
}
return len(dAtA) - i, nil
}
func (m *WrappedKey) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *WrappedKey) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *WrappedKey) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
if len(m.Key) > 0 {
i -= len(m.Key)
copy(dAtA[i:], m.Key)
i = encodeVarintHdr(dAtA, i, uint64(len(m.Key)))
i--
dAtA[i] = 0x22
}
if len(m.Nonce) > 0 {
i -= len(m.Nonce)
copy(dAtA[i:], m.Nonce)
i = encodeVarintHdr(dAtA, i, uint64(len(m.Nonce)))
i--
dAtA[i] = 0x1a
}
if len(m.Pk) > 0 {
i -= len(m.Pk)
copy(dAtA[i:], m.Pk)
i = encodeVarintHdr(dAtA, i, uint64(len(m.Pk)))
i--
dAtA[i] = 0x12
}
if len(m.PkHash) > 0 {
i -= len(m.PkHash)
copy(dAtA[i:], m.PkHash)
i = encodeVarintHdr(dAtA, i, uint64(len(m.PkHash)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func encodeVarintHdr(dAtA []byte, offset int, v uint64) int {
offset -= sovHdr(v)
base := offset
for v >= 1<<7 {
dAtA[offset] = uint8(v&0x7f | 0x80)
v >>= 7
offset++
}
dAtA[offset] = uint8(v)
return base
}
func (m *Header) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
if m.ChunkSize != 0 {
n += 1 + sovHdr(uint64(m.ChunkSize))
}
l = len(m.Salt)
if l > 0 {
n += 1 + l + sovHdr(uint64(l))
}
if len(m.Keys) > 0 {
for _, e := range m.Keys {
l = e.Size()
n += 1 + l + sovHdr(uint64(l))
}
}
return n
}
func (m *WrappedKey) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = len(m.PkHash)
if l > 0 {
n += 1 + l + sovHdr(uint64(l))
}
l = len(m.Pk)
if l > 0 {
n += 1 + l + sovHdr(uint64(l))
}
l = len(m.Nonce)
if l > 0 {
n += 1 + l + sovHdr(uint64(l))
}
l = len(m.Key)
if l > 0 {
n += 1 + l + sovHdr(uint64(l))
}
return n
}
func sovHdr(x uint64) (n int) {
return (math_bits.Len64(x|1) + 6) / 7
}
func sozHdr(x uint64) (n int) {
return sovHdr(uint64((x << 1) ^ uint64((int64(x) >> 63))))
}
func (this *Header) String() string {
if this == nil {
return "nil"
}
repeatedStringForKeys := "[]*WrappedKey{"
for _, f := range this.Keys {
repeatedStringForKeys += strings.Replace(fmt.Sprintf("%v", f), "WrappedKey", "WrappedKey", 1) + ","
}
repeatedStringForKeys += "}"
s := strings.Join([]string{`&Header{`,
`ChunkSize:` + fmt.Sprintf("%v", this.ChunkSize) + `,`,
`Salt:` + fmt.Sprintf("%v", this.Salt) + `,`,
`Keys:` + repeatedStringForKeys + `,`,
`}`,
}, "")
return s
}
func (this *WrappedKey) String() string {
if this == nil {
return "nil"
}
s := strings.Join([]string{`&WrappedKey{`,
`PkHash:` + fmt.Sprintf("%v", this.PkHash) + `,`,
`Pk:` + fmt.Sprintf("%v", this.Pk) + `,`,
`Nonce:` + fmt.Sprintf("%v", this.Nonce) + `,`,
`Key:` + fmt.Sprintf("%v", this.Key) + `,`,
`}`,
}, "")
return s
}
func valueToStringHdr(v interface{}) string {
rv := reflect.ValueOf(v)
if rv.IsNil() {
return "nil"
}
pv := reflect.Indirect(rv).Interface()
return fmt.Sprintf("*%v", pv)
}
func (m *Header) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowHdr
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: header: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: header: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field ChunkSize", wireType)
}
m.ChunkSize = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowHdr
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.ChunkSize |= uint32(b&0x7F) << shift
if b < 0x80 {
break
}
}
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Salt", wireType)
}
var byteLen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowHdr
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
byteLen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if byteLen < 0 {
return ErrInvalidLengthHdr
}
postIndex := iNdEx + byteLen
if postIndex < 0 {
return ErrInvalidLengthHdr
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Salt = append(m.Salt[:0], dAtA[iNdEx:postIndex]...)
if m.Salt == nil {
m.Salt = []byte{}
}
iNdEx = postIndex
case 3:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Keys", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowHdr
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthHdr
}
postIndex := iNdEx + msglen
if postIndex < 0 {
return ErrInvalidLengthHdr
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Keys = append(m.Keys, &WrappedKey{})
if err := m.Keys[len(m.Keys)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipHdr(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthHdr
}
if (iNdEx + skippy) < 0 {
return ErrInvalidLengthHdr
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *WrappedKey) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowHdr
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: wrapped_key: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: wrapped_key: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field PkHash", wireType)
}
var byteLen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowHdr
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
byteLen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if byteLen < 0 {
return ErrInvalidLengthHdr
}
postIndex := iNdEx + byteLen
if postIndex < 0 {
return ErrInvalidLengthHdr
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.PkHash = append(m.PkHash[:0], dAtA[iNdEx:postIndex]...)
if m.PkHash == nil {
m.PkHash = []byte{}
}
iNdEx = postIndex
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Pk", wireType)
}
var byteLen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowHdr
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
byteLen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if byteLen < 0 {
return ErrInvalidLengthHdr
}
postIndex := iNdEx + byteLen
if postIndex < 0 {
return ErrInvalidLengthHdr
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Pk = append(m.Pk[:0], dAtA[iNdEx:postIndex]...)
if m.Pk == nil {
m.Pk = []byte{}
}
iNdEx = postIndex
case 3:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Nonce", wireType)
}
var byteLen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowHdr
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
byteLen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if byteLen < 0 {
return ErrInvalidLengthHdr
}
postIndex := iNdEx + byteLen
if postIndex < 0 {
return ErrInvalidLengthHdr
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Nonce = append(m.Nonce[:0], dAtA[iNdEx:postIndex]...)
if m.Nonce == nil {
m.Nonce = []byte{}
}
iNdEx = postIndex
case 4:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType)
}
var byteLen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowHdr
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
byteLen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if byteLen < 0 {
return ErrInvalidLengthHdr
}
postIndex := iNdEx + byteLen
if postIndex < 0 {
return ErrInvalidLengthHdr
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Key = append(m.Key[:0], dAtA[iNdEx:postIndex]...)
if m.Key == nil {
m.Key = []byte{}
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipHdr(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthHdr
}
if (iNdEx + skippy) < 0 {
return ErrInvalidLengthHdr
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func skipHdr(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
depth := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowHdr
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
wireType := int(wire & 0x7)
switch wireType {
case 0:
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowHdr
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
iNdEx++
if dAtA[iNdEx-1] < 0x80 {
break
}
}
case 1:
iNdEx += 8
case 2:
var length int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowHdr
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
length |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
if length < 0 {
return 0, ErrInvalidLengthHdr
}
iNdEx += length
case 3:
depth++
case 4:
if depth == 0 {
return 0, ErrUnexpectedEndOfGroupHdr
}
depth--
case 5:
iNdEx += 4
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
if iNdEx < 0 {
return 0, ErrInvalidLengthHdr
}
if depth == 0 {
return iNdEx, nil
}
}
return 0, io.ErrUnexpectedEOF
}
var (
ErrInvalidLengthHdr = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowHdr = fmt.Errorf("proto: integer overflow")
ErrUnexpectedEndOfGroupHdr = fmt.Errorf("proto: unexpected end of group")
)

23
sign/hdr.proto Normal file
View file

@ -0,0 +1,23 @@
syntax="proto3";
//import "gogoproto/gogo.proto"
package sign;
//option (gogoproto.marshaler_all) = true;
//option (gogoproto.sizer_all) = true;
//option (gogoproto.unmarshaler_all) = true;
//option (gogoproto.goproto_getters_all) = false;
message header {
uint32 chunk_size = 1;
bytes salt = 2;
repeated wrapped_key keys = 3;
}
message wrapped_key {
bytes pk_hash = 1; // hash of Ed25519 PK
bytes pk = 2; // curve25519 PK
bytes nonce = 3; // AEAD nonce
bytes key = 4; // AEAD encrypted key
}

View file

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

View file

@ -1,529 +0,0 @@
// keys.go -- Ed25519 keys management
//
// (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.
// This file implements:
// - key generation, and key I/O
// - sign/verify of files and byte strings
package sign
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"crypto/sha512"
"encoding/base64"
"fmt"
"io/ioutil"
"math/big"
Ed "crypto/ed25519"
"golang.org/x/crypto/scrypt"
"gopkg.in/yaml.v3"
)
// Private Ed25519 key
type PrivateKey struct {
Sk []byte
// Encryption key: Curve25519 point corresponding to this Ed25519 key
ck []byte
// Cached copy of the public key
pk *PublicKey
}
// Public Ed25519 key
type PublicKey struct {
Pk []byte
// Comment string
Comment string
// Curve25519 point corresponding to this Ed25519 key
ck []byte
hash []byte
}
// Length of Ed25519 Public Key Hash
const PKHashLength = 16
// constants we use in this module
const (
// Scrypt parameters
_N int = 1 << 19
_r int = 8
_p int = 1
// Algorithm used in the encrypted private key
sk_algo = "scrypt-sha256"
sig_algo = "sha512-ed25519"
)
// Encrypted Private key
type serializedPrivKey struct {
Comment string `yaml:"comment,omitempty"`
// Encrypted Sk
Esk string `yaml:"esk"`
Salt string `yaml:"salt,omitempty"`
// Algorithm used for checksum and KDF
Algo string `yaml:"algo,omitempty"`
// These are params for scrypt.Key()
// CPU Cost parameter; must be a power of 2
N int `yaml:"Z,flow,omitempty"`
// r * p should be less than 2^30
R int `yaml:"r,flow,omitempty"`
P int `yaml:"p,flow,omitempty"`
}
// serialized representation of public key
type serializedPubKey struct {
Comment string `yaml:"comment,omitempty"`
Pk string `yaml:"pk"`
Hash string `yaml:"hash"`
}
// Serialized signature
type signature struct {
Comment string `yaml:"comment,omitempty"`
Pkhash string `yaml:"pkhash,omitempty"`
Signature string `yaml:"signature"`
}
// given a public key, generate a deterministic short-hash of it.
func pkhash(pk []byte) []byte {
z := sha256.Sum256(pk)
return z[:PKHashLength]
}
// NewPrivateKey generates a new Ed25519 private key
func NewPrivateKey() (*PrivateKey, error) {
pkb, skb, err := Ed.GenerateKey(rand.Reader)
if err != nil {
return nil, err
}
sk := &PrivateKey{
Sk: []byte(skb),
pk: &PublicKey{
Pk: []byte(pkb),
hash: pkhash([]byte(pkb)),
},
}
return sk, nil
}
// Read the private key in 'fn', optionally decrypting it using
// password 'pw' and create new instance of PrivateKey
func ReadPrivateKey(fn string, getpw func() ([]byte, error)) (*PrivateKey, error) {
yml, err := ioutil.ReadFile(fn)
if err != nil {
return nil, err
}
var sk PrivateKey
if err = sk.UnmarshalBinary(yml, getpw); err != nil {
return nil, err
}
return &sk, nil
}
// Make a private key from bytes 'yml' using optional caller provided
// getpw() function to read the password if needed.
// are assumed to be serialized version of the private key.
func MakePrivateKey(yml []byte, getpw func() ([]byte, error)) (*PrivateKey, error) {
var sk PrivateKey
err := sk.UnmarshalBinary(yml, getpw)
if err != nil {
return nil, err
}
return &sk, nil
}
// make a PrivateKey from a byte array containing ed25519 raw SK
func makePrivateKeyFromBytes(sk *PrivateKey, buf []byte) error {
if len(buf) != 64 {
return fmt.Errorf("private key is malformed (len %d!)", len(buf))
}
skb := make([]byte, 64)
copy(skb, buf)
edsk := Ed.PrivateKey(skb)
edpk := edsk.Public().(Ed.PublicKey)
pk := &PublicKey{
Pk: []byte(edpk),
hash: pkhash([]byte(edpk)),
}
sk.Sk = skb
sk.pk = pk
return nil
}
// Make a private key from 64-bytes of extended Ed25519 key
func PrivateKeyFromBytes(buf []byte) (*PrivateKey, error) {
var sk PrivateKey
if err := makePrivateKeyFromBytes(&sk, buf); err != nil {
return nil, err
}
return &sk, nil
}
// Given a secret key, return the corresponding Public Key
func (sk *PrivateKey) PublicKey() *PublicKey {
return sk.pk
}
// Convert an Ed25519 Private Key to Curve25519 Private key
func (sk *PrivateKey) ToCurve25519SK() []byte {
if sk.ck == nil {
var ek [64]byte
h := sha512.New()
h.Write(sk.Sk[:32])
h.Sum(ek[:0])
sk.ck = clamp(ek[:32])
}
return sk.ck
}
// Serialize the private key to file 'fn' using human readable
// 'comment' and encrypt the key with supplied passphrase 'pw'.
func (sk *PrivateKey) Serialize(fn, comment string, ovwrite bool, pw []byte) error {
b, err := sk.MarshalBinary(comment, pw)
if err == nil {
return writeFile(fn, b, ovwrite, 0600)
}
return err
}
// MarshalBinary marshals the private key with a caller provided
// passphrase 'pw' and human readable 'comment'
func (sk *PrivateKey) MarshalBinary(comment string, pw []byte) ([]byte, error) {
// expand the password into 64 bytes
pass := sha512.Sum512(pw)
salt := make([]byte, 32)
randRead(salt)
// "32" == Length of AES-256 key
key, err := scrypt.Key(pass[:], salt, _N, _r, _p, 32)
if err != nil {
return nil, fmt.Errorf("marshal: can't derive scrypt key: %s", err)
}
aes, err := aes.NewCipher(key)
if err != nil {
return nil, fmt.Errorf("marshal: %s", err)
}
ae, err := cipher.NewGCM(aes)
if err != nil {
return nil, fmt.Errorf("marshal: %s", err)
}
tl := ae.Overhead()
buf := make([]byte, tl+len(sk.Sk))
esk := ae.Seal(buf[:0], salt[:ae.NonceSize()], sk.Sk, nil)
enc := base64.StdEncoding.EncodeToString
ssk := serializedPrivKey{
Comment: comment,
Esk: enc(esk),
Salt: enc(salt),
Algo: sk_algo,
N: _N,
R: _r,
P: _p,
}
// We won't protect the Scrypt parameters with the hash above
// because it is not needed. If the parameters are wrong, the
// derived key will be wrong and thus, the hash will not match.
return yaml.Marshal(&ssk)
}
// UnmarshalBinary unmarshals the private key and optionally invokes the
// caller provided getpw() function to read the password if needed. If the
// input byte stream 'b' is an OpenSSH ed25519 key, this function transparently
// decodes it.
func (sk *PrivateKey) UnmarshalBinary(b []byte, getpw func() ([]byte, error)) error {
if bytes.Index(b, []byte("OPENSSH PRIVATE KEY-")) > 0 {
xk, err := parseSSHPrivateKey(b, getpw)
if err != nil {
return err
}
*sk = *xk
return nil
}
var pw []byte
if getpw != nil {
var err error
pw, err = getpw()
if err != nil {
return err
}
}
// We take short passwords and extend them
pwb := sha512.Sum512(pw)
var ssk serializedPrivKey
err := yaml.Unmarshal(b, &ssk)
if err != nil {
return fmt.Errorf("unmarshal priv key: can't parse YAML: %s", err)
}
if len(ssk.Salt) == 0 || len(ssk.Esk) == 0 {
return fmt.Errorf("unmarshal priv key: not YAML format")
}
b64 := base64.StdEncoding.DecodeString
salt, err := b64(ssk.Salt)
if err != nil {
return fmt.Errorf("unmarshal priv key: can't decode salt: %s", err)
}
esk, err := b64(ssk.Esk)
if err != nil {
return fmt.Errorf("unmarshal priv key: can't decode key: %s", err)
}
// "32" == Length of AES-256 key
key, err := scrypt.Key(pwb[:], salt, ssk.N, ssk.R, ssk.P, 32)
if err != nil {
return fmt.Errorf("unmarshal priv key: can't derive key: %s", err)
}
aes, err := aes.NewCipher(key)
if err != nil {
return fmt.Errorf("unmarshal priv key: aes failure: %s", err)
}
ae, err := cipher.NewGCM(aes)
if err != nil {
return fmt.Errorf("unmarshal priv key: aes failure: %s", err)
}
skb := make([]byte, 64)
skb, err = ae.Open(skb[:0], salt[:ae.NonceSize()], esk, nil)
if err != nil {
return fmt.Errorf("unmarshal priv key: wrong password")
}
return makePrivateKeyFromBytes(sk, skb)
}
// --- Public Key Methods ---
// Read the public key from 'fn' and create new instance of
// PublicKey
func ReadPublicKey(fn string) (*PublicKey, error) {
var err error
var yml []byte
if yml, err = ioutil.ReadFile(fn); err != nil {
return nil, err
}
var pk PublicKey
if err = pk.UnmarshalBinary(yml); err != nil {
return nil, err
}
return &pk, nil
}
// Parse a serialized public in 'yml' and return the resulting
// public key instance
func MakePublicKey(yml []byte) (*PublicKey, error) {
var pk PublicKey
if err := pk.UnmarshalBinary(yml); err != nil {
return nil, err
}
return &pk, nil
}
// Make a public key from a string
func MakePublicKeyFromString(s string) (*PublicKey, error) {
// first try to decode it as a openssh key
if pk2, err := parseEncPubKey([]byte(s), "command-line-pk"); err == nil {
return pk2, nil
}
// Now try to decode as an sigtool key
b64 := base64.StdEncoding.DecodeString
pkb, err := b64(s)
if err != nil {
return nil, err
}
var pk PublicKey
err = makePublicKeyFromBytes(&pk, pkb)
if err != nil {
return nil, err
}
return &pk, nil
}
func makePublicKeyFromBytes(pk *PublicKey, b []byte) error {
if len(b) != 32 {
return fmt.Errorf("public key is malformed (len %d!)", len(b))
}
pk.Pk = make([]byte, 32)
pk.hash = pkhash(b)
copy(pk.Pk, b)
return nil
}
// Make a public key from a byte string
func PublicKeyFromBytes(b []byte) (*PublicKey, error) {
var pk PublicKey
if err := makePublicKeyFromBytes(&pk, b); err != nil {
return nil, err
}
return &pk, nil
}
// Serialize a PublicKey into file 'fn' with a human readable 'comment'.
// If 'ovwrite' is true, overwrite the file if it exists.
func (pk *PublicKey) Serialize(fn, comment string, ovwrite bool) error {
out, err := pk.MarshalBinary(comment)
if err == nil {
return writeFile(fn, out, ovwrite, 0644)
}
return err
}
// from github.com/FiloSottile/age
var curve25519P, _ = new(big.Int).SetString("57896044618658097711785492504343953926634992332820282019728792003956564819949", 10)
// Convert an Ed25519 Public Key to Curve25519 public key
// from github.com/FiloSottile/age
func (pk *PublicKey) ToCurve25519PK() []byte {
if pk.ck != nil {
return pk.ck
}
// ed25519.PublicKey is a little endian representation of the y-coordinate,
// with the most significant bit set based on the sign of the x-ccordinate.
bigEndianY := make([]byte, Ed.PublicKeySize)
for i, b := range pk.Pk {
bigEndianY[Ed.PublicKeySize-i-1] = b
}
bigEndianY[0] &= 0b0111_1111
// The Montgomery u-coordinate is derived through the bilinear map
//
// u = (1 + y) / (1 - y)
//
// See https://blog.filippo.io/using-ed25519-keys-for-encryption.
y := new(big.Int).SetBytes(bigEndianY)
denom := big.NewInt(1)
denom.ModInverse(denom.Sub(denom, y), curve25519P) // 1 / (1 - y)
u := y.Mul(y.Add(y, big.NewInt(1)), denom)
u.Mod(u, curve25519P)
out := make([]byte, 32)
uBytes := u.Bytes()
n := len(uBytes)
for i, b := range uBytes {
out[n-i-1] = b
}
pk.ck = out
return out
}
// Public Key Hash
func (pk *PublicKey) Hash() []byte {
return pk.hash
}
// MarshalBinary marshals a PublicKey into a byte array
func (pk *PublicKey) MarshalBinary(comment string) ([]byte, error) {
b64 := base64.StdEncoding.EncodeToString
spk := &serializedPubKey{
Comment: comment,
Pk: b64(pk.Pk),
Hash: b64(pk.hash),
}
return yaml.Marshal(spk)
}
// UnmarshalBinary constructs a PublicKey from a previously
// marshaled byte stream instance. In addition, it is also
// capable of parsing an OpenSSH ed25519 public key.
func (pk *PublicKey) UnmarshalBinary(yml []byte) error {
// first try to parse as a ssh key
if xk, err := parseSSHPublicKey(yml); err == nil {
*pk = *xk
return nil
}
// OK Yaml it is.
var spk serializedPubKey
var err error
if err = yaml.Unmarshal(yml, &spk); err != nil {
return fmt.Errorf("can't parse YAML: %s", err)
}
if len(spk.Pk) == 0 {
return fmt.Errorf("sign: not a YAML public key")
}
b64 := base64.StdEncoding.DecodeString
var pkb []byte
if pkb, err = b64(spk.Pk); err != nil {
return fmt.Errorf("can't decode YAML:Pk: %s", err)
}
return makePublicKeyFromBytes(pk, pkb)
}
// -- Internal Utility Functions --
func clamp(k []byte) []byte {
k[0] &= 248
k[31] &= 127
k[31] |= 64
return k
}
// EOF
// vim: noexpandtab:ts=8:sw=8:tw=92:

View file

@ -1,45 +0,0 @@
// rand.go - utility functions to generate random quantities
//
// (c) 2018 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 (
"crypto/rand"
"encoding/binary"
"fmt"
"io"
)
func randu32() uint32 {
var b [4]byte
_, err := io.ReadFull(rand.Reader, b[:])
if err != nil {
panic(fmt.Sprintf("can't read 4 rand bytes: %s", err))
}
return binary.LittleEndian.Uint32(b[:])
}
func randRead(b []byte) []byte {
_, err := io.ReadFull(rand.Reader, b)
if err != nil {
panic(fmt.Sprintf("can't read %d bytes of random data: %s", len(b), err))
}
return b
}
func randBuf(sz int) []byte {
b := make([]byte, sz)
return randRead(b)
}

View file

@ -11,44 +11,344 @@
// warranty; it is provided "as is". No claim is made to its
// suitability for any purpose.
// This file implements:
// - key generation, and key I/O
// - sign/verify of files and byte strings
// Package sign implements Ed25519 signing, verification on files.
// It builds upon golang.org/x/crypto/ed25519 by adding methods
// for serializing and deserializing Ed25519 private & public keys.
// In addition, it works with large files - by precalculating their
// SHA512 checksum in mmap'd mode and sending the 64 byte signature
// for Ed25519 signing.
package sign
import (
"crypto"
"crypto/rand"
"crypto/sha256"
"crypto/sha512"
"crypto/subtle"
"encoding/base64"
"encoding/binary"
"fmt"
"hash"
"io"
"io/ioutil"
"os"
Ed "crypto/ed25519"
"golang.org/x/crypto/scrypt"
"gopkg.in/yaml.v2"
"gopkg.in/yaml.v3"
"github.com/opencoff/go-utils"
)
// Private Ed25519 key
type PrivateKey struct {
Sk []byte
// Encryption key: Curve25519 point corresponding to this Ed25519 key
ck []byte
// Cached copy of the public key
pk *PublicKey
}
// Public Ed25519 key
type PublicKey struct {
Pk []byte
// Curve25519 point corresponding to this Ed25519 key
ck []byte
hash []byte
}
// Ed25519 key pair
type Keypair struct {
Sec PrivateKey
Pub PublicKey
}
// An Ed25519 Signature
type Signature struct {
Sig []byte // Ed25519 sig bytes
pkhash []byte // [0:16] SHA256 hash of public key needed for verification
}
// Algorithm used in the encrypted private key
const sk_algo = "scrypt-sha256"
const sig_algo = "sha512-ed25519"
// Length of Ed25519 Public Key Hash
const PKHashLength = 16
// Scrypt parameters
const _N = 1 << 17
const _r = 16
const _p = 1
// Encrypted Private key
type encPrivKey struct {
// Encrypted Sk
Esk []byte
// parameters for Sk serialization
Salt []byte
// Algorithm used for checksum and KDF
Algo string
// Checksum to verify passphrase before we xor it
Verify []byte
// These are params for scrypt.Key()
// CPU Cost parameter; must be a power of 2
N uint32
// r * p should be less than 2^30
r uint32
p uint32
}
// Serialized representation of private key
type serializedPrivKey struct {
Comment string `yaml:"comment,omitempty"`
Esk string `yaml:"esk"`
Salt string `yaml:"salt,omitempty"`
Algo string `yaml:"algo,omitempty"`
Verify string `yaml:"verify,omitempty"`
N uint32 `yaml:"Z,flow,omitempty"`
R uint32 `yaml:"r,flow,omitempty"`
P uint32 `yaml:"p,flow,omitempty"`
}
// serialized representation of public key
type serializedPubKey struct {
Comment string `yaml:"comment,omitempty"`
Pk string `yaml:"pk"`
Hash string `yaml:"hash"`
}
// Serialized signature
type signature struct {
Comment string `yaml:"comment,omitempty"`
Pkhash string `yaml:"pkhash,omitempty"`
Signature string `yaml:"signature"`
}
func pkhash(pk []byte) []byte {
z := sha256.Sum256(pk)
return z[:PKHashLength]
}
// Generate a new Ed25519 keypair
func NewKeypair() (*Keypair, error) {
//kp := &Keypair{Sec: PrivateKey{N: 1 << 17, r: 64, p: 1}}
kp := &Keypair{}
sk := &kp.Sec
pk := &kp.Pub
sk.pk = pk
p, s, err := Ed.GenerateKey(rand.Reader)
if err != nil {
return nil, fmt.Errorf("Can't generate Ed25519 keys: %s", err)
}
pk.Pk = []byte(p)
sk.Sk = []byte(s)
pk.hash = pkhash(pk.Pk)
return kp, nil
}
// Serialize the keypair to two separate files. The basename of the
// file is 'bn'; the public key goes in $bn.pub and the private key
// goes in $bn.key.
// If password is non-empty, then the private key is encrypted
// before writing to disk.
func (kp *Keypair) Serialize(bn, comment string, pw string) error {
sk := &kp.Sec
pk := &kp.Pub
skf := fmt.Sprintf("%s.key", bn)
pkf := fmt.Sprintf("%s.pub", bn)
err := pk.serialize(pkf, comment)
if err != nil {
return fmt.Errorf("Can't serialize to %s: %s", pkf, err)
}
err = sk.serialize(skf, comment, pw)
if err != nil {
return fmt.Errorf("Can't serialize to %s: %s", pkf, err)
}
return nil
}
// Read the private key in 'fn', optionally decrypting it using
// password 'pw' and create new instance of PrivateKey
func ReadPrivateKey(fn string, pw string) (*PrivateKey, error) {
yml, err := ioutil.ReadFile(fn)
if err != nil {
return nil, err
}
return MakePrivateKey(yml, pw)
}
// Make a private key from bytes 'yml' and password 'pw'. The bytes
// are assumed to be serialized version of the private key.
func MakePrivateKey(yml []byte, pw string) (*PrivateKey, error) {
var ssk serializedPrivKey
err := yaml.Unmarshal(yml, &ssk)
if err != nil {
return nil, fmt.Errorf("can't parse YAML: %s", err)
}
esk := &encPrivKey{N: ssk.N, r: ssk.R, p: ssk.P, Algo: ssk.Algo}
b64 := base64.StdEncoding.DecodeString
esk.Esk, err = b64(ssk.Esk)
if err != nil {
return nil, fmt.Errorf("can't decode YAML:Esk: %s", err)
}
esk.Salt, err = b64(ssk.Salt)
if err != nil {
return nil, fmt.Errorf("can't decode YAML:Salt: %s", err)
}
esk.Verify, err = b64(ssk.Verify)
if err != nil {
return nil, fmt.Errorf("can't decode YAML:Verify: %s", err)
}
// We take short passwords and extend them
pwb := sha512.Sum512([]byte(pw))
xork, err := scrypt.Key(pwb[:], esk.Salt, int(esk.N), int(esk.r), int(esk.p), len(esk.Esk))
if err != nil {
return nil, fmt.Errorf("can't derive key: %s", err)
}
hh := sha256.New()
hh.Write(esk.Salt)
hh.Write(xork)
ck := hh.Sum(nil)
if subtle.ConstantTimeCompare(esk.Verify, ck) != 1 {
return nil, fmt.Errorf("incorrect private key password")
}
// Everything works. Now, decode the key
skb := make([]byte, len(esk.Esk))
for i := 0; i < len(esk.Esk); i++ {
skb[i] = esk.Esk[i] ^ xork[i]
}
return PrivateKeyFromBytes(skb)
}
// Make a private key from 64-bytes of extended Ed25519 key
func PrivateKeyFromBytes(skb []byte) (*PrivateKey, error) {
if len(skb) != 64 {
return nil, fmt.Errorf("private key is malformed (len %d!)", len(skb))
}
edsk := Ed.PrivateKey(skb)
edpk := edsk.Public().(Ed.PublicKey)
pk := &PublicKey{
Pk: []byte(edpk),
hash: pkhash([]byte(edpk)),
}
sk := &PrivateKey{
Sk: skb,
pk: pk,
}
return sk, nil
}
// Given a secret key, return the corresponding Public Key
func (sk *PrivateKey) PublicKey() *PublicKey {
return sk.pk
}
// Public Key Hash
func (pk *PublicKey) Hash() []byte {
return pk.hash
}
// Serialize the private key to a file
// Format: YAML
// All []byte are in base64 (RawEncoding)
func (sk *PrivateKey) serialize(fn, comment string, pw string) error {
b64 := base64.StdEncoding.EncodeToString
esk := &encPrivKey{}
ssk := &serializedPrivKey{Comment: comment}
// Even with an empty password, we still encrypt and store.
// expand the password into 64 bytes
pwb := sha512.Sum512([]byte(pw))
esk.N = _N
esk.r = _r
esk.p = _p
esk.Salt = make([]byte, 32)
esk.Esk = make([]byte, len(sk.Sk))
randread(esk.Salt)
xork, err := scrypt.Key(pwb[:], esk.Salt, int(esk.N), int(esk.r), int(esk.p), len(sk.Sk))
if err != nil {
return fmt.Errorf("Can't derive scrypt key: %s", err)
}
hh := sha256.New()
hh.Write(esk.Salt)
hh.Write(xork)
esk.Verify = hh.Sum(nil)
// We won't protect the Scrypt parameters with the hash above
// because it is not needed. If the parameters are wrong, the
// derived key will be wrong and thus, the hash will not match.
esk.Algo = sk_algo // global var
// Finally setup the encrypted key
for i := 0; i < len(sk.Sk); i++ {
esk.Esk[i] = sk.Sk[i] ^ xork[i]
}
ssk.Esk = b64(esk.Esk)
ssk.Salt = b64(esk.Salt)
ssk.Verify = b64(esk.Verify)
ssk.Algo = esk.Algo
ssk.N = esk.N
ssk.R = esk.r
ssk.P = esk.p
out, err := yaml.Marshal(ssk)
if err != nil {
return fmt.Errorf("can't marahal to YAML: %s", err)
}
return writeFile(fn, out, 0600)
}
// Sign a prehashed Message; return the signature as opaque bytes
// Signature is an YAML file:
//
// Comment: source file path
// Signature: Ed25519 signature
// Comment: source file path
// Signature: Ed25519 signature
func (sk *PrivateKey) SignMessage(ck []byte, comment string) (*Signature, error) {
h := sha512.New()
h.Write([]byte("sigtool signed message"))
h.Write(ck)
ck = h.Sum(nil)[:]
x := Ed.PrivateKey(sk.Sk)
sig, err := x.Sign(rand.Reader, ck, crypto.Hash(0))
if err != nil {
return nil, fmt.Errorf("can't sign %x: %s", ck, err)
@ -88,13 +388,12 @@ func ReadSignature(fn string) (*Signature, error) {
return nil, err
}
var sig Signature
return makeSignature(&sig, yml)
return MakeSignature(yml)
}
// Parse serialized signature from bytes 'b' and construct a
// Signature object
func makeSignature(sig *Signature, b []byte) (*Signature, error) {
func MakeSignature(b []byte) (*Signature, error) {
var ss signature
err := yaml.Unmarshal(b, &ss)
if err != nil {
@ -113,33 +412,29 @@ func makeSignature(sig *Signature, b []byte) (*Signature, error) {
return nil, fmt.Errorf("can't decode Base64:Pkhash <%s>: %s", ss.Pkhash, err)
}
sig.Sig = s
sig.pkhash = p
return sig, nil
return &Signature{Sig: s, pkhash: p}, nil
}
// MarshalBinary marshals a signature into a byte stream with
// an optional caller supplied comment.
func (sig *Signature) MarshalBinary(comment string) ([]byte, error) {
// Serialize a signature suitable for storing in durable media
func (sig *Signature) Serialize(comment string) ([]byte, error) {
sigs := base64.StdEncoding.EncodeToString(sig.Sig)
pks := base64.StdEncoding.EncodeToString(sig.pkhash)
ss := &signature{Comment: comment, Pkhash: pks, Signature: sigs}
return yaml.Marshal(ss)
out, err := yaml.Marshal(ss)
if err != nil {
return nil, fmt.Errorf("can't marshal signature of %x to YAML: %s", sig.Sig, err)
}
return out, nil
}
// UnmarshalBinary constructs a Signature from a previously
// serialized bytestream
func (sig *Signature) UnmarshalBinary(b []byte) error {
_, err := makeSignature(sig, b)
return err
}
// Serialize a signature suitable for storing in durable media
func (sig *Signature) Serialize(fn, comment string, ovwrite bool) error {
b, err := sig.MarshalBinary(comment)
// SerializeFile serializes the signature to an output file 'f'
func (sig *Signature) SerializeFile(fn, comment string) error {
b, err := sig.Serialize(comment)
if err == nil {
err = writeFile(fn, b, ovwrite, 0644)
err = writeFile(fn, b, 0644)
}
return err
}
@ -151,27 +446,163 @@ func (sig *Signature) IsPKMatch(pk *PublicKey) bool {
return subtle.ConstantTimeCompare(pk.hash, sig.pkhash) == 1
}
// --- Public Key Methods ---
// Read the public key from 'fn' and create new instance of
// PublicKey
func ReadPublicKey(fn string) (*PublicKey, error) {
var err error
var yml []byte
if yml, err = ioutil.ReadFile(fn); err != nil {
return nil, err
}
return MakePublicKey(yml)
}
// Parse a serialized public in 'yml' and return the resulting
// public key instance
func MakePublicKey(yml []byte) (*PublicKey, error) {
var spk serializedPubKey
var err error
if err = yaml.Unmarshal(yml, &spk); err != nil {
return nil, fmt.Errorf("can't parse YAML: %s", err)
}
b64 := base64.StdEncoding.DecodeString
var pk []byte
if pk, err = b64(spk.Pk); err != nil {
return nil, fmt.Errorf("can't decode YAML:Pk: %s", err)
}
return PublicKeyFromBytes(pk)
}
// Make a public key from a byte string
func PublicKeyFromBytes(b []byte) (*PublicKey, error) {
if len(b) != 32 {
return nil, fmt.Errorf("public key is malformed (len %d!)", len(b))
}
pk := &PublicKey{
Pk: make([]byte, 32),
hash: pkhash(b),
}
copy(pk.Pk, b)
return pk, nil
}
// Serialize Public Keys
func (pk *PublicKey) serialize(fn, comment string) error {
b64 := base64.StdEncoding.EncodeToString
spk := &serializedPubKey{
Comment: comment,
Pk: b64(pk.Pk),
Hash: b64(pk.hash),
}
out, err := yaml.Marshal(spk)
if err != nil {
return fmt.Errorf("can't marahal to YAML: %s", err)
}
return writeFile(fn, out, 0644)
}
// Verify a signature 'sig' for file 'fn' against public key 'pk'
// Return True if signature matches, False otherwise
func (pk *PublicKey) VerifyFile(fn string, sig *Signature) (bool, error) {
ck, err := fileCksum(fn, sha512.New())
if err != nil {
return false, err
}
return pk.VerifyMessage(ck, sig), nil
return pk.VerifyMessage(ck, sig)
}
// Verify a signature 'sig' for a pre-calculated checksum 'ck' against public key 'pk'
// Return True if signature matches, False otherwise
func (pk *PublicKey) VerifyMessage(ck []byte, sig *Signature) bool {
h := sha512.New()
h.Write([]byte("sigtool signed message"))
h.Write(ck)
ck = h.Sum(nil)[:]
func (pk *PublicKey) VerifyMessage(ck []byte, sig *Signature) (bool, error) {
x := Ed.PublicKey(pk.Pk)
return Ed.Verify(x, ck, sig.Sig)
return Ed.Verify(x, ck, sig.Sig), nil
}
// -- Internal Utility Functions --
// Unlink a file.
func unlink(f string) {
st, err := os.Stat(f)
if err == nil {
if !st.Mode().IsRegular() {
panic(fmt.Sprintf("%s can't be unlinked. Not a regular file?", f))
}
os.Remove(f)
return
}
}
// 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, mode uint32) error {
tmp := fmt.Sprintf("%s.tmp", fn)
unlink(tmp)
fd, err := os.OpenFile(tmp, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.FileMode(mode))
if err != nil {
return fmt.Errorf("Can't create file %s: %s", tmp, err)
}
_, err = fd.Write(b)
if err != nil {
fd.Close()
// XXX Do we delete the tmp file?
return fmt.Errorf("Can't write %v bytes to %s: %s", len(b), tmp, err)
}
fd.Close() // we ignore close(2) errors; unrecoverable anyway.
os.Rename(tmp, fn)
return nil
}
// 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 randread(b []byte) []byte {
_, err := io.ReadFull(rand.Reader, b)
if err != nil {
panic(fmt.Sprintf("can't read %d bytes of random data: %s", len(b), err))
}
return b
}
// EOF
// vim: noexpandtab:ts=8:sw=8:tw=92:

View file

@ -14,13 +14,39 @@
package sign
import (
"crypto/subtle"
"fmt"
"io/ioutil"
"os"
"path"
"runtime"
"testing"
// module under test
//"github.com/sign"
)
func newAsserter(t *testing.T) func(cond bool, msg string, args ...interface{}) {
return func(cond bool, msg string, args ...interface{}) {
if cond {
return
}
_, file, line, ok := runtime.Caller(1)
if !ok {
file = "???"
line = 0
}
s := fmt.Sprintf(msg, args...)
t.Fatalf("%s: %d: Assertion failed: %s\n", file, line, s)
}
}
// Return true if two byte arrays are equal
func byteEq(x, y []byte) bool {
return subtle.ConstantTimeCompare(x, y) == 1
}
// Return a temp dir in a temp-dir
func tempdir(t *testing.T) string {
assert := newAsserter(t)
@ -28,7 +54,7 @@ func tempdir(t *testing.T) string {
var b [10]byte
dn := os.TempDir()
randRead(b[:])
randread(b[:])
tmp := path.Join(dn, fmt.Sprintf("%x", b[:]))
err := os.MkdirAll(tmp, 0755)
@ -38,22 +64,6 @@ func tempdir(t *testing.T) string {
return tmp
}
var fixedPw = []byte("abc")
var badPw = []byte("def")
var nilPw []byte
// return a hardcoded password
func hardcodedPw() ([]byte, error) {
return fixedPw, nil
}
func wrongPw() ([]byte, error) {
return badPw, nil
}
func emptyPw() ([]byte, error) {
return nilPw, nil
}
// Return true if file exists, false otherwise
func fileExists(fn string) bool {
st, err := os.Stat(fn)
@ -80,110 +90,121 @@ r: 8
p: 1
`
// #1. Create new key pair, and read them back.
func TestSignSimple(t *testing.T) {
func Test0(t *testing.T) {
assert := newAsserter(t)
sk, err := NewPrivateKey()
assert(err == nil, "NewPrivateKey() fail")
kp, err := NewKeypair()
assert(err == nil, "NewKeyPair() fail")
pk := sk.PublicKey()
dn := t.TempDir()
dn := tempdir(t)
bn := fmt.Sprintf("%s/t0", dn)
err = kp.Serialize(bn, "", "abc")
assert(err == nil, "keyPair.Serialize() fail")
pkf := fmt.Sprintf("%s.pub", bn)
skf := fmt.Sprintf("%s.key", bn)
err = pk.Serialize(pkf, "", true)
assert(err == nil, "can't serialize pk %s", pkf)
// try to overwrite
err = pk.Serialize(pkf, "", false)
assert(err != nil, "pk %s overwritten!", pkf)
err = sk.Serialize(skf, "", true, fixedPw)
assert(err == nil, "can't serialize sk %s", skf)
err = sk.Serialize(skf, "", false, nilPw)
assert(err != nil, "sk %s overwritten!", skf)
// We must find these two files
assert(fileExists(pkf), "missing pkf %s", pkf)
assert(fileExists(skf), "missing skf %s", skf)
assert(fileExists(pkf), "missing pkf")
assert(fileExists(skf), "missing skf")
npk, err := ReadPublicKey(pkf)
// send wrong file and see what happens
pk, err := ReadPublicKey(skf)
assert(err != nil, "bad PK ReadPK fail")
pk, err = ReadPublicKey(pkf)
assert(err == nil, "ReadPK() fail")
// send the public key as private key
nsk, err := ReadPrivateKey(pkf, emptyPw)
assert(err != nil, "bad SK ReadSK fail: %s", err)
// -ditto- for Sk
sk, err := ReadPrivateKey(pkf, "")
assert(err != nil, "bad SK ReadSK fail")
nsk, err = ReadPrivateKey(skf, emptyPw)
assert(err != nil, "ReadSK() worked with empty pw")
sk, err = ReadPrivateKey(skf, "")
assert(err != nil, "ReadSK() empty pw fail")
nsk, err = ReadPrivateKey(skf, wrongPw)
assert(err != nil, "ReadSK() worked with wrong pw")
sk, err = ReadPrivateKey(skf, "abcdef")
assert(err != nil, "ReadSK() wrong pw fail")
badf := fmt.Sprintf("%s/badf.key", dn)
err = ioutil.WriteFile(badf, []byte(badsk), 0600)
assert(err == nil, "can't write badsk: %s", err)
assert(err == nil, "write badsk")
nsk, err = ReadPrivateKey(badf, hardcodedPw)
assert(err != nil, "decoded bad SK")
sk, err = ReadPrivateKey(badf, "abc")
assert(err != nil, "badsk read fail")
// Finally, with correct password it should work.
nsk, err = ReadPrivateKey(skf, hardcodedPw)
assert(err == nil, "ReadSK() correct pw fail: %s", err)
sk, err = ReadPrivateKey(skf, "abc")
assert(err == nil, "ReadSK() correct pw fail")
// And, deserialized keys should be identical
assert(byteEq(pk.Pk, npk.Pk), "pkbytes unequal")
assert(byteEq(sk.Sk, nsk.Sk), "skbytes unequal")
assert(byteEq(pk.Pk, kp.Pub.Pk), "pkbytes unequal")
assert(byteEq(sk.Sk, kp.Sec.Sk), "skbytes unequal")
os.RemoveAll(dn)
}
// #2. Create new key pair, sign a rand buffer and verify
func TestSignRandBuf(t *testing.T) {
func Test1(t *testing.T) {
assert := newAsserter(t)
sk, err := NewPrivateKey()
assert(err == nil, "NewPrivateKey() fail: %s", err)
kp, err := NewKeypair()
assert(err == nil, "NewKeyPair() fail")
var ck [64]byte // simulates sha512 sum
randRead(ck[:])
randread(ck[:])
pk := sk.PublicKey()
pk := &kp.Pub
sk := &kp.Sec
ss, err := sk.SignMessage(ck[:], "")
assert(err == nil, "sk.sign fail: %s", err)
assert(err == nil, "sk.sign fail")
assert(ss != nil, "sig is null")
// verify sig
assert(ss.IsPKMatch(pk), "pk match fail")
// Corrupt the pkhash and see
randRead(ss.pkhash)
randread(ss.pkhash)
assert(!ss.IsPKMatch(pk), "corrupt pk match fail")
// Incorrect checksum == should fail verification
ok := pk.VerifyMessage(ck[:16], ss)
ok, err := pk.VerifyMessage(ck[:16], ss)
assert(err == nil, "bad ck verify err fail")
assert(!ok, "bad ck verify fail")
// proper checksum == should work
ok = pk.VerifyMessage(ck[:], ss)
ok, err = pk.VerifyMessage(ck[:], ss)
assert(err == nil, "verify err")
assert(ok, "verify fail")
// Now sign a file
dn := t.TempDir()
dn := tempdir(t)
bn := fmt.Sprintf("%s/k", dn)
pkf := fmt.Sprintf("%s.pub", bn)
skf := fmt.Sprintf("%s.key", bn)
err = kp.Serialize(bn, "", "")
assert(err == nil, "keyPair.Serialize() fail")
// Now read the private key and sign
sk, err = ReadPrivateKey(skf, "")
assert(err == nil, "readSK fail")
pk, err = ReadPublicKey(pkf)
assert(err == nil, "ReadPK fail")
var buf [8192]byte
zf := fmt.Sprintf("%s/file.dat", dn)
fd, err := os.OpenFile(zf, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
assert(err == nil, "file.dat creat file: %s", err)
assert(err == nil, "file.dat creat file")
for i := 0; i < 8; i++ {
randRead(buf[:])
randread(buf[:])
n, err := fd.Write(buf[:])
assert(err == nil, fmt.Sprintf("file.dat write fail: %s", err))
assert(n == 8192, fmt.Sprintf("file.dat i/o fail: exp 8192 saw %v", n))
@ -192,31 +213,27 @@ func TestSignRandBuf(t *testing.T) {
fd.Close()
sig, err := sk.SignFile(zf)
assert(err == nil, "file.dat sign fail: %s", err)
assert(err == nil, "file.dat sign fail")
assert(sig != nil, "file.dat sign nil")
ok, err = pk.VerifyFile(zf, sig)
assert(err == nil, "file.dat verify fail: %s", err)
assert(err == nil, "file.dat verify fail")
assert(ok, "file.dat verify false")
// Now, serialize the signature and read it back
sf := fmt.Sprintf("%s/file.sig", dn)
err = sig.Serialize(sf, "", true)
assert(err == nil, "sig serialize fail: %s", err)
// now try to overwrite it
err = sig.Serialize(sf, "", false)
assert(err != nil, "sig serialize overwrote?!")
err = sig.SerializeFile(sf, "")
assert(err == nil, "sig serialize fail")
s2, err := ReadSignature(sf)
assert(err == nil, "file.sig read fail: %s", err)
assert(err == nil, "file.sig read fail")
assert(s2 != nil, "file.sig sig nil")
assert(byteEq(s2.Sig, sig.Sig), "sig compare fail")
// If we give a wrong file, verify must fail
st, err := os.Stat(zf)
assert(err == nil, "file.dat stat fail: %s", err)
assert(err == nil, "file.dat stat fail")
n := st.Size()
assert(n == 8192*8, "file.dat size fail")
@ -224,12 +241,12 @@ func TestSignRandBuf(t *testing.T) {
os.Truncate(zf, n-1)
st, err = os.Stat(zf)
assert(err == nil, "file.dat stat2 fail: %s", err)
assert(err == nil, "file.dat stat2 fail")
assert(st.Size() == (n-1), "truncate fail")
// Now verify this corrupt file
ok, err = pk.VerifyFile(zf, sig)
assert(err == nil, "file.dat corrupt i/o fail: %s", err)
assert(err == nil, "file.dat corrupt i/o fail")
assert(!ok, "file.dat corrupt verify false")
os.RemoveAll(dn)
@ -237,7 +254,7 @@ func TestSignRandBuf(t *testing.T) {
func Benchmark_Keygen(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = NewPrivateKey()
_, _ = NewKeypair()
}
}
@ -246,16 +263,10 @@ func Benchmark_Sig(b *testing.B) {
16,
32,
64,
1024,
4096,
256 * 1024,
1048576,
4 * 1048576,
}
b.StopTimer()
sk, _ := NewPrivateKey()
pk := sk.PublicKey()
kp, _ := NewKeypair()
var sig *Signature
for _, sz := range sizes {
buf := randbuf(sz)
@ -265,11 +276,11 @@ func Benchmark_Sig(b *testing.B) {
b.ResetTimer()
b.Run(s0, func(b *testing.B) {
sig = benchSign(b, buf, sk)
sig = benchSign(b, buf, &kp.Sec)
})
b.Run(s1, func(b *testing.B) {
benchVerify(b, buf, sig, pk)
benchVerify(b, buf, sig, &kp.Pub)
})
}
}
@ -289,7 +300,7 @@ func benchVerify(b *testing.B, buf []byte, sig *Signature, pk *PublicKey) {
func randbuf(sz uint) []byte {
b := make([]byte, sz)
randRead(b)
randread(b)
return b
}

View file

@ -1,384 +0,0 @@
// ssh.go - support for reading ssh private and public keys
//
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file is a bastardization of github.com/ScaleFT/sshkeys and
// golang.org/x/crypto/ssh/keys.go
//
// It is licensed under the terms of the original go source code
// OR the Apache 2.0 license (terms of sshkeys).
//
// Changes from that version:
// - don't use password but call a func() to get the password as needed
// - narrowly scope the key support for ONLY ed25519 keys
// - support reading multiple public keys from authorized_keys
package sign
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/x509"
"encoding/base64"
"encoding/binary"
"encoding/pem"
"fmt"
"regexp"
"strings"
"github.com/dchest/bcrypt_pbkdf"
"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/ssh"
)
const keySizeAES256 = 32
// ParseEncryptedRawPrivateKey returns a private key from an
// encrypted ed25519 private key.
func parseSSHPrivateKey(data []byte, getpw func() ([]byte, error)) (*PrivateKey, error) {
block, _ := pem.Decode(data)
if block == nil {
return nil, ErrNoPEMFound
}
if x509.IsEncryptedPEMBlock(block) {
return nil, fmt.Errorf("ssh: no support for legacy PEM encrypted keys")
}
switch block.Type {
case "OPENSSH PRIVATE KEY":
return parseOpenSSHPrivateKey(block.Bytes, getpw)
default:
return nil, fmt.Errorf("ssh: unsupported key type %q", block.Type)
}
}
func parseSSHPublicKey(in []byte) (*PublicKey, error) {
splitter := regexp.MustCompile("[ \\t]+")
v := splitter.Split(string(in), -1)
if len(v) != 3 {
return nil, ErrBadPublicKey
}
return parseEncPubKey([]byte(v[1]), v[2])
}
// parse a wire encoded public key
func parseEncPubKey(in []byte, comm string) (*PublicKey, error) {
in, err := base64.StdEncoding.DecodeString(string(in))
if err != nil {
return nil, err
}
algo, in, ok := parseString(in)
if !ok {
return nil, ErrKeyTooShort
}
if string(algo) != ssh.KeyAlgoED25519 {
return nil, nil
}
var w struct {
KeyBytes []byte
Rest []byte `ssh:"rest"`
}
if err := ssh.Unmarshal(in, &w); err != nil {
return nil, err
}
if len(w.Rest) > 0 {
return nil, ErrBadTrailers
}
var pk PublicKey
if err = makePublicKeyFromBytes(&pk, w.KeyBytes); err == nil {
pk.Comment = strings.TrimSpace(comm)
return &pk, nil
}
return nil, err
}
func parseString(in []byte) (out, rest []byte, ok bool) {
if len(in) < 4 {
return
}
length := binary.BigEndian.Uint32(in)
in = in[4:]
if uint32(len(in)) < length {
return
}
out = in[:length]
rest = in[length:]
ok = true
return
}
// parseAuthorizedKey parses a public key in OpenSSH binary format and decodes it.
// removed.
func parseAuthorizedKey(in []byte) (*PublicKey, error) {
in = bytes.TrimSpace(in)
i := bytes.IndexAny(in, " \t")
if i == -1 {
i = len(in)
}
pk, err := parseEncPubKey(in[:i], string(in[i:]))
if err != nil {
return nil, err
}
return pk, nil
}
// ParseAuthorizedKeys parses a public key from an authorized_keys
// file used in OpenSSH according to the sshd(8) manual page.
func ParseAuthorizedKeys(in []byte) ([]*PublicKey, error) {
var pka []*PublicKey
var rest []byte
for len(in) > 0 {
end := bytes.IndexByte(in, '\n')
if end != -1 {
rest = in[end+1:]
in = in[:end]
} else {
rest = nil
}
end = bytes.IndexByte(in, '\r')
if end != -1 {
in = in[:end]
}
in = bytes.TrimSpace(in)
if len(in) == 0 || in[0] == '#' {
in = rest
continue
}
i := bytes.IndexAny(in, " \t")
if i == -1 {
in = rest
continue
}
if pk, err := parseAuthorizedKey(in[i:]); err == nil {
if pk != nil {
pka = append(pka, pk)
}
in = rest
continue
}
// No key type recognised. Maybe there's an options field at
// the beginning.
var b byte
inQuote := false
var candidateOptions []string
optionStart := 0
for i, b = range in {
isEnd := !inQuote && (b == ' ' || b == '\t')
if (b == ',' && !inQuote) || isEnd {
if i-optionStart > 0 {
candidateOptions = append(candidateOptions, string(in[optionStart:i]))
}
optionStart = i + 1
}
if isEnd {
break
}
if b == '"' && (i == 0 || (i > 0 && in[i-1] != '\\')) {
inQuote = !inQuote
}
}
for i < len(in) && (in[i] == ' ' || in[i] == '\t') {
i++
}
if i == len(in) {
// Invalid line: unmatched quote
in = rest
continue
}
in = in[i:]
i = bytes.IndexAny(in, " \t")
if i == -1 {
in = rest
continue
}
if pk, err := parseAuthorizedKey(in[i:]); err == nil {
if pk != nil {
pka = append(pka, pk)
}
}
in = rest
continue
}
return pka, nil
}
const opensshv1Magic = "openssh-key-v1"
type opensshHeader struct {
CipherName string
KdfName string
KdfOpts string
NumKeys uint32
PubKey string
PrivKeyBlock string
}
type opensshKey struct {
Check1 uint32
Check2 uint32
Keytype string
Rest []byte `ssh:"rest"`
}
type opensshED25519 struct {
Pub []byte
Priv []byte
Comment string
Pad []byte `ssh:"rest"`
}
func parseOpenSSHPrivateKey(data []byte, getpw func() ([]byte, error)) (*PrivateKey, error) {
magic := append([]byte(opensshv1Magic), 0)
if !bytes.Equal(magic, data[0:len(magic)]) {
return nil, ErrBadFormat
}
remaining := data[len(magic):]
w := opensshHeader{}
if err := ssh.Unmarshal(remaining, &w); err != nil {
return nil, err
}
if w.NumKeys != 1 {
return nil, fmt.Errorf("ssh: NumKeys must be 1: %d", w.NumKeys)
}
var privateKeyBytes []byte
var encrypted bool
switch {
// OpenSSH supports bcrypt KDF w/ AES256-CBC or AES256-CTR mode
case w.KdfName == "bcrypt" && w.CipherName == "aes256-cbc":
pw, err := getpw()
if err != nil {
return nil, err
}
iv, block, err := extractBcryptIvBlock(pw, &w)
if err != nil {
return nil, err
}
cbc := cipher.NewCBCDecrypter(block, iv)
privateKeyBytes = []byte(w.PrivKeyBlock)
cbc.CryptBlocks(privateKeyBytes, privateKeyBytes)
encrypted = true
case w.KdfName == "bcrypt" && w.CipherName == "aes256-ctr":
pw, err := getpw()
if err != nil {
return nil, err
}
iv, block, err := extractBcryptIvBlock(pw, &w)
if err != nil {
return nil, err
}
stream := cipher.NewCTR(block, iv)
privateKeyBytes = []byte(w.PrivKeyBlock)
stream.XORKeyStream(privateKeyBytes, privateKeyBytes)
encrypted = true
case w.KdfName == "none" && w.CipherName == "none":
privateKeyBytes = []byte(w.PrivKeyBlock)
default:
return nil, fmt.Errorf("ssh: unknown Cipher/KDF: %s:%s", w.CipherName, w.KdfName)
}
pk1 := opensshKey{}
if err := ssh.Unmarshal(privateKeyBytes, &pk1); err != nil {
if encrypted {
return nil, ErrIncorrectPassword
}
return nil, err
}
if pk1.Check1 != pk1.Check2 {
return nil, ErrIncorrectPassword
}
// we only handle ed25519 and rsa keys currently
switch pk1.Keytype {
case ssh.KeyAlgoED25519:
key := opensshED25519{}
err := ssh.Unmarshal(pk1.Rest, &key)
if err != nil {
return nil, err
}
if len(key.Priv) != ed25519.PrivateKeySize {
return nil, ErrBadLength
}
for i, b := range key.Pad {
if int(b) != i+1 {
return nil, ErrBadPadding
}
}
var sk PrivateKey
if err = makePrivateKeyFromBytes(&sk, key.Priv); err == nil {
return &sk, nil
}
return nil, err
default:
return nil, fmt.Errorf("ssh: unhandled key type: %v", pk1.Keytype)
}
}
func extractBcryptIvBlock(passphrase []byte, w *opensshHeader) ([]byte, cipher.Block, error) {
cipherKeylen := keySizeAES256
cipherIvLen := aes.BlockSize
var opts struct {
Salt []byte
Rounds uint32
}
if err := ssh.Unmarshal([]byte(w.KdfOpts), &opts); err != nil {
return nil, nil, err
}
kdfdata, err := bcrypt_pbkdf.Key(passphrase, opts.Salt, int(opts.Rounds), cipherKeylen+cipherIvLen)
if err != nil {
return nil, nil, err
}
iv := kdfdata[cipherKeylen : cipherIvLen+cipherKeylen]
aeskey := kdfdata[0:cipherKeylen]
block, err := aes.NewCipher(aeskey)
if err != nil {
return nil, nil, err
}
return iv, block, nil
}

View file

@ -1,162 +0,0 @@
// stream.go - Streaming io.Reader, io.Writer interface to encryption/decryption
//
// (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 (
"io"
)
// encWriter buffers partial writes until a full chunk is accumulated.
// It's methods implement the io.WriteCloser interface.
type encWriter struct {
buf []byte
n int // # of bytes written
wr io.WriteCloser
e *Encryptor
blk uint32
err error
}
var _ io.WriteCloser = &encWriter{}
// NewStreamWriter begins stream encryption to an underlying destination writer 'wr'.
// It returns an io.WriteCloser.
func (e *Encryptor) NewStreamWriter(wr io.WriteCloser) (io.WriteCloser, error) {
if !e.started {
err := e.start(wr)
if err != nil {
return nil, err
}
}
w := &encWriter{
buf: make([]byte, e.ChunkSize),
wr: wr,
e: e,
}
e.stream = true
return w, nil
}
// Write implements the io.Writer interface
func (w *encWriter) Write(b []byte) (int, error) {
if w.err != nil {
return 0, w.err
}
n := len(b)
if n == 0 {
return 0, nil
}
max := int(w.e.ChunkSize)
for len(b) > 0 {
buf := w.buf[w.n:]
z := copy(buf, b)
b = b[z:]
w.n += z
// We only flush if we have more data remaining in the input buffer.
// This way, we don't flush a potentially last block here; that happens
// when the caller eventually closes the stream.
if w.n == max && len(b) > 0 {
w.err = w.e.encrypt(w.buf, w.wr, w.blk, false)
if w.err != nil {
return 0, w.err
}
w.n = 0
w.blk += 1
}
}
return n, nil
}
// Close implements the io.Close interface
func (w *encWriter) Close() error {
if w.err != nil {
return w.err
}
err := w.e.encrypt(w.buf[:w.n], w.wr, w.blk, true)
if err != nil {
w.err = err
return err
}
w.n = 0
w.err = ErrClosed
return w.wr.Close()
}
// encReader buffers partial reads and it's methods implement the io.Reader interface.
type encReader struct {
buf []byte
unread []byte
d *Decryptor
blk uint32
}
var _ io.Reader = &encReader{}
// NewStreamReader returns an io.Reader to read from the decrypted stream
func (d *Decryptor) NewStreamReader() (io.Reader, error) {
if d.key == nil {
return nil, ErrNoKey
}
if d.eof {
return nil, io.EOF
}
d.stream = true
return &encReader{
buf: make([]byte, d.ChunkSize),
d: d,
}, nil
}
// Read implements io.Reader interface
func (r *encReader) Read(b []byte) (int, error) {
if r.d.eof && len(r.unread) == 0 {
return 0, io.EOF
}
if len(r.unread) > 0 {
n := copy(b, r.unread)
r.unread = r.unread[n:]
return n, nil
}
buf, eof, err := r.d.decrypt(r.blk)
if err != nil {
return 0, err
}
r.blk += 1
n := copy(b, buf)
buf = buf[n:]
copy(r.buf, buf)
r.unread = r.buf[:len(buf)]
if eof {
r.d.eof = true
}
return n, nil
}

View file

@ -1,43 +0,0 @@
// utils_test.go -- Test harness utilities for sign
//
// (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 (
"crypto/subtle"
"fmt"
"runtime"
"testing"
)
func newAsserter(t *testing.T) func(cond bool, msg string, args ...interface{}) {
return func(cond bool, msg string, args ...interface{}) {
if cond {
return
}
_, file, line, ok := runtime.Caller(1)
if !ok {
file = "???"
line = 0
}
s := fmt.Sprintf(msg, args...)
t.Fatalf("%s: %d: Assertion failed: %s\n", file, line, s)
}
}
// Return true if two byte arrays are equal
func byteEq(x, y []byte) bool {
return subtle.ConstantTimeCompare(x, y) == 1
}

351
sigtool.go Normal file
View file

@ -0,0 +1,351 @@
// 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 {
warn("Insufficient arguments. Try '%s -h'", Z)
os.Exit(1)
}
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:])
}
// Run the generate command
func gen(args []string) {
var pw, 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(&pw, "password", "p", false, "Ask for passphrase to encrypt 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, "force", "F", 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]
if exists(bn) && !force {
die("Public/Private key files (%s.key, %s.pub) exist. Won't overwrite!", bn, bn)
}
var pws string
var err error
if len(envpw) > 0 {
pws = os.Getenv(envpw)
} else if pw {
pws, err = utils.Askpass("Enter passphrase for private key", true)
if err != nil {
die("%s", err)
}
}
kp, err := sign.NewKeypair()
if err != nil {
die("%s", err)
}
err = kp.Serialize(bn, comment, pws)
if err != nil {
die("%s", err)
}
}
// Run the 'sign' command.
func signify(args []string) {
var pw, help 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(&pw, "password", "p", false, "Ask for passphrase to decrypt 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.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 pws string
var err error
if len(envpw) > 0 {
pws = os.Getenv(envpw)
} else if pw {
pws, err = utils.Askpass("Enter passphrase for private key", false)
if err != nil {
die("%s", err)
}
}
if len(output) > 0 {
outf = output
}
sk, err := sign.ReadPrivateKey(kn, pws)
if err != nil {
die("%s", err)
}
sig, err := sk.SignFile(fn)
if err != nil {
die("%s", err)
}
sigo, err := sig.Serialize(fmt.Sprintf("input=%s", fn))
var fd io.Writer = os.Stdout
if outf != "-" {
fdx, err := os.OpenFile(outf, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
if err != nil {
die("can't create output file %s: %s", outf, err)
}
defer fdx.Close()
fd = fdx
}
fd.Write(sigo)
}
// 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(bn string) bool {
pk := bn + ".pub"
sk := bn + ".key"
if _, err := os.Stat(pk); err == nil {
return true
}
if _, err := os.Stat(sk); err == nil {
return true
}
return false
}
// die with error
func die(f string, v ...interface{}) {
warn(f, v...)
os.Exit(1)
}
func warn(f string, v ...interface{}) {
z := fmt.Sprintf("%s: %s", os.Args[0], f)
s := fmt.Sprintf(z, v...)
if n := len(s); s[n-1] != '\n' {
s += "\n"
}
os.Stderr.WriteString(s)
os.Stderr.Sync()
}
// This will be filled in by "build"
var RepoVersion string = "UNDEFINED"
var Buildtime string = "UNDEFINED"
var ProductVersion string = "UNDEFINED"
// vim: ft=go:sw=8:ts=8:noexpandtab:tw=98:

View file

@ -1,399 +0,0 @@
// crypt.go -- Encrypt/decrypt command handling
//
// (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"
"io/ioutil"
"os"
"strings"
"git.rgst.io/homelab/sigtool/v3/sign"
"github.com/opencoff/go-fio"
"github.com/opencoff/go-utils"
flag "github.com/opencoff/pflag"
)
// sigtool encrypt [-i|--identity my.key] to.pub [to.pub] [ssh.pub] inputfile|- [-o output]
func encrypt(args []string) {
fs := flag.NewFlagSet("encrypt", flag.ExitOnError)
fs.Usage = func() {
encryptUsage(fs)
}
var outfile string
var keyfile string
var szstr string = "128k"
var envpw string
var nopw, force bool
var blksize uint64
fs.StringVarP(&outfile, "outfile", "o", "", "Write the output to file `F`")
fs.StringVarP(&keyfile, "sign", "s", "", "Sign using private key `S`")
fs.BoolVarP(&nopw, "no-password", "", false, "Don't ask for passphrase to decrypt the private key")
fs.StringVarP(&envpw, "env-password", "E", "", "Use passphrase from environment variable `E`")
fs.StringVarP(&szstr, "block-size", "B", szstr, "Use `S` as the encryption block size")
fs.BoolVarP(&force, "overwrite", "", false, "Overwrite the output file if it exists")
err := fs.Parse(args)
if err != nil {
Die("%s", err)
}
if blksize, err = utils.ParseSize(szstr); err != nil {
Die("%s", err)
}
var pws, infile string
var sk *sign.PrivateKey
if len(keyfile) > 0 {
sk, err = sign.ReadPrivateKey(keyfile, func() ([]byte, error) {
if nopw {
return nil, nil
}
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)
}
}
args = fs.Args()
if len(args) < 2 {
Die("Insufficient args. Try '%s --help'", os.Args[0])
}
var infd io.Reader = os.Stdin
var outfd io.WriteCloser = os.Stdout
var inf *os.File
if len(args) > 1 {
infile = args[len(args)-1]
if infile != "-" {
inf := mustOpen(infile, os.O_RDONLY)
defer inf.Close()
infd = inf
}
}
// Lets try to read the authorized files
home, err := os.UserHomeDir()
if err != nil {
Die("can't find homedir for this user")
}
authkeys := fmt.Sprintf("%s/.ssh/authorized_keys", home)
authdata, err := ioutil.ReadFile(authkeys)
if err != nil {
if !os.IsNotExist(err) {
Die("can't open %s: %s", authkeys, err)
}
}
pka, err := sign.ParseAuthorizedKeys(authdata)
keymap := make(map[string]*sign.PublicKey)
for _, pk := range pka {
keymap[pk.Comment] = pk
}
if len(outfile) > 0 && outfile != "-" {
var mode os.FileMode = 0600 // conservative output mode
if inf != nil {
var err error
var ist, ost os.FileInfo
if ost, err = os.Stat(outfile); err != nil {
Die("can't stat %s: %s", outfile, err)
}
if ist, err = inf.Stat(); err != nil {
Die("can't stat %s: %s", infile, err)
}
if os.SameFile(ist, ost) {
Die("won't create output file: same as input file!")
}
mode = ist.Mode()
}
var opts uint32
if force {
opts |= fio.OPT_OVERWRITE
}
sf, err := fio.NewSafeFile(outfile, opts, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode)
if err != nil {
Die("%s", err)
}
AtExit(sf.Abort)
defer sf.Abort()
outfd = sf
}
en, err := sign.NewEncryptor(sk, blksize)
if err != nil {
Die("%s", err)
}
errs := 0
for i := 0; i < len(args)-1; i++ {
var err error
var pk *sign.PublicKey
fn := args[i]
if strings.Index(fn, "@") > 0 {
var ok bool
pk, ok = keymap[fn]
if !ok {
Warn("can't find user %s in %s", fn, authkeys)
errs += 1
continue
}
} else {
pk, err = sign.ReadPublicKey(fn)
if err != nil {
Warn("%s", err)
errs += 1
continue
}
}
err = en.AddRecipient(pk)
if err != nil {
Die("%s", err)
}
}
if errs > 0 {
Die("Too many errors!")
}
err = en.Encrypt(infd, outfd)
if err != nil {
Die("%s", err)
}
outfd.Close()
}
type nullWriter struct{}
func (w *nullWriter) Write(p []byte) (int, error) {
return len(p), nil
}
func (w *nullWriter) Close() error {
return nil
}
var _ io.WriteCloser = &nullWriter{}
// sigtool decrypt a.key [file] [-o output]
func decrypt(args []string) {
fs := flag.NewFlagSet("decrypt", flag.ExitOnError)
fs.Usage = func() {
decryptUsage(fs)
}
var envpw string
var outfile string
var pubkey string
var nopw, test, force bool
fs.StringVarP(&outfile, "outfile", "o", "", "Write the output to file `F`")
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(&pubkey, "verify-sender", "v", "", "Verify that the sender matches public key in `F`")
fs.BoolVarP(&test, "test", "t", false, "Test the encrypted file against the given key without writing to output")
fs.BoolVarP(&force, "overwrite", "", false, "Overwrite the output file if it exists")
err := fs.Parse(args)
if err != nil {
Die("%s", err)
}
args = fs.Args()
if len(args) < 1 {
Die("Insufficient args. Try '%s --help'", os.Args[0])
}
var infd io.Reader = os.Stdin
var outfd io.WriteCloser = os.Stdout
var inf *os.File
var infile string
keyfile := args[0]
sk, err := sign.ReadPrivateKey(keyfile, func() ([]byte, error) {
var pws string
if nopw {
return nil, nil
}
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)
}
var pk *sign.PublicKey
if len(pubkey) > 0 {
pk, err = sign.ReadPublicKey(pubkey)
if err != nil {
Die("%s", err)
}
}
if len(args) > 1 {
infile = args[1]
if infile != "-" {
inf := mustOpen(infile, os.O_RDONLY)
defer inf.Close()
infd = inf
}
}
if test {
outfd = &nullWriter{}
} else if len(outfile) > 0 && outfile != "-" {
var mode os.FileMode = 0600 // conservative mode
if inf != nil {
var ist, ost os.FileInfo
var err error
if ost, err = os.Stat(outfile); err != nil {
Die("can't stat %s: %s", outfile, err)
}
if ist, err = inf.Stat(); err != nil {
Die("can't stat %s: %s", infile, err)
}
if os.SameFile(ist, ost) {
Die("won't create output file: same as input file!")
}
mode = ist.Mode()
}
var opts uint32
if force {
opts |= fio.OPT_OVERWRITE
}
sf, err := fio.NewSafeFile(outfile, opts, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode)
if err != nil {
Die("%s", err)
}
AtExit(sf.Abort)
defer sf.Abort()
outfd = sf
}
d, err := sign.NewDecryptor(infd)
if err != nil {
Die("%s", err)
}
err = d.SetPrivateKey(sk, pk)
if err != nil {
Die("%s", err)
}
if pk == nil && d.AuthenticatedSender() {
var fn string = infile
if len(fn) == 0 || fn == "-" {
fn = "<stdin>"
}
Warn("%s: Missing sender Public Key; can't authenticate sender ..", fn)
}
if err = d.Decrypt(outfd); err != nil {
Die("%s", err)
}
outfd.Close()
if test {
Warn("Enc file OK")
}
}
func encryptUsage(fs *flag.FlagSet) {
fmt.Printf(`%s encrypt: Encrypt a file to one or more recipients.
Usage: %s encrypt [options] to [to ...] infile|-
Where TO is the public key of the recipient; it can be one of:
- a file referring to an SSH or sigtool public key.
- string of the form 'a@b' - in which case the user's default
ssh/authorized_keys is consulted to find the comment matching
'a@b' - in which case the user's ssh authorized_keys file is consulted to
find the comment matching the string.
INFILE is an input file to be encrypted. If the input file is '-' then %s
reads from STDIN. Unless '-o' is used, %s writes the encrypted output to STDOUT.
Options:
`, Z, Z, Z, Z)
fs.PrintDefaults()
os.Exit(0)
}
func decryptUsage(fs *flag.FlagSet) {
fmt.Printf(`%s decrypt: Decrypt a file.
Usage: %s decrypt [options] key [infile]
Where KEY is the private key to be used for decryption and INFILE is
the encrypted input file. If INFILE is not provided, %s reads
from STDIN. Unless '-o' is used, %s writes the decrypted output to STDOUT.
Options:
`, Z, Z, Z, Z)
fs.PrintDefaults()
os.Exit(0)
}
func mustOpen(fn string, flag int) *os.File {
fdk, err := os.OpenFile(fn, flag, 0600)
if err != nil {
Die("can't open file %s: %s", fn, err)
}
return fdk
}

View file

@ -1,55 +0,0 @@
// die.go -- die() and warn()
//
// (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"
)
var atExit []func()
// Die prints an error message to stderr
// and exits the program after calling all the registered
// at-exit functions.
func Die(f string, v ...interface{}) {
Warn(f, v...)
Exit(1)
}
// Warn prints an error message to stderr
func Warn(f string, v ...interface{}) {
z := fmt.Sprintf("%s: %s", os.Args[0], f)
s := fmt.Sprintf(z, v...)
if n := len(s); s[n-1] != '\n' {
s += "\n"
}
os.Stderr.WriteString(s)
os.Stderr.Sync()
}
// AtExit registers a function to be called before the program exits.
func AtExit(f func()) {
atExit = append(atExit, f)
}
// Exit invokes the registered atexit handlers and exits with the
// given code.
func Exit(v int) {
for _, f := range atExit {
f()
}
os.Exit(v)
}

View file

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

View file

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

View file

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

View file

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

124
tests.sh
View file

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

1
version Normal file
View file

@ -0,0 +1 @@
0.2.0