Compare commits
37 commits
Author | SHA1 | Date | |
---|---|---|---|
aa96303d43 | |||
8d3686eb14 | |||
6b2e5526b2 | |||
d128cfa7ce | |||
3f6b9cd2af | |||
6a16789886 | |||
1823aaa5e0 | |||
810aa02bdd | |||
|
1786734c0a | ||
|
d47a4596ef | ||
|
fc94d7cd7d | ||
|
2e6d92c753 | ||
|
5c6152b4ed | ||
|
e3053142f5 | ||
|
d49f732c71 | ||
|
15053202a1 | ||
|
c5400a6b18 | ||
|
eae20abd24 | ||
|
c4f79962c9 | ||
|
a538ac8e5c | ||
|
c95515af0e | ||
|
bbd7afd496 | ||
|
f343d45a8e | ||
|
a428db8feb | ||
|
0ddf48c92f | ||
|
42bbe5ddeb | ||
|
f180079586 | ||
|
445c13ca6f | ||
|
bce89dacb0 | ||
|
460f1cf703 | ||
|
43a9f38592 | ||
|
81a6522ee7 | ||
|
85ed0d06dd | ||
|
0658fb75d4 | ||
|
945046a815 | ||
|
3fa0ce0c9c | ||
|
e22fae05f7 |
33 changed files with 3150 additions and 2386 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -24,12 +24,11 @@ _testmain.go
|
|||
*.prof
|
||||
vendor/*
|
||||
|
||||
# vendor management
|
||||
vendor/src/*
|
||||
vendor/pkg/*
|
||||
bin/*
|
||||
sigtool
|
||||
|
||||
.??*.sw?
|
||||
*.pub
|
||||
*.key
|
||||
*.sig
|
||||
releases/*
|
||||
|
|
2
.mise.toml
Normal file
2
.mise.toml
Normal file
|
@ -0,0 +1,2 @@
|
|||
[tools]
|
||||
golang = "1.24"
|
160
README.md
160
README.md
|
@ -7,28 +7,40 @@
|
|||
`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.
|
||||
easier to use. It can use SSH ed25519 public and private 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.
|
||||
are human readable YAML files.
|
||||
|
||||
It can encrypt files for multiple recipients - each of whom is identified
|
||||
by their Ed25519 public key. The encryption by default generates ephmeral
|
||||
by their Ed25519 public key. The encryption 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
|
||||
the encrypted file. The caller can optionally use a specific private 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).
|
||||
corresponding sender's 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?
|
||||
With Go 1.5 and later:
|
||||
You need two things:
|
||||
|
||||
git clone https://github.com/opencoff/sigtool
|
||||
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
|
||||
cd sigtool
|
||||
make
|
||||
|
||||
|
@ -89,6 +101,10 @@ 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.
|
||||
|
||||
|
@ -138,28 +154,49 @@ recipient can decrypt using their private key.
|
|||
|
||||
## Technical Details
|
||||
|
||||
### 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.
|
||||
|
||||
### How is the Encryption done?
|
||||
### How is the file encryption done?
|
||||
The file encryption uses AES-GCM-256 in AEAD mode. The encryption uses
|
||||
a random 32-byte AES-256 key. This key is mixed in with the header checksum
|
||||
as a safeguard to protect the header against accidental or malicious corruption.
|
||||
a random 32-byte AES-256 key. This root key is expanded via
|
||||
HKDF-SHA256 into:
|
||||
|
||||
- AES-GCM-256 key (32 bytes)
|
||||
- AES Nonce (12 bytes)
|
||||
- HMAC-SHA-256 key (32 bytes)
|
||||
|
||||
The input to the HKDF is the root-key, header-checksum ("salt") and
|
||||
a context string.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
### What is the public-key cryptography?
|
||||
`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.
|
||||
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).
|
||||
|
||||
The Ed25519 keys generated by `sigtool` are transformed to their
|
||||
corresponding Curve25519 points in order to generate the shared secret.
|
||||
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.
|
||||
This elliptic co-ordinate transform follows [FiloSottile's writeup][2].
|
||||
|
||||
### Format of the Encrypted File
|
||||
|
@ -180,18 +217,11 @@ 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
|
||||
sender sender_pk = 4; // sender's encrypted ed25519 PK
|
||||
repeated wrapped_key keys = 5;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sender info is wrapped using the data encryption key
|
||||
*/
|
||||
message sender {
|
||||
bytes pk = 1;
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -199,7 +229,8 @@ described as a protobuf file (sign/hdr.proto):
|
|||
* key. WrappedKey describes such a wrapped key.
|
||||
*/
|
||||
message wrapped_key {
|
||||
bytes key = 2;
|
||||
bytes d_key = 1;
|
||||
bytes nonce = 2;
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -210,16 +241,35 @@ chunk is encoded the same way:
|
|||
|
||||
```C
|
||||
4 byte chunk length (big endian encoding)
|
||||
encrypted chunk data
|
||||
AEAD encrypted 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
|
||||
computed. 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
|
||||
|
@ -230,7 +280,10 @@ etc.
|
|||
* `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.
|
||||
for encryption and decryption.
|
||||
* `tests.sh` simple round trip test using the tool; this is in addition to the tests in
|
||||
`sign/`.
|
||||
|
||||
|
||||
The generated keys and signatures are proper YAML files and human
|
||||
readable.
|
||||
|
@ -243,6 +296,11 @@ 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
|
||||
|
@ -263,24 +321,6 @@ And, a serialized Ed25519 private key looks like so:
|
|||
p: 1
|
||||
```
|
||||
|
||||
The Ed25519 private key is encrypted using AES-256-GCM AEAD mode;
|
||||
the encryption key is derived from the user supplied passphrase
|
||||
using scrypt KDF. A user supplied passphrase is first expanded
|
||||
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)
|
||||
key = Scrypt(hpass, 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
|
||||
|
||||
### Ed25519 Signature
|
||||
A generated signature looks like below after serialization:
|
||||
|
|
271
build
271
build
|
@ -4,7 +4,7 @@
|
|||
#
|
||||
# - it tacks on a version number for use by the individual tools
|
||||
# - it supports git and mercurial version#
|
||||
#
|
||||
#
|
||||
# NB:
|
||||
# o the attempt at decoding dirty repo state for mercurial is
|
||||
# borked. It doesn't know about untracked files
|
||||
|
@ -13,12 +13,13 @@
|
|||
#
|
||||
# License: GPLv2
|
||||
#
|
||||
Progs=".:sigtool"
|
||||
Progs="src:sigtool"
|
||||
|
||||
# Relative path to protobuf sources
|
||||
# e.g. src/foo/a.proto
|
||||
Protobufs="internal/pb/hdr.proto"
|
||||
|
||||
#set -x
|
||||
|
||||
# -- DO NOT CHANGE ANYTHING AFTER THIS --
|
||||
|
||||
|
@ -27,13 +28,11 @@ PWD=`pwd`
|
|||
|
||||
Static=0
|
||||
Dryrun=0
|
||||
Prodver=0.1
|
||||
Prodver=""
|
||||
Repover=""
|
||||
Verbose=0
|
||||
|
||||
hostos=$(go env GOHOSTOS) || exit 1
|
||||
hostcpu=$(go env GOHOSTARCH) || exit 1
|
||||
|
||||
[ -f ./version ] && Prodver=$(cat ./version)
|
||||
Go=`which go`
|
||||
Bindir=$PWD/bin
|
||||
|
||||
die() {
|
||||
echo "$Z: $@" 1>&2
|
||||
|
@ -51,47 +50,56 @@ case $BASH_VERSION in
|
|||
;;
|
||||
esac
|
||||
|
||||
getvcs_version() {
|
||||
local rev=
|
||||
local prodv=
|
||||
local git=`which git`
|
||||
local hg=`which hg`
|
||||
|
||||
# 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
|
||||
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"
|
||||
fi
|
||||
|
||||
# from here - we want this dir to find all build artifacts
|
||||
PATH=$PATH:$bindir
|
||||
export PATH
|
||||
[ -n "$Prodver" ] && prodv=$Prodver
|
||||
|
||||
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
|
||||
echo "$rev $prodv"
|
||||
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
|
||||
|
@ -100,12 +108,14 @@ 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: $Progs (source in ./src/)
|
||||
With no arguments, $0 builds: $pstr
|
||||
|
||||
If ./version is present, its content are used as version number for the binary.
|
||||
The repository's latest tag is used as the default version of the software being
|
||||
built. The current repository version is $Repover.
|
||||
|
||||
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]
|
||||
|
@ -113,14 +123,16 @@ 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
|
||||
|
@ -153,6 +165,7 @@ done
|
|||
Tool=
|
||||
doinit=0
|
||||
args=
|
||||
Printarch=0
|
||||
|
||||
#set -x
|
||||
ac_prev=
|
||||
|
@ -180,13 +193,23 @@ 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
|
||||
;;
|
||||
|
@ -195,9 +218,14 @@ do
|
|||
Tool=vet
|
||||
;;
|
||||
|
||||
--mod)
|
||||
Tool=mod
|
||||
;;
|
||||
|
||||
-V|--version)
|
||||
ac_prev=Prodver
|
||||
;;
|
||||
|
||||
-v|--verbose)
|
||||
Verbose=1
|
||||
;;
|
||||
|
@ -214,6 +242,14 @@ 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"
|
||||
|
@ -225,10 +261,80 @@ do
|
|||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
[ $Dryrun -gt 0 ] && e=echo
|
||||
|
||||
# let every error abort
|
||||
#set -e
|
||||
set -e
|
||||
|
||||
# build a tool that runs on the host - if needed.
|
||||
hosttool() {
|
||||
local tool=$1
|
||||
local bindir=$2
|
||||
local src=$3
|
||||
|
||||
p=$bindir/$tool
|
||||
if [ -x $p ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local tmpdir=/tmp/$tool.$$
|
||||
mkdir $tmpdir || die "can't make $tmpdir"
|
||||
|
||||
# since go1.20 - install uses env vars to decide where to put
|
||||
# build artifacts. Why are all the google tooling so bloody dev
|
||||
# hostile! WTF is wrong with command line args?!
|
||||
export GOBIN=$bindir
|
||||
|
||||
# build it and stash it in the hostdir
|
||||
echo "Building tool $tool from $src .."
|
||||
(
|
||||
cd $tmpdir
|
||||
$e $Go install $src@latest || die "can't install $tool"
|
||||
)
|
||||
$e rm -rf $tmpdir
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# protobuf gen
|
||||
buildproto() {
|
||||
local pbgo=protoc-gen-go
|
||||
local vtgo=protoc-gen-go-vtproto
|
||||
local vtgo_src=github.com/planetscale/vtprotobuf/cmd/protoc-gen-go-vtproto
|
||||
local pc
|
||||
local args="$*"
|
||||
|
||||
local pgen=$(type -p protoc)
|
||||
local gogen=$(type -p $pbgo)
|
||||
local vt=$Hostbindir/$vtgo
|
||||
|
||||
[ -z $pgen ] && die "install protoc tools"
|
||||
[ -z $gogen ] && die "install protoc-gen-go"
|
||||
|
||||
# now install the vtproto generator
|
||||
hosttool $vtgo $Hostbindir $vtgo_src
|
||||
|
||||
for f in $args; do
|
||||
local dn=$(dirname $f)
|
||||
local bn=$(basename $f .proto)
|
||||
|
||||
|
||||
$e $pgen \
|
||||
--go_out=. --plugin protoc-gen-go=$gogen \
|
||||
--go-vtproto_out=. --plugin protoc-gen-go-vtproto="$vt" \
|
||||
--go-vtproto_opt=features=marshal+unmarshal+size \
|
||||
$f || die "can't generate protobuf output for $f .."
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
# the rest has to execute in the context of main shell (not funcs)
|
||||
|
||||
hostos=$($Go env GOHOSTOS) || exit 1
|
||||
hostcpu=$($Go env GOHOSTARCH) || exit 1
|
||||
|
||||
# This fragment can't be in a function - since it exports several vars
|
||||
if [ -n "$Arch" ]; then
|
||||
|
@ -274,63 +380,32 @@ else
|
|||
fi
|
||||
fi
|
||||
|
||||
# This is where build outputs go
|
||||
Bindir=$PWD/bin/$cross
|
||||
Hostbindir=$PWD/bin/$hostos-$hostcpu
|
||||
if [ $Printarch -gt 0 ]; then
|
||||
echo "$hostos-$hostcpu"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
[ -d $Bindir ] || mkdir -p $Bindir
|
||||
|
||||
# This is where build outputs go
|
||||
Outdir=$Bindir/$cross
|
||||
Hostbindir=$Bindir/$hostos-$hostcpu
|
||||
export PATH=$Hostbindir:$PATH
|
||||
|
||||
[ -d $Outdir ] || mkdir -p $Outdir
|
||||
[ -d $Hostbindir ] || mkdir -p $Hostbindir
|
||||
|
||||
# Get git/hg version info for the build
|
||||
if [ -d "./.hg" ]; then
|
||||
xrev=$(hg id --id) || exit 1
|
||||
brev=${xrev%+}
|
||||
if [ "$brev" != "$xrev" ]; then
|
||||
rev="hg:${brev}-dirty"
|
||||
else
|
||||
rev="hg:${brev}"
|
||||
fi
|
||||
elif [ -d "./.git" ]; then
|
||||
xrev=$(git describe --always --dirty --long --abbrev=12) || exit 1
|
||||
rev="git:$xrev"
|
||||
else
|
||||
rev="UNKNOWN-VER"
|
||||
echo "$0: Can't find version info" 1>&2
|
||||
fi
|
||||
|
||||
# Do Protobufs if needed
|
||||
if [ -n "$Protobufs" ]; then
|
||||
slick=$Hostbindir/protoc-gen-gogoslick
|
||||
slicksrc=github.com/gogo/protobuf/protoc-gen-gogoslick
|
||||
pc=$(type -p protoc)
|
||||
|
||||
[ -z "$pc" ] && die "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
|
||||
set +e
|
||||
buildproto $Protobufs
|
||||
set -e
|
||||
fi
|
||||
|
||||
repover="main.RepoVersion=$rev"
|
||||
# Get git/hg version info for the build
|
||||
repover="main.RepoVersion=$Repover"
|
||||
prodver="main.ProductVersion=$Prodver"
|
||||
date="main.Buildtime=`date -u '+%Y-%m-%dT%H:%M.%SZ'`"
|
||||
ldflags="-ldflags \"-X $repover -X $prodver -X $date $ldflags\""
|
||||
ldflags="-ldflags \"-X $repover -X $prodver $ldflags -buildid=\""
|
||||
vflag=""
|
||||
|
||||
[ $Verbose -gt 0 ] && vflag="-v"
|
||||
|
@ -338,12 +413,17 @@ vflag=""
|
|||
case $Tool in
|
||||
test)
|
||||
set -- $args
|
||||
$e go test $vflag "$@"
|
||||
$e $Go test $vflag "$@"
|
||||
;;
|
||||
|
||||
vet)
|
||||
set -- $args
|
||||
$e go vet $vflag "$@"
|
||||
$e $Go vet $vflag "$@"
|
||||
;;
|
||||
|
||||
mod)
|
||||
set -- $args
|
||||
$e $Go mod $vflag "$@"
|
||||
;;
|
||||
|
||||
*) # Default is to build programs
|
||||
|
@ -354,9 +434,11 @@ case $Tool in
|
|||
all="$@"
|
||||
fi
|
||||
|
||||
echo "Building $msg $Prodver ($rev) for $cross .."
|
||||
[ -z "$all" ] && die "No programs specified. Try '$Z --help'"
|
||||
|
||||
for p in $all; do
|
||||
echo "Building $Prodver ($Repover), $cross $msg .."
|
||||
|
||||
for p in $all; do
|
||||
if echo $p | grep -q ':' ; then
|
||||
out=${p##*:}
|
||||
dir=${p%%:*}
|
||||
|
@ -364,8 +446,15 @@ 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 -o $Bindir/$out $isuffix "$ldflags" ./$dir || exit 1
|
||||
$e eval $Go build $vflag -trimpath -o $Outdir/$out $isuffix "$ldflags" ./$dir || exit 1
|
||||
done
|
||||
;;
|
||||
esac
|
||||
|
|
27
go.mod
27
go.mod
|
@ -1,12 +1,25 @@
|
|||
module github.com/opencoff/sigtool
|
||||
module git.rgst.io/homelab/sigtool/v3
|
||||
|
||||
go 1.13
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a
|
||||
github.com/gogo/protobuf v1.3.1
|
||||
github.com/opencoff/go-utils v0.4.1
|
||||
github.com/opencoff/pflag v0.5.0
|
||||
golang.org/x/crypto v0.0.0-20200109152110-61a87790db17
|
||||
gopkg.in/yaml.v2 v2.2.7
|
||||
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
|
||||
)
|
||||
|
||||
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
|
||||
|
|
56
go.sum
56
go.sum
|
@ -1,23 +1,39 @@
|
|||
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/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.4.1 h1:Ke4Q1Tl2GKMI+dwleuPNHH713ngRiNMOFIkymncHqXg=
|
||||
github.com/opencoff/go-utils v0.4.1/go.mod h1:c+7QUAiCCHcNH6OGvsZ0fviG7cgse8Y3ucg+xy7sGXM=
|
||||
github.com/opencoff/pflag v0.5.0 h1:kK3cSTlGj0fHby/PoFzHkf+Jx3PdiACJwzYDWEWlEKQ=
|
||||
github.com/opencoff/pflag v0.5.0/go.mod h1:mTLzGGUGda1Av3d34iAJlh0JIlRxmFZtmc6qoWPspK0=
|
||||
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-20200109152110-61a87790db17 h1:nVJ3guKA9qdkEQ3TUdXI9QSINo2CUPM/cySEvw2w8I0=
|
||||
golang.org/x/crypto v0.0.0-20200109152110-61a87790db17/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
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=
|
||||
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=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
|
||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,13 +1,8 @@
|
|||
syntax="proto3";
|
||||
|
||||
//import "gogoproto/gogo.proto"
|
||||
|
||||
package pb;
|
||||
option go_package = "internal/pb";
|
||||
|
||||
//option (gogoproto.marshaler_all) = true;
|
||||
//option (gogoproto.sizer_all) = true;
|
||||
//option (gogoproto.unmarshaler_all) = true;
|
||||
//option (gogoproto.goproto_getters_all) = false;
|
||||
|
||||
/*
|
||||
* Every encrypted file starts with a header describing the
|
||||
|
@ -16,18 +11,11 @@ package pb;
|
|||
* protobuf format before writing to disk.
|
||||
*/
|
||||
message header {
|
||||
uint32 chunk_size = 1;
|
||||
bytes salt = 2;
|
||||
bytes pk = 3; // sender's ephemeral curve PK
|
||||
sender sender_pk = 4; // sender's encrypted ed25519 PK
|
||||
repeated wrapped_key keys = 5;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sender info is wrapped using the data encryption key
|
||||
*/
|
||||
message sender {
|
||||
bytes pk = 1;
|
||||
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
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -35,5 +23,7 @@ message sender {
|
|||
* key. WrappedKey describes such a wrapped key.
|
||||
*/
|
||||
message wrapped_key {
|
||||
bytes key = 2;
|
||||
bytes d_key = 1; // encrypted data key
|
||||
bytes nonce = 2; // nonce used for encryption
|
||||
}
|
||||
|
||||
|
|
512
internal/pb/hdr_vtproto.pb.go
Normal file
512
internal/pb/hdr_vtproto.pb.go
Normal file
|
@ -0,0 +1,512 @@
|
|||
// 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
|
||||
}
|
16
internal/pb/helpers.go
Normal file
16
internal/pb/helpers.go
Normal file
|
@ -0,0 +1,16 @@
|
|||
// 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)
|
||||
}
|
|
@ -1,97 +0,0 @@
|
|||
// wrap.go - wrap keys and sender as needed
|
||||
//
|
||||
// (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 pb
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
WrapReceiverNonce = "Receiver PK"
|
||||
WrapSenderNonce = "Sender PK"
|
||||
)
|
||||
|
||||
// Wrap sender's PK with the data encryption key
|
||||
func WrapSenderPK(pk []byte, k, salt []byte) ([]byte, error) {
|
||||
aes, err := aes.NewCipher(k)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("wrap: %s", err)
|
||||
}
|
||||
|
||||
ae, err := cipher.NewGCM(aes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("wrap: %s", err)
|
||||
}
|
||||
|
||||
nonce := MakeNonce([]byte(WrapSenderNonce), salt)
|
||||
buf := make([]byte, ae.Overhead()+len(pk))
|
||||
out := ae.Seal(buf[:0], nonce[:ae.NonceSize()], pk, nil)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Given a wrapped PK of sender 's', unwrap it using the given key and salt
|
||||
func (s *Sender) UnwrapPK(k, salt []byte) ([]byte, error) {
|
||||
aes, err := aes.NewCipher(k)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("uwrap-sender: %s", err)
|
||||
}
|
||||
|
||||
ae, err := cipher.NewGCM(aes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unwrap-sender: %s", err)
|
||||
}
|
||||
|
||||
nonce := MakeNonce([]byte(WrapSenderNonce), salt)
|
||||
want := 32 + ae.Overhead()
|
||||
if len(s.Pk) != want {
|
||||
return nil, fmt.Errorf("unwrap-sender: incorrect decrypt bytes (need %d, saw %d)", want, 32)
|
||||
}
|
||||
|
||||
out := make([]byte, 32)
|
||||
pk, err := ae.Open(out[:0], nonce[:ae.NonceSize()], s.Pk, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unwrap-sender: %s", err)
|
||||
}
|
||||
|
||||
return pk, nil
|
||||
}
|
||||
|
||||
func MakeNonce(v ...[]byte) []byte {
|
||||
h := sha256.New()
|
||||
for _, x := range v {
|
||||
h.Write(x)
|
||||
}
|
||||
return h.Sum(nil)[:]
|
||||
}
|
||||
|
||||
func Clamp(k []byte) []byte {
|
||||
k[0] &= 248
|
||||
k[31] &= 127
|
||||
k[31] |= 64
|
||||
return k
|
||||
}
|
||||
|
||||
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
|
||||
}
|
54
mk-rel.sh
Executable file
54
mk-rel.sh
Executable file
|
@ -0,0 +1,54 @@
|
|||
#! /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
|
3
renovate.json
Normal file
3
renovate.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
[](https://godoc.org/github.com/opencoff/sigtool/sign)
|
||||
[](https://godoc.org/git.rgst.io/homelab/sigtool/v3/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://github.com/opencoff/sigtool) uses this library.
|
||||
The companion program [sigtool](https://git.rgst.io/homelab/sigtool/v3) uses this library.
|
||||
## License
|
||||
GPL v2.0
|
||||
|
|
790
sign/encrypt.go
790
sign/encrypt.go
File diff suppressed because it is too large
Load diff
|
@ -34,8 +34,10 @@ func (b *Buffer) Close() error {
|
|||
func TestEncryptSimple(t *testing.T) {
|
||||
assert := newAsserter(t)
|
||||
|
||||
receiver, err := NewKeypair()
|
||||
assert(err == nil, "receiver keypair gen failed: %s", err)
|
||||
sk, err := NewPrivateKey()
|
||||
assert(err == nil, "SK gen failed: %s", err)
|
||||
|
||||
pk := sk.PublicKey()
|
||||
|
||||
var blkSize int = 1024
|
||||
var size int = (blkSize * 10)
|
||||
|
@ -49,7 +51,7 @@ func TestEncryptSimple(t *testing.T) {
|
|||
ee, err := NewEncryptor(nil, uint64(blkSize))
|
||||
assert(err == nil, "encryptor create fail: %s", err)
|
||||
|
||||
err = ee.AddRecipient(&receiver.Pub)
|
||||
err = ee.AddRecipient(pk)
|
||||
assert(err == nil, "can't add recipient: %s", err)
|
||||
|
||||
rd := bytes.NewBuffer(buf)
|
||||
|
@ -63,7 +65,7 @@ func TestEncryptSimple(t *testing.T) {
|
|||
dd, err := NewDecryptor(rd)
|
||||
assert(err == nil, "decryptor create fail: %s", err)
|
||||
|
||||
err = dd.SetPrivateKey(&receiver.Sec, nil)
|
||||
err = dd.SetPrivateKey(sk, nil)
|
||||
assert(err == nil, "decryptor can't add SK: %s", err)
|
||||
|
||||
wr = Buffer{}
|
||||
|
@ -76,12 +78,67 @@ func TestEncryptSimple(t *testing.T) {
|
|||
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)
|
||||
|
||||
receiver, err := NewKeypair()
|
||||
assert(err == nil, "receiver keypair gen failed: %s", err)
|
||||
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)
|
||||
|
@ -95,7 +152,7 @@ func TestEncryptCorrupted(t *testing.T) {
|
|||
ee, err := NewEncryptor(nil, uint64(blkSize))
|
||||
assert(err == nil, "encryptor create fail: %s", err)
|
||||
|
||||
err = ee.AddRecipient(&receiver.Pub)
|
||||
err = ee.AddRecipient(pk)
|
||||
assert(err == nil, "can't add recipient: %s", err)
|
||||
|
||||
rd := bytes.NewReader(buf)
|
||||
|
@ -107,6 +164,7 @@ func TestEncryptCorrupted(t *testing.T) {
|
|||
rb := wr.Bytes()
|
||||
n := len(rb)
|
||||
|
||||
// corrupt the input
|
||||
for i := 0; i < n; i++ {
|
||||
j := randint() % n
|
||||
rb[j] = byte(randint() & 0xff)
|
||||
|
@ -122,11 +180,11 @@ func TestEncryptCorrupted(t *testing.T) {
|
|||
func TestEncryptSenderVerified(t *testing.T) {
|
||||
assert := newAsserter(t)
|
||||
|
||||
sender, err := NewKeypair()
|
||||
assert(err == nil, "sender keypair gen failed: %s", err)
|
||||
sender, err := NewPrivateKey()
|
||||
assert(err == nil, "sender SK gen failed: %s", err)
|
||||
|
||||
receiver, err := NewKeypair()
|
||||
assert(err == nil, "receiver keypair 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)
|
||||
|
@ -137,10 +195,10 @@ func TestEncryptSenderVerified(t *testing.T) {
|
|||
buf[i] = byte(i & 0xff)
|
||||
}
|
||||
|
||||
ee, err := NewEncryptor(&sender.Sec, uint64(blkSize))
|
||||
ee, err := NewEncryptor(sender, uint64(blkSize))
|
||||
assert(err == nil, "encryptor create fail: %s", err)
|
||||
|
||||
err = ee.AddRecipient(&receiver.Pub)
|
||||
err = ee.AddRecipient(receiver.PublicKey())
|
||||
assert(err == nil, "can't add recipient: %s", err)
|
||||
|
||||
rd := bytes.NewBuffer(buf)
|
||||
|
@ -154,14 +212,15 @@ func TestEncryptSenderVerified(t *testing.T) {
|
|||
dd, err := NewDecryptor(rd)
|
||||
assert(err == nil, "decryptor create fail: %s", err)
|
||||
|
||||
// first send a wrong sender key
|
||||
randkey, err := NewKeypair()
|
||||
assert(err == nil, "receiver rand keypair gen failed: %s", err)
|
||||
randkey, err := NewPrivateKey()
|
||||
assert(err == nil, "rand SK gen failed: %s", err)
|
||||
|
||||
err = dd.SetPrivateKey(&receiver.Sec, &randkey.Pub)
|
||||
// first send a wrong sender PK
|
||||
err = dd.SetPrivateKey(receiver, randkey.PublicKey())
|
||||
assert(err != nil, "decryptor failed to verify sender")
|
||||
|
||||
err = dd.SetPrivateKey(&receiver.Sec, &sender.Pub)
|
||||
// then the correct sender PK
|
||||
err = dd.SetPrivateKey(receiver, sender.PublicKey())
|
||||
assert(err == nil, "decryptor can't add SK: %s", err)
|
||||
|
||||
wr = Buffer{}
|
||||
|
@ -178,8 +237,8 @@ func TestEncryptSenderVerified(t *testing.T) {
|
|||
func TestEncryptMultiReceiver(t *testing.T) {
|
||||
assert := newAsserter(t)
|
||||
|
||||
sender, err := NewKeypair()
|
||||
assert(err == nil, "sender keypair gen failed: %s", err)
|
||||
sender, err := NewPrivateKey()
|
||||
assert(err == nil, "sender SK gen failed: %s", err)
|
||||
|
||||
var blkSize int = 1024
|
||||
var size int = (blkSize * 23) + randmod(blkSize)
|
||||
|
@ -190,17 +249,17 @@ func TestEncryptMultiReceiver(t *testing.T) {
|
|||
buf[i] = byte(i & 0xff)
|
||||
}
|
||||
|
||||
ee, err := NewEncryptor(&sender.Sec, uint64(blkSize))
|
||||
ee, err := NewEncryptor(sender, uint64(blkSize))
|
||||
assert(err == nil, "encryptor create fail: %s", err)
|
||||
|
||||
n := 4
|
||||
rx := make([]*Keypair, n)
|
||||
rx := make([]*PrivateKey, n)
|
||||
for i := 0; i < n; i++ {
|
||||
r, err := NewKeypair()
|
||||
assert(err == nil, "can't make receiver key %d: %s", i, err)
|
||||
r, err := NewPrivateKey()
|
||||
assert(err == nil, "can't make receiver SK %d: %s", i, err)
|
||||
rx[i] = r
|
||||
|
||||
err = ee.AddRecipient(&r.Pub)
|
||||
err = ee.AddRecipient(r.PublicKey())
|
||||
assert(err == nil, "can't add recipient %d: %s", i, err)
|
||||
}
|
||||
|
||||
|
@ -217,7 +276,7 @@ func TestEncryptMultiReceiver(t *testing.T) {
|
|||
dd, err := NewDecryptor(rd)
|
||||
assert(err == nil, "decryptor %d create fail: %s", i, err)
|
||||
|
||||
err = dd.SetPrivateKey(&rx[i].Sec, &sender.Pub)
|
||||
err = dd.SetPrivateKey(rx[i], sender.PublicKey())
|
||||
assert(err == nil, "decryptor can't add SK %d: %s", i, err)
|
||||
|
||||
wr = Buffer{}
|
||||
|
@ -235,7 +294,7 @@ func TestEncryptMultiReceiver(t *testing.T) {
|
|||
func TestStreamIO(t *testing.T) {
|
||||
assert := newAsserter(t)
|
||||
|
||||
receiver, err := NewKeypair()
|
||||
receiver, err := NewPrivateKey()
|
||||
assert(err == nil, "receiver keypair gen failed: %s", err)
|
||||
|
||||
var blkSize int = 1024
|
||||
|
@ -250,7 +309,7 @@ func TestStreamIO(t *testing.T) {
|
|||
ee, err := NewEncryptor(nil, uint64(blkSize))
|
||||
assert(err == nil, "encryptor create fail: %s", err)
|
||||
|
||||
err = ee.AddRecipient(&receiver.Pub)
|
||||
err = ee.AddRecipient(receiver.PublicKey())
|
||||
assert(err == nil, "can't add recipient: %s", err)
|
||||
|
||||
wr := Buffer{}
|
||||
|
@ -283,7 +342,7 @@ func TestStreamIO(t *testing.T) {
|
|||
dd, err := NewDecryptor(rd)
|
||||
assert(err == nil, "decryptor create fail: %s", err)
|
||||
|
||||
err = dd.SetPrivateKey(&receiver.Sec, nil)
|
||||
err = dd.SetPrivateKey(receiver, nil)
|
||||
assert(err == nil, "decryptor can't add SK: %s", err)
|
||||
|
||||
rio, err := dd.NewStreamReader()
|
||||
|
@ -313,6 +372,93 @@ func TestStreamIO(t *testing.T) {
|
|||
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
|
|
45
sign/errors.go
Normal file
45
sign/errors.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
// 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")
|
||||
)
|
67
sign/iomisc.go
Normal file
67
sign/iomisc.go
Normal file
|
@ -0,0 +1,67 @@
|
|||
// 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
|
||||
}
|
605
sign/keys.go
605
sign/keys.go
|
@ -25,19 +25,14 @@ import (
|
|||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"os"
|
||||
|
||||
Ed "crypto/ed25519"
|
||||
"golang.org/x/crypto/scrypt"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/opencoff/go-utils"
|
||||
"github.com/opencoff/sigtool/internal/pb"
|
||||
"golang.org/x/crypto/scrypt"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Private Ed25519 key
|
||||
|
@ -64,21 +59,10 @@ type PublicKey struct {
|
|||
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
|
||||
}
|
||||
|
||||
// Length of Ed25519 Public Key Hash
|
||||
const PKHashLength = 16
|
||||
|
||||
// constants we use in this module
|
||||
const (
|
||||
// Scrypt parameters
|
||||
_N int = 1 << 19
|
||||
|
@ -124,55 +108,27 @@ type signature struct {
|
|||
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]
|
||||
}
|
||||
|
||||
// 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)
|
||||
// NewPrivateKey generates a new Ed25519 private key
|
||||
func NewPrivateKey() (*PrivateKey, error) {
|
||||
pkb, skb, err := Ed.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Can't generate Ed25519 keys: %s", err)
|
||||
return nil, 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, getpw func() ([]byte, error)) 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)
|
||||
sk := &PrivateKey{
|
||||
Sk: []byte(skb),
|
||||
pk: &PublicKey{
|
||||
Pk: []byte(pkb),
|
||||
hash: pkhash([]byte(pkb)),
|
||||
},
|
||||
}
|
||||
|
||||
err = sk.serialize(skf, comment, getpw)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Can't serialize to %s: %s", pkf, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return sk, nil
|
||||
}
|
||||
|
||||
// Read the private key in 'fn', optionally decrypting it using
|
||||
|
@ -183,73 +139,30 @@ func ReadPrivateKey(fn string, getpw func() ([]byte, error)) (*PrivateKey, error
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if bytes.Index(yml, []byte("OPENSSH PRIVATE KEY-")) > 0 {
|
||||
return parseSSHPrivateKey(yml, getpw)
|
||||
var sk PrivateKey
|
||||
if err = sk.UnmarshalBinary(yml, getpw); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if pw, err := getpw(); err == nil {
|
||||
return MakePrivateKey(yml, pw)
|
||||
}
|
||||
return nil, err
|
||||
return &sk, nil
|
||||
}
|
||||
|
||||
// Make a private key from bytes 'yml' and password 'pw'. The bytes
|
||||
// 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, pw []byte) (*PrivateKey, error) {
|
||||
var ssk serializedPrivKey
|
||||
func MakePrivateKey(yml []byte, getpw func() ([]byte, error)) (*PrivateKey, error) {
|
||||
var sk PrivateKey
|
||||
|
||||
err := yaml.Unmarshal(yml, &ssk)
|
||||
err := sk.UnmarshalBinary(yml, getpw)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("make priv key: can't parse YAML: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(ssk.Salt) == 0 || len(ssk.Esk) == 0 {
|
||||
return nil, fmt.Errorf("sign: not YAML private key")
|
||||
}
|
||||
|
||||
b64 := base64.StdEncoding.DecodeString
|
||||
|
||||
salt, err := b64(ssk.Salt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("make priv key: can't decode salt: %s", err)
|
||||
}
|
||||
|
||||
esk, err := b64(ssk.Esk)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("make priv key: can't decode key: %s", err)
|
||||
}
|
||||
|
||||
// We take short passwords and extend them
|
||||
pwb := sha512.Sum512(pw)
|
||||
|
||||
// "32" == Length of AES-256 key
|
||||
key, err := scrypt.Key(pwb[:], salt, ssk.N, ssk.R, ssk.P, 32)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("make priv key: can't derive key: %s", err)
|
||||
}
|
||||
|
||||
aes, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("make priv key: aes failure: %s", err)
|
||||
}
|
||||
|
||||
ae, err := cipher.NewGCM(aes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("make priv key: aes failure: %s", err)
|
||||
}
|
||||
|
||||
skb, err := ae.Open(nil, salt[:ae.NonceSize()], esk, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("make priv key: wrong password")
|
||||
}
|
||||
|
||||
return PrivateKeyFromBytes(skb)
|
||||
return &sk, nil
|
||||
}
|
||||
|
||||
// Make a private key from 64-bytes of extended Ed25519 key
|
||||
func PrivateKeyFromBytes(buf []byte) (*PrivateKey, error) {
|
||||
// make a PrivateKey from a byte array containing ed25519 raw SK
|
||||
func makePrivateKeyFromBytes(sk *PrivateKey, buf []byte) error {
|
||||
if len(buf) != 64 {
|
||||
return nil, fmt.Errorf("private key is malformed (len %d!)", len(buf))
|
||||
return fmt.Errorf("private key is malformed (len %d!)", len(buf))
|
||||
}
|
||||
|
||||
skb := make([]byte, 64)
|
||||
|
@ -262,12 +175,18 @@ func PrivateKeyFromBytes(buf []byte) (*PrivateKey, error) {
|
|||
Pk: []byte(edpk),
|
||||
hash: pkhash([]byte(edpk)),
|
||||
}
|
||||
sk := &PrivateKey{
|
||||
Sk: skb,
|
||||
pk: pk,
|
||||
}
|
||||
sk.Sk = skb
|
||||
sk.pk = pk
|
||||
return nil
|
||||
}
|
||||
|
||||
return sk, 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
|
||||
|
@ -276,7 +195,7 @@ func (sk *PrivateKey) PublicKey() *PublicKey {
|
|||
}
|
||||
|
||||
// Convert an Ed25519 Private Key to Curve25519 Private key
|
||||
func (sk *PrivateKey) toCurve25519SK() []byte {
|
||||
func (sk *PrivateKey) ToCurve25519SK() []byte {
|
||||
if sk.ck == nil {
|
||||
var ek [64]byte
|
||||
|
||||
|
@ -290,12 +209,228 @@ func (sk *PrivateKey) toCurve25519SK() []byte {
|
|||
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 {
|
||||
func (pk *PublicKey) ToCurve25519PK() []byte {
|
||||
if pk.ck != nil {
|
||||
return pk.ck
|
||||
}
|
||||
|
@ -335,131 +470,8 @@ func (pk *PublicKey) Hash() []byte {
|
|||
return pk.hash
|
||||
}
|
||||
|
||||
// Serialize the private key to a file
|
||||
// AEAD encryption for protecting the private key
|
||||
// Format: YAML
|
||||
// All []byte are in base64 (RawEncoding)
|
||||
func (sk *PrivateKey) serialize(fn, comment string, getpw func() ([]byte, error)) error {
|
||||
pw, err := getpw()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// expand the password into 64 bytes
|
||||
pass := sha512.Sum512(pw)
|
||||
salt := make([]byte, 32)
|
||||
|
||||
pb.Randread(salt)
|
||||
|
||||
// "32" == Length of AES-256 key
|
||||
key, err := scrypt.Key(pass[:], salt, _N, _r, _p, 32)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal: can't derive scrypt key: %s", err)
|
||||
}
|
||||
|
||||
aes, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal: %s", err)
|
||||
}
|
||||
|
||||
ae, err := cipher.NewGCM(aes)
|
||||
if err != nil {
|
||||
return 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.
|
||||
|
||||
out, err := yaml.Marshal(&ssk)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't marahal to YAML: %s", err)
|
||||
}
|
||||
|
||||
return writeFile(fn, out, 0600)
|
||||
}
|
||||
|
||||
// --- 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
|
||||
}
|
||||
|
||||
// first try to parse as a ssh key
|
||||
pk, err := parseSSHPublicKey(yml)
|
||||
if err != nil {
|
||||
pk, err = MakePublicKey(yml)
|
||||
}
|
||||
return pk, err
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
if len(spk.Pk) == 0 {
|
||||
return nil, fmt.Errorf("sign: not a YAML public key")
|
||||
}
|
||||
|
||||
b64 := base64.StdEncoding.DecodeString
|
||||
var pkb []byte
|
||||
|
||||
if pkb, err = b64(spk.Pk); err != nil {
|
||||
return nil, fmt.Errorf("can't decode YAML:Pk: %s", err)
|
||||
}
|
||||
|
||||
if pk, err := PublicKeyFromBytes(pkb); err == nil {
|
||||
pk.Comment = spk.Comment
|
||||
return pk, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// MarshalBinary marshals a PublicKey into a byte array
|
||||
func (pk *PublicKey) MarshalBinary(comment string) ([]byte, error) {
|
||||
b64 := base64.StdEncoding.EncodeToString
|
||||
spk := &serializedPubKey{
|
||||
Comment: comment,
|
||||
|
@ -467,76 +479,45 @@ func (pk *PublicKey) serialize(fn, comment string) error {
|
|||
Hash: b64(pk.hash),
|
||||
}
|
||||
|
||||
out, err := yaml.Marshal(spk)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't marahal to YAML: %s", err)
|
||||
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
|
||||
}
|
||||
|
||||
return writeFile(fn, out, 0644)
|
||||
// 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 --
|
||||
|
||||
// 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 clamp(k []byte) []byte {
|
||||
k[0] &= 248
|
||||
k[31] &= 127
|
||||
|
|
45
sign/rand.go
Normal file
45
sign/rand.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
// 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)
|
||||
}
|
58
sign/sign.go
58
sign/sign.go
|
@ -27,13 +27,21 @@ import (
|
|||
"io/ioutil"
|
||||
|
||||
Ed "crypto/ed25519"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// An Ed25519 Signature
|
||||
type Signature struct {
|
||||
Sig []byte // Ed25519 sig bytes
|
||||
pkhash []byte // [0:16] SHA256 hash of public key needed for verification
|
||||
}
|
||||
|
||||
// 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"))
|
||||
|
@ -80,12 +88,13 @@ func ReadSignature(fn string) (*Signature, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return MakeSignature(yml)
|
||||
var sig Signature
|
||||
return makeSignature(&sig, yml)
|
||||
}
|
||||
|
||||
// Parse serialized signature from bytes 'b' and construct a
|
||||
// Signature object
|
||||
func MakeSignature(b []byte) (*Signature, error) {
|
||||
func makeSignature(sig *Signature, b []byte) (*Signature, error) {
|
||||
var ss signature
|
||||
err := yaml.Unmarshal(b, &ss)
|
||||
if err != nil {
|
||||
|
@ -104,29 +113,33 @@ func MakeSignature(b []byte) (*Signature, error) {
|
|||
return nil, fmt.Errorf("can't decode Base64:Pkhash <%s>: %s", ss.Pkhash, err)
|
||||
}
|
||||
|
||||
return &Signature{Sig: s, pkhash: p}, nil
|
||||
sig.Sig = s
|
||||
sig.pkhash = p
|
||||
return sig, nil
|
||||
}
|
||||
|
||||
// Serialize a signature suitable for storing in durable media
|
||||
func (sig *Signature) Serialize(comment string) ([]byte, error) {
|
||||
|
||||
// MarshalBinary marshals a signature into a byte stream with
|
||||
// an optional caller supplied comment.
|
||||
func (sig *Signature) MarshalBinary(comment string) ([]byte, error) {
|
||||
sigs := base64.StdEncoding.EncodeToString(sig.Sig)
|
||||
pks := base64.StdEncoding.EncodeToString(sig.pkhash)
|
||||
ss := &signature{Comment: comment, Pkhash: pks, Signature: sigs}
|
||||
|
||||
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
|
||||
return yaml.Marshal(ss)
|
||||
}
|
||||
|
||||
// SerializeFile serializes the signature to an output file 'f'
|
||||
func (sig *Signature) SerializeFile(fn, comment string) error {
|
||||
b, err := sig.Serialize(comment)
|
||||
// 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)
|
||||
if err == nil {
|
||||
err = writeFile(fn, b, 0644)
|
||||
err = writeFile(fn, b, ovwrite, 0644)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
@ -141,25 +154,24 @@ func (sig *Signature) IsPKMatch(pk *PublicKey) bool {
|
|||
// 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)
|
||||
return pk.VerifyMessage(ck, sig), nil
|
||||
}
|
||||
|
||||
// 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, error) {
|
||||
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)[:]
|
||||
|
||||
x := Ed.PublicKey(pk.Pk)
|
||||
return Ed.Verify(x, ck, sig.Sig), nil
|
||||
return Ed.Verify(x, ck, sig.Sig)
|
||||
}
|
||||
|
||||
// vim: noexpandtab:ts=8:sw=8:tw=92:
|
||||
|
|
|
@ -19,8 +19,6 @@ import (
|
|||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/opencoff/sigtool/internal/pb"
|
||||
)
|
||||
|
||||
// Return a temp dir in a temp-dir
|
||||
|
@ -30,7 +28,7 @@ func tempdir(t *testing.T) string {
|
|||
var b [10]byte
|
||||
|
||||
dn := os.TempDir()
|
||||
pb.Randread(b[:])
|
||||
randRead(b[:])
|
||||
|
||||
tmp := path.Join(dn, fmt.Sprintf("%x", b[:]))
|
||||
err := os.MkdirAll(tmp, 0755)
|
||||
|
@ -40,16 +38,20 @@ 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 []byte("abc"), nil
|
||||
return fixedPw, nil
|
||||
}
|
||||
|
||||
func wrongPw() ([]byte, error) {
|
||||
return []byte("xyz"), nil
|
||||
return badPw, nil
|
||||
}
|
||||
func emptyPw() ([]byte, error) {
|
||||
return nil, nil
|
||||
return nilPw, nil
|
||||
}
|
||||
|
||||
// Return true if file exists, false otherwise
|
||||
|
@ -82,112 +84,106 @@ p: 1
|
|||
func TestSignSimple(t *testing.T) {
|
||||
assert := newAsserter(t)
|
||||
|
||||
kp, err := NewKeypair()
|
||||
assert(err == nil, "NewKeyPair() fail")
|
||||
sk, err := NewPrivateKey()
|
||||
assert(err == nil, "NewPrivateKey() fail")
|
||||
|
||||
dn := tempdir(t)
|
||||
pk := sk.PublicKey()
|
||||
|
||||
dn := t.TempDir()
|
||||
bn := fmt.Sprintf("%s/t0", dn)
|
||||
|
||||
err = kp.Serialize(bn, "", hardcodedPw)
|
||||
assert(err == nil, "keyPair.Serialize() fail")
|
||||
|
||||
pkf := fmt.Sprintf("%s.pub", bn)
|
||||
skf := fmt.Sprintf("%s.key", bn)
|
||||
|
||||
// We must find these two files
|
||||
assert(fileExists(pkf), "missing pkf")
|
||||
assert(fileExists(skf), "missing skf")
|
||||
err = pk.Serialize(pkf, "", true)
|
||||
assert(err == nil, "can't serialize pk %s", pkf)
|
||||
|
||||
pk, err := ReadPublicKey(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)
|
||||
|
||||
npk, err := ReadPublicKey(pkf)
|
||||
assert(err == nil, "ReadPK() fail")
|
||||
|
||||
// -ditto- for Sk
|
||||
sk, err := ReadPrivateKey(pkf, emptyPw)
|
||||
// send the public key as private key
|
||||
nsk, err := ReadPrivateKey(pkf, emptyPw)
|
||||
assert(err != nil, "bad SK ReadSK fail: %s", err)
|
||||
|
||||
sk, err = ReadPrivateKey(skf, emptyPw)
|
||||
assert(err != nil, "ReadSK() empty pw fail: ks", err)
|
||||
nsk, err = ReadPrivateKey(skf, emptyPw)
|
||||
assert(err != nil, "ReadSK() worked with empty pw")
|
||||
|
||||
sk, err = ReadPrivateKey(skf, wrongPw)
|
||||
assert(err != nil, "ReadSK() wrong pw fail: %s", err)
|
||||
nsk, err = ReadPrivateKey(skf, wrongPw)
|
||||
assert(err != nil, "ReadSK() worked with wrong pw")
|
||||
|
||||
badf := fmt.Sprintf("%s/badf.key", dn)
|
||||
err = ioutil.WriteFile(badf, []byte(badsk), 0600)
|
||||
assert(err == nil, "write badsk")
|
||||
assert(err == nil, "can't write badsk: %s", err)
|
||||
|
||||
sk, err = ReadPrivateKey(badf, hardcodedPw)
|
||||
assert(err != nil, "badsk read fail: %s", err)
|
||||
nsk, err = ReadPrivateKey(badf, hardcodedPw)
|
||||
assert(err != nil, "decoded bad SK")
|
||||
|
||||
// Finally, with correct password it should work.
|
||||
sk, err = ReadPrivateKey(skf, hardcodedPw)
|
||||
assert(err == nil, "ReadSK() correct pw fail")
|
||||
nsk, err = ReadPrivateKey(skf, hardcodedPw)
|
||||
assert(err == nil, "ReadSK() correct pw fail: %s", err)
|
||||
|
||||
// And, deserialized keys should be identical
|
||||
assert(byteEq(pk.Pk, kp.Pub.Pk), "pkbytes unequal")
|
||||
assert(byteEq(sk.Sk, kp.Sec.Sk), "skbytes unequal")
|
||||
|
||||
os.RemoveAll(dn)
|
||||
assert(byteEq(pk.Pk, npk.Pk), "pkbytes unequal")
|
||||
assert(byteEq(sk.Sk, nsk.Sk), "skbytes unequal")
|
||||
}
|
||||
|
||||
// #2. Create new key pair, sign a rand buffer and verify
|
||||
func TestSignRandBuf(t *testing.T) {
|
||||
assert := newAsserter(t)
|
||||
kp, err := NewKeypair()
|
||||
assert(err == nil, "NewKeyPair() fail")
|
||||
|
||||
sk, err := NewPrivateKey()
|
||||
assert(err == nil, "NewPrivateKey() fail: %s", err)
|
||||
|
||||
var ck [64]byte // simulates sha512 sum
|
||||
|
||||
pb.Randread(ck[:])
|
||||
randRead(ck[:])
|
||||
|
||||
pk := &kp.Pub
|
||||
sk := &kp.Sec
|
||||
pk := sk.PublicKey()
|
||||
|
||||
ss, err := sk.SignMessage(ck[:], "")
|
||||
assert(err == nil, "sk.sign fail")
|
||||
assert(err == nil, "sk.sign fail: %s", err)
|
||||
assert(ss != nil, "sig is null")
|
||||
|
||||
// verify sig
|
||||
assert(ss.IsPKMatch(pk), "pk match fail")
|
||||
|
||||
// Corrupt the pkhash and see
|
||||
pb.Randread(ss.pkhash)
|
||||
randRead(ss.pkhash)
|
||||
assert(!ss.IsPKMatch(pk), "corrupt pk match fail")
|
||||
|
||||
// Incorrect checksum == should fail verification
|
||||
ok, err := pk.VerifyMessage(ck[:16], ss)
|
||||
assert(err == nil, "bad ck verify err fail")
|
||||
ok := pk.VerifyMessage(ck[:16], ss)
|
||||
assert(!ok, "bad ck verify fail")
|
||||
|
||||
// proper checksum == should work
|
||||
ok, err = pk.VerifyMessage(ck[:], ss)
|
||||
assert(err == nil, "verify err")
|
||||
ok = pk.VerifyMessage(ck[:], ss)
|
||||
assert(ok, "verify fail")
|
||||
|
||||
// Now sign a file
|
||||
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, "", emptyPw)
|
||||
assert(err == nil, "keyPair.Serialize() fail")
|
||||
|
||||
// Now read the private key and sign
|
||||
sk, err = ReadPrivateKey(skf, emptyPw)
|
||||
assert(err == nil, "readSK fail")
|
||||
|
||||
pk, err = ReadPublicKey(pkf)
|
||||
assert(err == nil, "ReadPK fail")
|
||||
dn := t.TempDir()
|
||||
|
||||
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")
|
||||
assert(err == nil, "file.dat creat file: %s", err)
|
||||
|
||||
for i := 0; i < 8; i++ {
|
||||
pb.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))
|
||||
|
@ -196,27 +192,31 @@ func TestSignRandBuf(t *testing.T) {
|
|||
fd.Close()
|
||||
|
||||
sig, err := sk.SignFile(zf)
|
||||
assert(err == nil, "file.dat sign fail")
|
||||
assert(err == nil, "file.dat sign fail: %s", err)
|
||||
assert(sig != nil, "file.dat sign nil")
|
||||
|
||||
ok, err = pk.VerifyFile(zf, sig)
|
||||
assert(err == nil, "file.dat verify fail")
|
||||
assert(err == nil, "file.dat verify fail: %s", err)
|
||||
assert(ok, "file.dat verify false")
|
||||
|
||||
// Now, serialize the signature and read it back
|
||||
sf := fmt.Sprintf("%s/file.sig", dn)
|
||||
err = sig.SerializeFile(sf, "")
|
||||
assert(err == nil, "sig serialize fail")
|
||||
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?!")
|
||||
|
||||
s2, err := ReadSignature(sf)
|
||||
assert(err == nil, "file.sig read fail")
|
||||
assert(err == nil, "file.sig read fail: %s", err)
|
||||
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")
|
||||
assert(err == nil, "file.dat stat fail: %s", err)
|
||||
|
||||
n := st.Size()
|
||||
assert(n == 8192*8, "file.dat size fail")
|
||||
|
@ -224,12 +224,12 @@ func TestSignRandBuf(t *testing.T) {
|
|||
os.Truncate(zf, n-1)
|
||||
|
||||
st, err = os.Stat(zf)
|
||||
assert(err == nil, "file.dat stat2 fail")
|
||||
assert(err == nil, "file.dat stat2 fail: %s", err)
|
||||
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")
|
||||
assert(err == nil, "file.dat corrupt i/o fail: %s", err)
|
||||
assert(!ok, "file.dat corrupt verify false")
|
||||
|
||||
os.RemoveAll(dn)
|
||||
|
@ -237,7 +237,7 @@ func TestSignRandBuf(t *testing.T) {
|
|||
|
||||
func Benchmark_Keygen(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = NewKeypair()
|
||||
_, _ = NewPrivateKey()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -254,7 +254,8 @@ func Benchmark_Sig(b *testing.B) {
|
|||
}
|
||||
|
||||
b.StopTimer()
|
||||
kp, _ := NewKeypair()
|
||||
sk, _ := NewPrivateKey()
|
||||
pk := sk.PublicKey()
|
||||
var sig *Signature
|
||||
for _, sz := range sizes {
|
||||
buf := randbuf(sz)
|
||||
|
@ -264,11 +265,11 @@ func Benchmark_Sig(b *testing.B) {
|
|||
b.ResetTimer()
|
||||
|
||||
b.Run(s0, func(b *testing.B) {
|
||||
sig = benchSign(b, buf, &kp.Sec)
|
||||
sig = benchSign(b, buf, sk)
|
||||
})
|
||||
|
||||
b.Run(s1, func(b *testing.B) {
|
||||
benchVerify(b, buf, sig, &kp.Pub)
|
||||
benchVerify(b, buf, sig, pk)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -288,7 +289,7 @@ func benchVerify(b *testing.B, buf []byte, sig *Signature, pk *PublicKey) {
|
|||
|
||||
func randbuf(sz uint) []byte {
|
||||
b := make([]byte, sz)
|
||||
pb.Randread(b)
|
||||
randRead(b)
|
||||
return b
|
||||
}
|
||||
|
||||
|
|
33
sign/ssh.go
33
sign/ssh.go
|
@ -25,8 +25,8 @@ import (
|
|||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/dchest/bcrypt_pbkdf"
|
||||
|
@ -34,17 +34,6 @@ import (
|
|||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrIncorrectPassword = errors.New("ssh: Invalid Passphrase")
|
||||
ErrNoPEMFound = errors.New("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")
|
||||
)
|
||||
|
||||
const keySizeAES256 = 32
|
||||
|
||||
// ParseEncryptedRawPrivateKey returns a private key from an
|
||||
|
@ -68,12 +57,13 @@ func parseSSHPrivateKey(data []byte, getpw func() ([]byte, error)) (*PrivateKey,
|
|||
}
|
||||
|
||||
func parseSSHPublicKey(in []byte) (*PublicKey, error) {
|
||||
v := bytes.Split(in, []byte(" \t"))
|
||||
splitter := regexp.MustCompile("[ \\t]+")
|
||||
v := splitter.Split(string(in), -1)
|
||||
if len(v) != 3 {
|
||||
return nil, ErrBadPublicKey
|
||||
}
|
||||
|
||||
return parseEncPubKey(v[1], string(v[2]))
|
||||
return parseEncPubKey([]byte(v[1]), v[2])
|
||||
}
|
||||
|
||||
// parse a wire encoded public key
|
||||
|
@ -105,11 +95,13 @@ func parseEncPubKey(in []byte, comm string) (*PublicKey, error) {
|
|||
return nil, ErrBadTrailers
|
||||
}
|
||||
|
||||
pk, err := PublicKeyFromBytes(w.KeyBytes)
|
||||
if err == nil {
|
||||
var pk PublicKey
|
||||
|
||||
if err = makePublicKeyFromBytes(&pk, w.KeyBytes); err == nil {
|
||||
pk.Comment = strings.TrimSpace(comm)
|
||||
return &pk, nil
|
||||
}
|
||||
return pk, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func parseString(in []byte) (out, rest []byte, ok bool) {
|
||||
|
@ -353,8 +345,11 @@ func parseOpenSSHPrivateKey(data []byte, getpw func() ([]byte, error)) (*Private
|
|||
}
|
||||
}
|
||||
|
||||
pk, err := PrivateKeyFromBytes(key.Priv)
|
||||
return pk, err
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -15,8 +15,6 @@
|
|||
package sign
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
|
@ -31,6 +29,8 @@ type encWriter struct {
|
|||
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) {
|
||||
|
@ -98,7 +98,7 @@ func (w *encWriter) Close() error {
|
|||
}
|
||||
|
||||
w.n = 0
|
||||
w.err = errClosed
|
||||
w.err = ErrClosed
|
||||
return w.wr.Close()
|
||||
}
|
||||
|
||||
|
@ -110,10 +110,12 @@ type encReader struct {
|
|||
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, fmt.Errorf("streamReader: wrapped-key not decrypted (missing SetPrivateKey()?")
|
||||
return nil, ErrNoKey
|
||||
}
|
||||
|
||||
if d.eof {
|
||||
|
@ -158,7 +160,3 @@ func (r *encReader) Read(b []byte) (int, error) {
|
|||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
var (
|
||||
errClosed = errors.New("encrypt: stream already closed")
|
||||
)
|
||||
|
|
362
sigtool.go
362
sigtool.go
|
@ -1,362 +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"
|
||||
"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 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, "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 err error
|
||||
|
||||
kp, err := sign.NewKeypair()
|
||||
if err != nil {
|
||||
die("%s", err)
|
||||
}
|
||||
|
||||
err = kp.Serialize(bn, comment, 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", true)
|
||||
if err != nil {
|
||||
die("%s", err)
|
||||
}
|
||||
}
|
||||
return []byte(pws), nil
|
||||
})
|
||||
if err != nil {
|
||||
die("%s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Run the 'sign' command.
|
||||
func signify(args []string) {
|
||||
var nopw, 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(&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.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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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:
|
|
@ -20,9 +20,10 @@ import (
|
|||
"os"
|
||||
"strings"
|
||||
|
||||
"git.rgst.io/homelab/sigtool/v3/sign"
|
||||
"github.com/opencoff/go-fio"
|
||||
"github.com/opencoff/go-utils"
|
||||
flag "github.com/opencoff/pflag"
|
||||
"github.com/opencoff/sigtool/sign"
|
||||
)
|
||||
|
||||
// sigtool encrypt [-i|--identity my.key] to.pub [to.pub] [ssh.pub] inputfile|- [-o output]
|
||||
|
@ -35,19 +36,25 @@ func encrypt(args []string) {
|
|||
|
||||
var outfile string
|
||||
var keyfile string
|
||||
var szstr string = "128k"
|
||||
var envpw string
|
||||
var nopw bool
|
||||
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", "", "", "Use passphrase from environment variable `E`")
|
||||
fs.SizeVarP(&blksize, "block-size", "B", 128 * 1024, "Use `S` as the encryption block size")
|
||||
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)
|
||||
Die("%s", err)
|
||||
}
|
||||
|
||||
if blksize, err = utils.ParseSize(szstr); err != nil {
|
||||
Die("%s", err)
|
||||
}
|
||||
|
||||
var pws, infile string
|
||||
|
@ -63,19 +70,19 @@ func encrypt(args []string) {
|
|||
} else {
|
||||
pws, err = utils.Askpass("Enter passphrase for private key", false)
|
||||
if err != nil {
|
||||
die("%s", err)
|
||||
Die("%s", err)
|
||||
}
|
||||
}
|
||||
return []byte(pws), nil
|
||||
})
|
||||
if err != nil {
|
||||
die("%s", err)
|
||||
Die("%s", err)
|
||||
}
|
||||
}
|
||||
|
||||
args = fs.Args()
|
||||
if len(args) < 2 {
|
||||
die("Insufficient args. Try '%s --help'", os.Args[0])
|
||||
Die("Insufficient args. Try '%s --help'", os.Args[0])
|
||||
}
|
||||
|
||||
var infd io.Reader = os.Stdin
|
||||
|
@ -95,14 +102,14 @@ func encrypt(args []string) {
|
|||
// Lets try to read the authorized files
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
die("can't find homedir for this user")
|
||||
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 err != os.ErrNotExist {
|
||||
die("can't open %s: %s", authkeys, err)
|
||||
if !os.IsNotExist(err) {
|
||||
Die("can't open %s: %s", authkeys, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,31 +121,43 @@ func encrypt(args []string) {
|
|||
}
|
||||
|
||||
if len(outfile) > 0 && outfile != "-" {
|
||||
var mode os.FileMode = 0600 // conservative output mode
|
||||
|
||||
if inf != nil {
|
||||
ost, err := os.Stat(outfile)
|
||||
if err != nil {
|
||||
die("can't stat %s: %s", outfile, err)
|
||||
var err error
|
||||
var ist, ost os.FileInfo
|
||||
|
||||
if ost, err = os.Stat(outfile); 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 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!")
|
||||
Die("won't create output file: same as input file!")
|
||||
}
|
||||
mode = ist.Mode()
|
||||
}
|
||||
|
||||
outf := mustOpen(outfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC)
|
||||
defer outf.Close()
|
||||
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)
|
||||
}
|
||||
|
||||
outfd = outf
|
||||
AtExit(sf.Abort)
|
||||
defer sf.Abort()
|
||||
outfd = sf
|
||||
}
|
||||
|
||||
en, err := sign.NewEncryptor(sk, blksize)
|
||||
if err != nil {
|
||||
die("%s", err)
|
||||
Die("%s", err)
|
||||
}
|
||||
|
||||
errs := 0
|
||||
|
@ -151,14 +170,14 @@ func encrypt(args []string) {
|
|||
var ok bool
|
||||
pk, ok = keymap[fn]
|
||||
if !ok {
|
||||
warn("can't find user %s in %s", fn, authkeys)
|
||||
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)
|
||||
Warn("%s", err)
|
||||
errs += 1
|
||||
continue
|
||||
}
|
||||
|
@ -166,18 +185,19 @@ func encrypt(args []string) {
|
|||
|
||||
err = en.AddRecipient(pk)
|
||||
if err != nil {
|
||||
die("%s", err)
|
||||
Die("%s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if errs > 0 {
|
||||
die("Too many errors!")
|
||||
Die("Too many errors!")
|
||||
}
|
||||
|
||||
err = en.Encrypt(infd, outfd)
|
||||
if err != nil {
|
||||
die("%s", err)
|
||||
Die("%s", err)
|
||||
}
|
||||
outfd.Close()
|
||||
}
|
||||
|
||||
type nullWriter struct{}
|
||||
|
@ -202,26 +222,27 @@ func decrypt(args []string) {
|
|||
var envpw string
|
||||
var outfile string
|
||||
var pubkey string
|
||||
var nopw, test bool
|
||||
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", "", "", "Use passphrase from environment variable `E`")
|
||||
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)
|
||||
Die("%s", err)
|
||||
}
|
||||
|
||||
args = fs.Args()
|
||||
if len(args) < 1 {
|
||||
die("Insufficient args. Try '%s --help'", os.Args[0])
|
||||
Die("Insufficient args. Try '%s --help'", os.Args[0])
|
||||
}
|
||||
|
||||
var infd io.Reader = os.Stdin
|
||||
var outfd io.Writer = os.Stdout
|
||||
var outfd io.WriteCloser = os.Stdout
|
||||
var inf *os.File
|
||||
var infile string
|
||||
|
||||
|
@ -237,13 +258,13 @@ func decrypt(args []string) {
|
|||
} else {
|
||||
pws, err = utils.Askpass("Enter passphrase for private key", false)
|
||||
if err != nil {
|
||||
die("%s", err)
|
||||
Die("%s", err)
|
||||
}
|
||||
}
|
||||
return []byte(pws), nil
|
||||
})
|
||||
if err != nil {
|
||||
die("%s", err)
|
||||
Die("%s", err)
|
||||
}
|
||||
|
||||
var pk *sign.PublicKey
|
||||
|
@ -251,7 +272,7 @@ func decrypt(args []string) {
|
|||
if len(pubkey) > 0 {
|
||||
pk, err = sign.ReadPublicKey(pubkey)
|
||||
if err != nil {
|
||||
die("%s", err)
|
||||
Die("%s", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -264,48 +285,70 @@ func decrypt(args []string) {
|
|||
infd = inf
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if test {
|
||||
outfd = &nullWriter{}
|
||||
} else if len(outfile) > 0 && outfile != "-" {
|
||||
var mode os.FileMode = 0600 // conservative mode
|
||||
|
||||
if inf != nil {
|
||||
ost, err := os.Stat(outfile)
|
||||
if err != nil {
|
||||
die("can't stat %s: %s", outfile, err)
|
||||
var ist, ost os.FileInfo
|
||||
var err error
|
||||
|
||||
if ost, err = os.Stat(outfile); 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 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!")
|
||||
Die("won't create output file: same as input file!")
|
||||
}
|
||||
mode = ist.Mode()
|
||||
}
|
||||
|
||||
outf := mustOpen(outfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC)
|
||||
defer outf.Close()
|
||||
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)
|
||||
}
|
||||
|
||||
outfd = outf
|
||||
AtExit(sf.Abort)
|
||||
defer sf.Abort()
|
||||
outfd = sf
|
||||
}
|
||||
|
||||
d, err := sign.NewDecryptor(infd)
|
||||
if err != nil {
|
||||
die("%s", err)
|
||||
Die("%s", err)
|
||||
}
|
||||
|
||||
err = d.SetPrivateKey(sk, pk)
|
||||
if err != nil {
|
||||
die("%s", err)
|
||||
Die("%s", err)
|
||||
}
|
||||
|
||||
err = d.Decrypt(outfd)
|
||||
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")
|
||||
Warn("Enc file OK")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func encryptUsage(fs *flag.FlagSet) {
|
||||
|
@ -313,9 +356,16 @@ func encryptUsage(fs *flag.FlagSet) {
|
|||
|
||||
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.
|
||||
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)
|
||||
|
@ -343,7 +393,7 @@ Options:
|
|||
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)
|
||||
Die("can't open file %s: %s", fn, err)
|
||||
}
|
||||
return fdk
|
||||
}
|
55
src/die.go
Normal file
55
src/die.go
Normal file
|
@ -0,0 +1,55 @@
|
|||
// 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)
|
||||
}
|
100
src/gen.go
Normal file
100
src/gen.go
Normal file
|
@ -0,0 +1,100 @@
|
|||
// gen.go -- generate keys
|
||||
//
|
||||
// (c) 2016 Sudhi Herle <sudhi@herle.net>
|
||||
//
|
||||
// Licensing Terms: GPLv2
|
||||
//
|
||||
// If you need a commercial license for this work, please contact
|
||||
// the author.
|
||||
//
|
||||
// This software does not come with any express or implied
|
||||
// warranty; it is provided "as is". No claim is made to its
|
||||
// suitability for any purpose.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"git.rgst.io/homelab/sigtool/v3/sign"
|
||||
"github.com/opencoff/go-utils"
|
||||
flag "github.com/opencoff/pflag"
|
||||
)
|
||||
|
||||
// Run the generate command
|
||||
func gen(args []string) {
|
||||
var nopw, help, force bool
|
||||
var comment string
|
||||
var envpw string
|
||||
|
||||
fs := flag.NewFlagSet("generate", flag.ExitOnError)
|
||||
fs.BoolVarP(&help, "help", "h", false, "Show this help and exit")
|
||||
fs.BoolVarP(&nopw, "no-password", "", false, "Don't ask for a password for the private key")
|
||||
fs.StringVarP(&comment, "comment", "c", "", "Use `C` as the text comment for the keys")
|
||||
fs.StringVarP(&envpw, "env-password", "E", "", "Use passphrase from environment variable `E`")
|
||||
fs.BoolVarP(&force, "overwrite", "", false, "Overwrite the output file if it exists")
|
||||
|
||||
fs.Parse(args)
|
||||
|
||||
if help {
|
||||
fs.SetOutput(os.Stdout)
|
||||
fmt.Printf(`%s generate|gen|g [options] file-prefix
|
||||
|
||||
Generate a new Ed25519 public+private key pair and write public key to
|
||||
FILE-PREFIX.pub and private key to FILE-PREFIX.key.
|
||||
|
||||
Options:
|
||||
`, Z)
|
||||
fs.PrintDefaults()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
args = fs.Args()
|
||||
if len(args) < 1 {
|
||||
Die("Insufficient arguments to 'generate'. Try '%s generate -h' ..", Z)
|
||||
}
|
||||
|
||||
bn := args[0]
|
||||
|
||||
pkn := fmt.Sprintf("%s.pub", path.Clean(bn))
|
||||
skn := fmt.Sprintf("%s.key", path.Clean(bn))
|
||||
|
||||
if !force {
|
||||
if exists(pkn) || exists(skn) {
|
||||
Die("Public/Private key files (%s, %s) exist. won't overwrite!", skn, pkn)
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
var pw []byte
|
||||
|
||||
if !nopw {
|
||||
var pws string
|
||||
if len(envpw) > 0 {
|
||||
pws = os.Getenv(envpw)
|
||||
} else {
|
||||
pws, err = utils.Askpass("Enter passphrase for private key", true)
|
||||
if err != nil {
|
||||
Die("%s", err)
|
||||
}
|
||||
}
|
||||
|
||||
pw = []byte(pws)
|
||||
}
|
||||
|
||||
sk, err := sign.NewPrivateKey()
|
||||
if err != nil {
|
||||
Die("%s", err)
|
||||
}
|
||||
|
||||
if err = sk.Serialize(skn, comment, force, pw); err != nil {
|
||||
Die("%s", err)
|
||||
}
|
||||
|
||||
pk := sk.PublicKey()
|
||||
if err = pk.Serialize(pkn, comment, force); err != nil {
|
||||
Die("%s", err)
|
||||
}
|
||||
}
|
116
src/sign.go
Normal file
116
src/sign.go
Normal file
|
@ -0,0 +1,116 @@
|
|||
// sign.go -- 'sign' command implementation
|
||||
//
|
||||
// (c) 2016 Sudhi Herle <sudhi@herle.net>
|
||||
//
|
||||
// Licensing Terms: GPLv2
|
||||
//
|
||||
// If you need a commercial license for this work, please contact
|
||||
// the author.
|
||||
//
|
||||
// This software does not come with any express or implied
|
||||
// warranty; it is provided "as is". No claim is made to its
|
||||
// suitability for any purpose.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"git.rgst.io/homelab/sigtool/v3/sign"
|
||||
"github.com/opencoff/go-fio"
|
||||
"github.com/opencoff/go-utils"
|
||||
flag "github.com/opencoff/pflag"
|
||||
)
|
||||
|
||||
// Run the 'sign' command.
|
||||
func signify(args []string) {
|
||||
var nopw, help, force bool
|
||||
var output string
|
||||
var envpw string
|
||||
|
||||
fs := flag.NewFlagSet("sign", flag.ExitOnError)
|
||||
fs.BoolVarP(&help, "help", "h", false, "Show this help and exit")
|
||||
fs.BoolVarP(&nopw, "no-password", "", false, "Don't ask for a password for the private key")
|
||||
fs.StringVarP(&envpw, "env-password", "E", "", "Use passphrase from environment variable `E`")
|
||||
fs.StringVarP(&output, "output", "o", "", "Write signature to file `F`")
|
||||
fs.BoolVarP(&force, "overwrite", "", false, "Overwrite previous signature file if it exists")
|
||||
|
||||
fs.Parse(args)
|
||||
|
||||
if help {
|
||||
fs.SetOutput(os.Stdout)
|
||||
fmt.Printf(`%s sign|s [options] privkey file
|
||||
|
||||
Sign FILE with a Ed25519 private key PRIVKEY and write signature to FILE.sig
|
||||
|
||||
Options:
|
||||
`, Z)
|
||||
fs.PrintDefaults()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
args = fs.Args()
|
||||
if len(args) < 2 {
|
||||
Die("Insufficient arguments to 'sign'. Try '%s sign -h' ..", Z)
|
||||
}
|
||||
|
||||
kn := args[0]
|
||||
fn := args[1]
|
||||
outf := fmt.Sprintf("%s.sig", fn)
|
||||
|
||||
var err error
|
||||
|
||||
if len(output) > 0 {
|
||||
outf = output
|
||||
}
|
||||
|
||||
var fd io.WriteCloser = os.Stdout
|
||||
|
||||
if outf != "-" {
|
||||
var opts uint32
|
||||
if force {
|
||||
opts |= fio.OPT_OVERWRITE
|
||||
}
|
||||
sf, err := fio.NewSafeFile(outf, opts, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
Die("can't create sig file: %s", err)
|
||||
}
|
||||
|
||||
// we unlink and remove temp on any error
|
||||
AtExit(sf.Abort)
|
||||
defer sf.Abort()
|
||||
fd = sf
|
||||
}
|
||||
|
||||
sk, err := sign.ReadPrivateKey(kn, func() ([]byte, error) {
|
||||
if nopw {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var pws string
|
||||
if len(envpw) > 0 {
|
||||
pws = os.Getenv(envpw)
|
||||
} else {
|
||||
pws, err = utils.Askpass("Enter passphrase for private key", false)
|
||||
if err != nil {
|
||||
Die("%s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return []byte(pws), nil
|
||||
})
|
||||
if err != nil {
|
||||
Die("%s", err)
|
||||
}
|
||||
|
||||
sig, err := sk.SignFile(fn)
|
||||
if err != nil {
|
||||
Die("%s", err)
|
||||
}
|
||||
|
||||
sigbytes, err := sig.MarshalBinary(fmt.Sprintf("input=%s", fn))
|
||||
fd.Write(sigbytes)
|
||||
fd.Close()
|
||||
}
|
129
src/sigtool.go
Normal file
129
src/sigtool.go
Normal file
|
@ -0,0 +1,129 @@
|
|||
// 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:
|
96
src/verify.go
Normal file
96
src/verify.go
Normal file
|
@ -0,0 +1,96 @@
|
|||
// 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
Executable file
124
tests.sh
Executable file
|
@ -0,0 +1,124 @@
|
|||
#! /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
1
version
|
@ -1 +0,0 @@
|
|||
1.0.0
|
Loading…
Add table
Reference in a new issue