Compare commits
No commits in common. "master" and "v0.1.1" have entirely different histories.
34 changed files with 934 additions and 5493 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -24,11 +24,12 @@ _testmain.go
|
|||
*.prof
|
||||
vendor/*
|
||||
|
||||
# vendor management
|
||||
vendor/src/*
|
||||
vendor/pkg/*
|
||||
bin/*
|
||||
sigtool
|
||||
|
||||
.??*.sw?
|
||||
*.pub
|
||||
*.key
|
||||
*.sig
|
||||
releases/*
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
[tools]
|
||||
golang = "1.24"
|
5
Makefile
5
Makefile
|
@ -4,10 +4,11 @@ pwd = $(shell pwd)
|
|||
.PHONY: all test clean realclean
|
||||
|
||||
all:
|
||||
./build -s
|
||||
mkdir -p bin
|
||||
go build -o bin/sigtool .
|
||||
|
||||
test:
|
||||
go test ./sign
|
||||
|
||||
clean realclean:
|
||||
rm -rf bin
|
||||
rm -f bin/sigtool
|
||||
|
|
283
README.md
283
README.md
|
@ -4,49 +4,24 @@
|
|||
|
||||
|
||||
## What is this?
|
||||
`sigtool` is an opinionated tool to generate keys, sign, verify, encrypt &
|
||||
decrypt files using Ed25519 signature scheme. In many ways, it is like
|
||||
like OpenBSD's [signify][1] -- except written in Golang and definitely
|
||||
easier to use. It can use SSH ed25519 public and private keys.
|
||||
`sigtool` is an opinionated tool to generate, sign and verify Ed25519
|
||||
signatures on files. In many ways, it is like like OpenBSD's signify_
|
||||
-- except written in Golang and definitely easier to use.
|
||||
|
||||
It can sign and verify very large files - it prehashes the files
|
||||
with SHA-512 and then signs the SHA-512 checksum. The keys and signatures
|
||||
are human readable YAML files.
|
||||
with SHA-512 and then signs the SHA-512 checksum.
|
||||
|
||||
It can encrypt files for multiple recipients - each of whom is identified
|
||||
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 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 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`.
|
||||
All the artifacts produced by this tool are standard YAML files -
|
||||
thus, human readable.
|
||||
|
||||
## How do I build it?
|
||||
You need two things:
|
||||
With Go 1.5 and later:
|
||||
|
||||
1. Protobuf compiler:
|
||||
|
||||
On Debian based systems: `apt install protobuf-compiler`
|
||||
|
||||
Consult your OS's package manager to install protobuf tools;
|
||||
these are typically named 'protobuf' or 'protoc'.
|
||||
|
||||
2. go 1.13+ toolchain
|
||||
|
||||
|
||||
Next, build sigtool:
|
||||
|
||||
git clone https://git.rgst.io/homelab/sigtool/v3
|
||||
git clone https://github.com/opencoff/sigtool
|
||||
cd sigtool
|
||||
make
|
||||
|
||||
The binary will be in `./bin/$HOSTOS-$ARCH/sigtool`.
|
||||
where `$HOSTOS` is the host OS where you are building (e.g., openbsd)
|
||||
and `$ARCH` is the CPU architecture (e.g., amd64).
|
||||
The binary will be in `./sigtool`.
|
||||
|
||||
## How do I use it?
|
||||
Broadly, the tool can:
|
||||
|
@ -54,8 +29,6 @@ Broadly, the tool can:
|
|||
- generate new key pairs (public key and private key)
|
||||
- sign a file
|
||||
- verify a file against its signature
|
||||
- encrypt a file
|
||||
- decrypt a file
|
||||
|
||||
### Generate Key pair
|
||||
To start with, you generate a new key pair (a public key used for
|
||||
|
@ -76,15 +49,15 @@ e.g., to sign `archive.tar.gz` with private key `/tmp/testkey.key`:
|
|||
|
||||
sigtool sign /tmp/testkey.key archive.tar.gz
|
||||
|
||||
If *testkey.key* was encrypted without a user pass phrase:
|
||||
If *testkey.key* was encrypted with a user pass phrase:
|
||||
|
||||
sigtool sign --no-password /tmp/testkey.key archive.tar.gz
|
||||
sigtool sign -p /tmp/testkey.key archive.tar.gz
|
||||
|
||||
|
||||
The signature can also be written directly to a user supplied output
|
||||
file.
|
||||
|
||||
sigtool sign -o archive.sig /tmp/testkey.key archive.tar.gz
|
||||
sigtool sign -p -o archive.sig /tmp/testkey.key archive.tar.gz
|
||||
|
||||
|
||||
### Verify a signature against a file
|
||||
|
@ -100,190 +73,22 @@ e.g., to verify the signature of *archive.tar.gz* against
|
|||
|
||||
sigtool verify /tmp/testkey.pub archive.sig archive.tar.gz
|
||||
|
||||
## How is the private key protected?
|
||||
The Ed25519 private key is encrypted using a key derived from the
|
||||
user supplied pass phrase. This pass phrase is used to derive an
|
||||
encryption key using the Scrypt key derivation algorithm. The
|
||||
resulting derived key is XOR'd with the Ed25519 private key before
|
||||
being committed to disk. To protect the integrity of the process,
|
||||
the essential parameters used for deriving the key, and the derived
|
||||
key are hashed via SHA256 and stored along with the encrypted key.
|
||||
|
||||
You can also pass a public key as a string (instead of a file):
|
||||
|
||||
sigtool verify iF84Dymq/bAEnUMK6DRIHWAQDRD8FwDDDfsgFfzdjWM= archive.sig archive.tar.gz
|
||||
|
||||
Note that signing and verifying can also work with OpenSSH ed25519
|
||||
keys.
|
||||
|
||||
### Encrypt a file by authenticating the sender
|
||||
If the sender wishes to prove to the recipient that they encrypted
|
||||
a file:
|
||||
|
||||
sigtool encrypt -s sender.key to.pub -o archive.tar.gz.enc archive.tar.gz
|
||||
|
||||
|
||||
This will create an encrypted file *archive.tar.gz.enc* such that the
|
||||
recipient in possession of *to.key* can decrypt it. Furthermore, if
|
||||
the recipient has *sender.pub*, they can verify that the sender is indeed
|
||||
who they expect.
|
||||
|
||||
### Decrypt a file and verify the sender
|
||||
If the receiver has the public key of the sender, they can verify that
|
||||
they indeed sent the file by cryptographically checking the output:
|
||||
|
||||
sigtool decrypt -o archive.tar.gz -v sender.pub to.key archive.tar.gz.enc
|
||||
|
||||
Note that the verification is optional and if the `-v` option is not
|
||||
used, then decryption will proceed without verifying the sender.
|
||||
|
||||
### Encrypt a file *without* authenticating the sender
|
||||
`sigtool` can generate ephemeral keys for encrypting a file such that
|
||||
the receiver doesn't need to authenticate the sender:
|
||||
|
||||
sigtool encrypt to.pub -o archive.tar.gz.enc archive.tar.gz
|
||||
|
||||
This will create an encrypted file *archive.tar.gz.enc* such that the
|
||||
recipient in possession of *to.key* can decrypt it.
|
||||
|
||||
### Encrypt a file to an OpenSSH recipient *without* authenticating the sender
|
||||
Suppose you want to send an encrypted file where the recipient's
|
||||
public key is in `~/.ssh/authorized_keys`. Such a recipient is identified
|
||||
by their OpenSSH key comment (typically `name@domain`):
|
||||
|
||||
sigtool encrypt user@domain -o archive.tar.gz.enc archive.tar.gz
|
||||
|
||||
If you have their public key in file "name-domain.pub", you can do:
|
||||
|
||||
sigtool encrypt name-domain.pub -o archive.tar.gz.enc archive.tar.gz
|
||||
|
||||
This will create an encrypted file *archive.tar.gz.enc* such that the
|
||||
recipient can decrypt using their private key.
|
||||
|
||||
## Technical Details
|
||||
|
||||
### How is the file encryption done?
|
||||
The file encryption uses AES-GCM-256 in AEAD mode. The encryption uses
|
||||
a random 32-byte AES-256 key. This root key is expanded via
|
||||
HKDF-SHA256 into:
|
||||
|
||||
- AES-GCM-256 key (32 bytes)
|
||||
- AES Nonce (12 bytes)
|
||||
- HMAC-SHA-256 key (32 bytes)
|
||||
|
||||
The input to the HKDF is the root-key, header-checksum ("salt") and
|
||||
a context string.
|
||||
|
||||
The input is broken into chunks and each chunk is individually AEAD encrypted.
|
||||
The default chunk size is 4MB (4 * 1048576 bytes). Each chunk generates
|
||||
its own nonce: the top-4 bytes of the nonce is the chunk-number. The
|
||||
actual chunk-length and EOF marker is used as additional data (the
|
||||
"AD" of "AEAD").
|
||||
The last block has its most-signficant-bit set to 1 to denote EOF. Thus, the
|
||||
maximum chunk size is set to 1GB.
|
||||
|
||||
We calculate a running hmac of the plaintext blocks; when sender
|
||||
identity is present, the final HMAC is signed via the sender's
|
||||
Ed25519 key. This signature is appended as the "trailer" (last 64
|
||||
bytes of the encrypted file are the Ed25519 signature).
|
||||
|
||||
When sender identity is not present, the last bytes are random
|
||||
bytes.
|
||||
|
||||
### What is the public-key cryptography in sigtool?
|
||||
`sigtool` uses ephemeral Curve25519 keys to generate shared secrets
|
||||
between pairs of sender & one or more recipients. This pairwise shared
|
||||
secret is used as a key-encryption-key (KEK) to wrap the
|
||||
data-encryption key in AEAD mode. Thus, each recipient has their own
|
||||
individual encrypted key blob - that **only** they can decrypt.
|
||||
|
||||
If the sender authenticates the encryption by providing their secret
|
||||
key, the encryption key material is signed via Ed25519 and the signature
|
||||
is encrypted (using the data-encryption key) and stored in the
|
||||
header. If the sender opts to not authenticate, a "signature" of all
|
||||
zeroes is encrypted instead.
|
||||
|
||||
The Ed25519 keys generated by `sigtool` or OpenSSH are transformed to their
|
||||
corresponding Curve25519 points in order to generate the pair-wise shared secret.
|
||||
This elliptic co-ordinate transform follows [FiloSottile's writeup][2].
|
||||
|
||||
### Format of the Encrypted File
|
||||
Every encrypted file starts with a header and the header-checksum:
|
||||
|
||||
* Fixed-size header
|
||||
* Variable-length header
|
||||
* SHA256 sum of both of the above
|
||||
|
||||
The fixed length header is:
|
||||
|
||||
7 byte magic ("SigTool")
|
||||
1 byte version number
|
||||
4 byte header length (big endian encoding)
|
||||
|
||||
The variable length header has the per-recipient wrapped keys. This is
|
||||
described as a protobuf file (sign/hdr.proto):
|
||||
|
||||
```protobuf
|
||||
message header {
|
||||
uint32 chunk_size = 1;
|
||||
bytes salt = 2;
|
||||
bytes pk = 3; // sender's ephemeral curve PK
|
||||
bytes sender = 4; // ed25519 signature of key material
|
||||
repeated wrapped_key keys = 5;
|
||||
}
|
||||
|
||||
/*
|
||||
* A file encryption key is wrapped by a recipient specific public
|
||||
* key. WrappedKey describes such a wrapped key.
|
||||
*/
|
||||
message wrapped_key {
|
||||
bytes d_key = 1;
|
||||
bytes nonce = 2;
|
||||
}
|
||||
```
|
||||
|
||||
The SHA256 sum covers the fixed-length and variable-length headers.
|
||||
|
||||
The encrypted data immediately follows the headers above. Each encrypted
|
||||
chunk is encoded the same way:
|
||||
|
||||
```C
|
||||
4 byte chunk length (big endian encoding)
|
||||
AEAD encrypted chunk data
|
||||
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
|
||||
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
|
||||
|
||||
As an additional security measure, the user supplied pass phrase is
|
||||
hashed with SHA512.
|
||||
|
||||
## Understanding the Code
|
||||
The core logic is in `src/sign`: it is a library that exposes all the
|
||||
functionality: key generation, key parsing, signing, encryption, decryption
|
||||
etc.
|
||||
|
||||
* `src/encrypt.go` contains the core encryption, decryption code
|
||||
* `src/sign.go` contains the Ed25519 signing, verification code
|
||||
* `src/keys.go` contains key generation, serialization, de-serialization
|
||||
* `src/ssh.go` contains code to parse SSH Ed25519 key files
|
||||
* `src/stream.go` contains code that provides an `io.Reader` and `io.WriteCloser` interface
|
||||
for encryption and decryption.
|
||||
* `tests.sh` simple round trip test using the tool; this is in addition to the tests in
|
||||
`sign/`.
|
||||
|
||||
`src/sign` is a library to generate, verify and store Ed25519 keys
|
||||
and signatures. It uses the extended library (golang.org/x/crypto)
|
||||
for the underlying operations.
|
||||
|
||||
The generated keys and signatures are proper YAML files and human
|
||||
readable.
|
||||
|
@ -296,11 +101,6 @@ Signatures on large files are calculated efficiently by reading them
|
|||
in memory mapped mode (```mmap(2)```) and hashing the file contents
|
||||
using SHA-512. The Ed25519 signature is calculated on the file-hash.
|
||||
|
||||
### Tests
|
||||
The core library in `sign/` has extensive tests to verify signing and encryption.
|
||||
Additionally, a simple shell script `tests.sh` does a full roundtrip of tests
|
||||
using `sigtool`.
|
||||
|
||||
## Example of Keys, Signature
|
||||
|
||||
### Ed25519 Public Key
|
||||
|
@ -311,26 +111,44 @@ A serialized Ed25519 public key looks like so:
|
|||
### Ed25519 Private Key
|
||||
And, a serialized Ed25519 private key looks like so:
|
||||
|
||||
```yaml
|
||||
|
||||
esk: t3vfqHbgUiA733KKPymFjWT8DdnBEkiMfsDHolPUdQWpvVn/F1Z4J6KYV3M5rGO9xgKxh5RAmqt+6LKgOiJAMQ==
|
||||
salt: pPHKG55UJYtJ5wU0G9hBvNQJ0DvT0a7T4Fmj4aPB84s=
|
||||
algo: scrypt-sha256
|
||||
verify: JvjRjJMKhJhBmZngC3Pvq7x3KCLKt7gar1AAz7HB4qM=
|
||||
Z: 131072
|
||||
r: 16
|
||||
p: 1
|
||||
```
|
||||
|
||||
The Ed25519 private key is encrypted using Scrypt password hashing
|
||||
mechanism. A user supplied passphrase to protect the private key
|
||||
is first pre-hashed using SHA-512 before being used in
|
||||
```scrypt()```. In pseudo code, this operation looks like below:
|
||||
|
||||
passphrase = get_user_passphrase()
|
||||
hpass = SHA512(passphrase)
|
||||
salt = randombytes(32)
|
||||
xorkey = Scrypt(hpass, salt, N, r, p)
|
||||
verify = SHA256(salt, xorkey)
|
||||
esk = ed25519_private_key ^ xorkey
|
||||
|
||||
Where, ```N```, ```r```, ```p``` are Scrypt parameters. In our
|
||||
implementation:
|
||||
|
||||
N = 131072
|
||||
r = 16
|
||||
p = 1
|
||||
|
||||
```verify``` is used during the decryption of the Ed25519 private
|
||||
key - *before* actually doing the "xor" operation. This check
|
||||
ensures that the supplied passphrase yields the same value as
|
||||
```verify```.
|
||||
|
||||
### Ed25519 Signature
|
||||
A generated signature looks like below after serialization:
|
||||
|
||||
```yaml
|
||||
|
||||
comment: inpfile=/tmp/file.txt
|
||||
pkhash: 36z9tCwTIVNwwDlExrB0SQ==
|
||||
signature: ow2oBP+buDbEvlNakOrsxgB5Yc/7PYyPVZCkfyu7oahw8BakF4Qf32uswPaKGZ8RVz4uXboYHdZtfrEjCgP/Cg==
|
||||
```
|
||||
|
||||
Here, ```pkhash`` is a SHA256 of the public key needed to verify
|
||||
this signature.
|
||||
|
@ -345,5 +163,4 @@ See the file ``LICENSE.md`` for the full terms of the license.
|
|||
## Author
|
||||
Sudhi Herle <sw@herle.net>
|
||||
|
||||
[1]: https://www.openbsd.org/papers/bsdcan-signify.html
|
||||
[2]: https://blog.filippo.io/using-ed25519-keys-for-encryption/
|
||||
.. _signify: https://www.openbsd.org/papers/bsdcan-signify.html
|
||||
|
|
462
build
462
build
|
@ -1,462 +0,0 @@
|
|||
#! /usr/bin/env bash
|
||||
|
||||
# Tool to build go programs in this repo
|
||||
#
|
||||
# - it tacks on a version number for use by the individual tools
|
||||
# - it supports git and mercurial version#
|
||||
#
|
||||
# NB:
|
||||
# o the attempt at decoding dirty repo state for mercurial is
|
||||
# borked. It doesn't know about untracked files
|
||||
#
|
||||
# (c) 2016 Sudhi Herle
|
||||
#
|
||||
# License: GPLv2
|
||||
#
|
||||
Progs="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 --
|
||||
|
||||
Z=`basename $0`
|
||||
PWD=`pwd`
|
||||
|
||||
Static=0
|
||||
Dryrun=0
|
||||
Prodver=""
|
||||
Repover=""
|
||||
Verbose=0
|
||||
Go=`which go`
|
||||
Bindir=$PWD/bin
|
||||
|
||||
die() {
|
||||
echo "$Z: $@" 1>&2
|
||||
exit 0
|
||||
}
|
||||
|
||||
warn() {
|
||||
echo "$Z: $@" 1>&2
|
||||
}
|
||||
|
||||
case $BASH_VERSION in
|
||||
4.*|5.*) ;;
|
||||
|
||||
*) die "I need bash 4.x to run!"
|
||||
;;
|
||||
esac
|
||||
|
||||
getvcs_version() {
|
||||
local rev=
|
||||
local prodv=
|
||||
local git=`which git`
|
||||
local hg=`which hg`
|
||||
|
||||
if [ -n "$git" ]; then
|
||||
local xrev=$(git describe --always --dirty --long --abbrev=12) || exit 1
|
||||
rev="git:$xrev"
|
||||
prodv=$(git tag --list | sort -V | tail -1)
|
||||
elif [ -n "$hg" ]; then
|
||||
local xrev=$(hg id --id) || exit 1
|
||||
local brev=${xrev%+}
|
||||
if [ "$brev" != "$xrev" ]; then
|
||||
rev="hg:${brev}-dirty"
|
||||
else
|
||||
rev="hg:${brev}"
|
||||
fi
|
||||
prodv=$(hg log -r "branch(stable) and tag()" -T "{tags}\n" | sort -V | tail -1)
|
||||
else
|
||||
warn "no git or hg found; can't get VCS info"
|
||||
rev="UNKNOWN-VER"
|
||||
fi
|
||||
|
||||
[ -n "$Prodver" ] && prodv=$Prodver
|
||||
|
||||
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.
|
||||
|
||||
Build output is in bin/\$OS-\$CPU for a given OS, CPU combination.
|
||||
|
||||
Usage: $0
|
||||
$0 [options] [PROGS]
|
||||
|
||||
Where OS-ARCH denotes one of the valid OS, ARCH combinations supported by 'go'.
|
||||
And, PROGS is one or more go programs.
|
||||
|
||||
With no arguments, $0 builds: $pstr
|
||||
|
||||
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]
|
||||
-n, --dry-run Dry-run, don't actually build anything [False]
|
||||
-t, --test Run "go test" on modules named on the command line [False]
|
||||
-v, --verbose Build verbosely (adds "-v" to go tooling) [False]
|
||||
--vet Run "go vet" on modules named on the command line [False]
|
||||
--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]'`
|
||||
|
||||
declare -A oses
|
||||
declare -A cpus
|
||||
declare -A cgo
|
||||
|
||||
# Supported & Verified OS/CPU combos for this script
|
||||
oslist="linux android openbsd freebsd darwin dragonfly netbsd windows"
|
||||
needcgo="android"
|
||||
cpulist="i386 amd64 arm arm64"
|
||||
cpualias_i386="i486 i586 i686"
|
||||
cpualias_amd64="x86_64"
|
||||
cpualias_arm64="aarch64"
|
||||
|
||||
# CGO Cross-Compilers for various CPU+OS combinations of Android
|
||||
android_i386=i686-linux-android-gcc
|
||||
android_arm64=aarch64-linux-android-gcc
|
||||
android_arm=arm-linux-androideabi-gcc
|
||||
|
||||
# initialize the various hash tables
|
||||
for o in $oslist; do oses[$o]=$o; done
|
||||
for o in $needcgo; do cgo[$o]=$o; done
|
||||
for c in $cpulist; do
|
||||
cpus[$c]=$c
|
||||
a="cpualias_$c"
|
||||
a=${!a}
|
||||
for x in $a; do cpus[$x]=$c; done
|
||||
done
|
||||
|
||||
|
||||
Tool=
|
||||
doinit=0
|
||||
args=
|
||||
Printarch=0
|
||||
|
||||
#set -x
|
||||
ac_prev=
|
||||
for ac_option
|
||||
do
|
||||
shift
|
||||
|
||||
if [ -n "$ac_prev" ]; then
|
||||
eval "$ac_prev=\$ac_option"
|
||||
ac_prev=
|
||||
continue
|
||||
fi
|
||||
|
||||
case "$ac_option" in
|
||||
-*=*) ac_optarg=`echo "$ac_option" | sed 's/[-_a-zA-Z0-9]*=//'` ;;
|
||||
*) ac_optarg= ;;
|
||||
esac
|
||||
|
||||
|
||||
case "$ac_option" in
|
||||
--help|-h|--hel|--he|--h)
|
||||
usage;
|
||||
;;
|
||||
|
||||
--arch=*)
|
||||
Arch=$ac_optarg
|
||||
;;
|
||||
|
||||
-a|--arch)
|
||||
ac_prev=Arch
|
||||
;;
|
||||
|
||||
-b|--bindir)
|
||||
ac_prev=Bindir
|
||||
;;
|
||||
|
||||
--bindir=*)
|
||||
Bindir=$ac_optarg
|
||||
;;
|
||||
|
||||
--version=*)
|
||||
Prodver=$ac_optarg
|
||||
;;
|
||||
|
||||
--test|-t)
|
||||
Tool=test
|
||||
;;
|
||||
|
||||
--vet)
|
||||
Tool=vet
|
||||
;;
|
||||
|
||||
--mod)
|
||||
Tool=mod
|
||||
;;
|
||||
|
||||
-V|--version)
|
||||
ac_prev=Prodver
|
||||
;;
|
||||
|
||||
-v|--verbose)
|
||||
Verbose=1
|
||||
;;
|
||||
|
||||
-s|--static)
|
||||
Static=1
|
||||
;;
|
||||
|
||||
--dry-run|-n)
|
||||
Dryrun=1
|
||||
;;
|
||||
|
||||
--debug|-x)
|
||||
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"
|
||||
for xx
|
||||
do
|
||||
args="$args $xx"
|
||||
done
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
[ $Dryrun -gt 0 ] && e=echo
|
||||
|
||||
# let every error abort
|
||||
set -e
|
||||
|
||||
# build a tool that runs on the host - if needed.
|
||||
hosttool() {
|
||||
local tool=$1
|
||||
local bindir=$2
|
||||
local src=$3
|
||||
|
||||
p=$bindir/$tool
|
||||
if [ -x $p ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local tmpdir=/tmp/$tool.$$
|
||||
mkdir $tmpdir || die "can't make $tmpdir"
|
||||
|
||||
# since go1.20 - install uses env vars to decide where to put
|
||||
# build artifacts. Why are all the google tooling so bloody dev
|
||||
# hostile! WTF is wrong with command line args?!
|
||||
export GOBIN=$bindir
|
||||
|
||||
# build it and stash it in the hostdir
|
||||
echo "Building tool $tool from $src .."
|
||||
(
|
||||
cd $tmpdir
|
||||
$e $Go install $src@latest || die "can't install $tool"
|
||||
)
|
||||
$e rm -rf $tmpdir
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# protobuf gen
|
||||
buildproto() {
|
||||
local pbgo=protoc-gen-go
|
||||
local vtgo=protoc-gen-go-vtproto
|
||||
local vtgo_src=github.com/planetscale/vtprotobuf/cmd/protoc-gen-go-vtproto
|
||||
local pc
|
||||
local args="$*"
|
||||
|
||||
local pgen=$(type -p protoc)
|
||||
local gogen=$(type -p $pbgo)
|
||||
local vt=$Hostbindir/$vtgo
|
||||
|
||||
[ -z $pgen ] && die "install protoc tools"
|
||||
[ -z $gogen ] && die "install protoc-gen-go"
|
||||
|
||||
# now install the vtproto generator
|
||||
hosttool $vtgo $Hostbindir $vtgo_src
|
||||
|
||||
for f in $args; do
|
||||
local dn=$(dirname $f)
|
||||
local bn=$(basename $f .proto)
|
||||
|
||||
|
||||
$e $pgen \
|
||||
--go_out=. --plugin protoc-gen-go=$gogen \
|
||||
--go-vtproto_out=. --plugin protoc-gen-go-vtproto="$vt" \
|
||||
--go-vtproto_opt=features=marshal+unmarshal+size \
|
||||
$f || die "can't generate protobuf output for $f .."
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
# the rest has to execute in the context of main shell (not funcs)
|
||||
|
||||
hostos=$($Go env GOHOSTOS) || exit 1
|
||||
hostcpu=$($Go env GOHOSTARCH) || exit 1
|
||||
|
||||
# This fragment can't be in a function - since it exports several vars
|
||||
if [ -n "$Arch" ]; then
|
||||
ox=${Arch%%-*}
|
||||
cx=${Arch##*-}
|
||||
[ "$ox" = "$cx" ] && cx=$hostcpu
|
||||
|
||||
os=${oses[$ox]}
|
||||
cpu=${cpus[$cx]}
|
||||
[ -z "$os" ] && die "Don't know anything about OS $ox"
|
||||
[ -z "$cpu" ] && die "Don't know anything about CPU $cx"
|
||||
|
||||
export GOOS=$os GOARCH=$cpu
|
||||
cross=$os-$cpu
|
||||
|
||||
else
|
||||
os=$hostos
|
||||
cpu=$hostcpu
|
||||
cross=$os-$cpu
|
||||
fi
|
||||
|
||||
# If we don't need CGO, then we can attempt a static link
|
||||
if [ -n "${cgo[$os]}" ]; then
|
||||
export CGO_ENABLED=1
|
||||
|
||||
# See if we have a specific cross-compiler for this CPU+OS combo
|
||||
xcc="${GOOS}_${GOARCH}"
|
||||
xcc=${!xcc}
|
||||
if [ -n "$xcc" ]; then
|
||||
p=`type -p $xcc`
|
||||
[ -n "$p" ] || die "Can't find $xcc! Do you have compilers for $GOARCH available in PATH?"
|
||||
export CC=$xcc
|
||||
else
|
||||
echo "$Z: No Cross compiler defined for $GOOS-$GOARCH. Build may fail.." 1>&2
|
||||
fi
|
||||
else
|
||||
if [ $Static -gt 0 ]; then
|
||||
export CGO_ENABLED=0
|
||||
|
||||
isuffix="--installsuffix cgo"
|
||||
ldflags="-s"
|
||||
msg="statically linked"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ $Printarch -gt 0 ]; then
|
||||
echo "$hostos-$hostcpu"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
# This is where build outputs go
|
||||
Outdir=$Bindir/$cross
|
||||
Hostbindir=$Bindir/$hostos-$hostcpu
|
||||
export PATH=$Hostbindir:$PATH
|
||||
|
||||
[ -d $Outdir ] || mkdir -p $Outdir
|
||||
[ -d $Hostbindir ] || mkdir -p $Hostbindir
|
||||
|
||||
|
||||
# Do Protobufs if needed
|
||||
if [ -n "$Protobufs" ]; then
|
||||
set +e
|
||||
buildproto $Protobufs
|
||||
set -e
|
||||
fi
|
||||
|
||||
# Get git/hg version info for the build
|
||||
repover="main.RepoVersion=$Repover"
|
||||
prodver="main.ProductVersion=$Prodver"
|
||||
ldflags="-ldflags \"-X $repover -X $prodver $ldflags -buildid=\""
|
||||
vflag=""
|
||||
|
||||
[ $Verbose -gt 0 ] && vflag="-v"
|
||||
|
||||
case $Tool in
|
||||
test)
|
||||
set -- $args
|
||||
$e $Go test $vflag "$@"
|
||||
;;
|
||||
|
||||
vet)
|
||||
set -- $args
|
||||
$e $Go vet $vflag "$@"
|
||||
;;
|
||||
|
||||
mod)
|
||||
set -- $args
|
||||
$e $Go mod $vflag "$@"
|
||||
;;
|
||||
|
||||
*) # Default is to build programs
|
||||
set -- $args
|
||||
if [ -z "$1" ]; then
|
||||
all=$Progs
|
||||
else
|
||||
all="$@"
|
||||
fi
|
||||
|
||||
[ -z "$all" ] && die "No programs specified. Try '$Z --help'"
|
||||
|
||||
echo "Building $Prodver ($Repover), $cross $msg .."
|
||||
|
||||
for p in $all; do
|
||||
if echo $p | grep -q ':' ; then
|
||||
out=${p##*:}
|
||||
dir=${p%%:*}
|
||||
else
|
||||
out=$p
|
||||
dir=$p
|
||||
fi
|
||||
|
||||
# Add .exe suffix to out if needed
|
||||
if [ "$GOOS" = "windows" ]; then
|
||||
base=${out%%.exe}
|
||||
out="${base}.exe"
|
||||
fi
|
||||
|
||||
echo " $dir: $out .. "
|
||||
$e eval $Go build $vflag -trimpath -o $Outdir/$out $isuffix "$ldflags" ./$dir || exit 1
|
||||
done
|
||||
;;
|
||||
esac
|
||||
|
||||
# vim: ft=sh:expandtab:ts=4:sw=4:tw=84:
|
27
go.mod
27
go.mod
|
@ -1,25 +1,10 @@
|
|||
module git.rgst.io/homelab/sigtool/v3
|
||||
module github.com/opencoff/sigtool
|
||||
|
||||
go 1.24.0
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a
|
||||
github.com/opencoff/go-fio v0.5.14
|
||||
github.com/opencoff/go-mmap v0.1.5
|
||||
github.com/opencoff/go-utils v1.0.2
|
||||
github.com/opencoff/pflag v1.0.7
|
||||
github.com/planetscale/vtprotobuf v0.6.0
|
||||
golang.org/x/crypto v0.36.0
|
||||
google.golang.org/protobuf v1.36.5
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
github.com/opencoff/go-utils v0.1.0
|
||||
github.com/opencoff/pflag v0.3.3
|
||||
golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443
|
||||
gopkg.in/yaml.v2 v2.2.2
|
||||
)
|
||||
|
||||
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,39 +1,19 @@
|
|||
github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a h1:saTgr5tMLFnmy/yg3qDTft4rE5DY2uJ/cCxCe3q0XTU=
|
||||
github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a/go.mod h1:Bw9BbhOJVNR+t0jCqx2GC6zv0TGBsShs56Y3gfSCvl0=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/opencoff/go-fio v0.5.14 h1:PGi4XLLO4RSuc3m5exY0G2vweov6w3UThhScehBfM8c=
|
||||
github.com/opencoff/go-fio v0.5.14/go.mod h1:hoSySYpavRnfQUsxzUgadk31kYiNQhMDvA2MObsXKf8=
|
||||
github.com/opencoff/go-mmap v0.1.5 h1:RKPtevC4mOW5bi9skBPPo4nFTIH4lVWAL20Tff+FjLg=
|
||||
github.com/opencoff/go-mmap v0.1.5/go.mod h1:y/6Jk/tDUc00k3oSQpiJX++20Nw7xFSlc5kLkhGnRXw=
|
||||
github.com/opencoff/go-utils v1.0.2 h1:BANRL8ZxgHpuo8gQBAzT3M9Im3aNFhaWW28jhc86LNs=
|
||||
github.com/opencoff/go-utils v1.0.2/go.mod h1:eZkEVQVzNfuE8uGepyhscMsqcXq7liGbBHYYwgYaoy8=
|
||||
github.com/opencoff/pflag v1.0.7 h1:o5cQIuX75bDcdJ6AXl68gzpA72a3CJ2MPStaMnEuwi4=
|
||||
github.com/opencoff/pflag v1.0.7/go.mod h1:2bXtpAD/5h/2LarkbsRwiUxqnvB1nZBzn9Xjad1P41A=
|
||||
github.com/pkg/xattr v0.4.10 h1:Qe0mtiNFHQZ296vRgUjRCoPHPqH7VdTOrZx3g0T+pGA=
|
||||
github.com/pkg/xattr v0.4.10/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU=
|
||||
github.com/planetscale/vtprotobuf v0.6.0 h1:nBeETjudeJ5ZgBHUz1fVHvbqUKnYOXNhsIEabROxmNA=
|
||||
github.com/planetscale/vtprotobuf v0.6.0/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
|
||||
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
||||
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
||||
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
|
||||
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
github.com/opencoff/go-utils v0.0.0-20180228221344-c48e36f0cfc3 h1:IFg/fPDg7NFEGTxTcFcxkdvPd2RQG0u3K8MfWMEamM8=
|
||||
github.com/opencoff/go-utils v0.0.0-20180228221344-c48e36f0cfc3/go.mod h1:W/XsuvsMfXD7lUVR2TC408iKFfydI0RkNgkQ33s48gs=
|
||||
github.com/opencoff/go-utils v0.1.0 h1:CAEMUIPdlY6Q+8aVKx2cE2btRSOqLzUytOspHin9yS8=
|
||||
github.com/opencoff/go-utils v0.1.0/go.mod h1:c+7QUAiCCHcNH6OGvsZ0fviG7cgse8Y3ucg+xy7sGXM=
|
||||
github.com/opencoff/pflag v0.2.0 h1:bSI5Qz5W15aCs+4YRDYX6LK5qaUD9GpjZKYP4fli/EY=
|
||||
github.com/opencoff/pflag v0.2.0/go.mod h1:mTLzGGUGda1Av3d34iAJlh0JIlRxmFZtmc6qoWPspK0=
|
||||
github.com/opencoff/pflag v0.3.3 h1:yohZkwYGPkB34WXvUQzU5GyLhImnjfePDARUaE8me3U=
|
||||
github.com/opencoff/pflag v0.3.3/go.mod h1:mTLzGGUGda1Av3d34iAJlh0JIlRxmFZtmc6qoWPspK0=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443 h1:IcSOAf4PyMp3U3XbIEj1/xJ2BjNN2jWv7JoyOsMxXUU=
|
||||
golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
|
|
@ -1,258 +0,0 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.32.0
|
||||
// protoc v3.21.12
|
||||
// source: internal/pb/hdr.proto
|
||||
|
||||
package pb
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
// Every encrypted file starts with a header describing the
|
||||
// Block Size, Salt, Recipient keys etc. Header represents a
|
||||
// decoded version of this information. It is encoded in
|
||||
// protobuf format before writing to disk.
|
||||
type Header struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
ChunkSize uint32 `protobuf:"varint,1,opt,name=chunk_size,json=chunkSize,proto3" json:"chunk_size,omitempty"` // encryption block size
|
||||
Salt []byte `protobuf:"bytes,2,opt,name=salt,proto3" json:"salt,omitempty"` // master salt (nonces are derived from this)
|
||||
Pk []byte `protobuf:"bytes,3,opt,name=pk,proto3" json:"pk,omitempty"` // ephemeral curve PK
|
||||
Sender []byte `protobuf:"bytes,4,opt,name=sender,proto3" json:"sender,omitempty"` // sender signed artifacts
|
||||
Keys []*WrappedKey `protobuf:"bytes,5,rep,name=keys,proto3" json:"keys,omitempty"` // list of wrapped receiver blocks
|
||||
}
|
||||
|
||||
func (x *Header) Reset() {
|
||||
*x = Header{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_internal_pb_hdr_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Header) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Header) ProtoMessage() {}
|
||||
|
||||
func (x *Header) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_internal_pb_hdr_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Header.ProtoReflect.Descriptor instead.
|
||||
func (*Header) Descriptor() ([]byte, []int) {
|
||||
return file_internal_pb_hdr_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *Header) GetChunkSize() uint32 {
|
||||
if x != nil {
|
||||
return x.ChunkSize
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Header) GetSalt() []byte {
|
||||
if x != nil {
|
||||
return x.Salt
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Header) GetPk() []byte {
|
||||
if x != nil {
|
||||
return x.Pk
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Header) GetSender() []byte {
|
||||
if x != nil {
|
||||
return x.Sender
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Header) GetKeys() []*WrappedKey {
|
||||
if x != nil {
|
||||
return x.Keys
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// A file encryption key is wrapped by a recipient specific public
|
||||
// key. WrappedKey describes such a wrapped key.
|
||||
type WrappedKey struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
DKey []byte `protobuf:"bytes,1,opt,name=d_key,json=dKey,proto3" json:"d_key,omitempty"` // encrypted data key
|
||||
Nonce []byte `protobuf:"bytes,2,opt,name=nonce,proto3" json:"nonce,omitempty"` // nonce used for encryption
|
||||
}
|
||||
|
||||
func (x *WrappedKey) Reset() {
|
||||
*x = WrappedKey{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_internal_pb_hdr_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *WrappedKey) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*WrappedKey) ProtoMessage() {}
|
||||
|
||||
func (x *WrappedKey) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_internal_pb_hdr_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use WrappedKey.ProtoReflect.Descriptor instead.
|
||||
func (*WrappedKey) Descriptor() ([]byte, []int) {
|
||||
return file_internal_pb_hdr_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *WrappedKey) GetDKey() []byte {
|
||||
if x != nil {
|
||||
return x.DKey
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *WrappedKey) GetNonce() []byte {
|
||||
if x != nil {
|
||||
return x.Nonce
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_internal_pb_hdr_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_internal_pb_hdr_proto_rawDesc = []byte{
|
||||
0x0a, 0x15, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x62, 0x2f, 0x68, 0x64,
|
||||
0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x85, 0x01, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64,
|
||||
0x65, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x5f, 0x73, 0x69, 0x7a, 0x65,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x53, 0x69, 0x7a,
|
||||
0x65, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x61, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52,
|
||||
0x04, 0x73, 0x61, 0x6c, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x70, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||
0x0c, 0x52, 0x02, 0x70, 0x6b, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18,
|
||||
0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x20, 0x0a,
|
||||
0x04, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x77, 0x72,
|
||||
0x61, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x52, 0x04, 0x6b, 0x65, 0x79, 0x73, 0x22,
|
||||
0x38, 0x0a, 0x0b, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x12, 0x13,
|
||||
0x0a, 0x05, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64,
|
||||
0x4b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01,
|
||||
0x28, 0x0c, 0x52, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x42, 0x0d, 0x5a, 0x0b, 0x69, 0x6e, 0x74,
|
||||
0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_internal_pb_hdr_proto_rawDescOnce sync.Once
|
||||
file_internal_pb_hdr_proto_rawDescData = file_internal_pb_hdr_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_internal_pb_hdr_proto_rawDescGZIP() []byte {
|
||||
file_internal_pb_hdr_proto_rawDescOnce.Do(func() {
|
||||
file_internal_pb_hdr_proto_rawDescData = protoimpl.X.CompressGZIP(file_internal_pb_hdr_proto_rawDescData)
|
||||
})
|
||||
return file_internal_pb_hdr_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_internal_pb_hdr_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_internal_pb_hdr_proto_goTypes = []interface{}{
|
||||
(*Header)(nil), // 0: header
|
||||
(*WrappedKey)(nil), // 1: wrapped_key
|
||||
}
|
||||
var file_internal_pb_hdr_proto_depIdxs = []int32{
|
||||
1, // 0: header.keys:type_name -> wrapped_key
|
||||
1, // [1:1] is the sub-list for method output_type
|
||||
1, // [1:1] is the sub-list for method input_type
|
||||
1, // [1:1] is the sub-list for extension type_name
|
||||
1, // [1:1] is the sub-list for extension extendee
|
||||
0, // [0:1] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_internal_pb_hdr_proto_init() }
|
||||
func file_internal_pb_hdr_proto_init() {
|
||||
if File_internal_pb_hdr_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_internal_pb_hdr_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Header); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_internal_pb_hdr_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*WrappedKey); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_internal_pb_hdr_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 2,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_internal_pb_hdr_proto_goTypes,
|
||||
DependencyIndexes: file_internal_pb_hdr_proto_depIdxs,
|
||||
MessageInfos: file_internal_pb_hdr_proto_msgTypes,
|
||||
}.Build()
|
||||
File_internal_pb_hdr_proto = out.File
|
||||
file_internal_pb_hdr_proto_rawDesc = nil
|
||||
file_internal_pb_hdr_proto_goTypes = nil
|
||||
file_internal_pb_hdr_proto_depIdxs = nil
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
syntax="proto3";
|
||||
|
||||
|
||||
option go_package = "internal/pb";
|
||||
|
||||
|
||||
/*
|
||||
* Every encrypted file starts with a header describing the
|
||||
* Block Size, Salt, Recipient keys etc. Header represents a
|
||||
* decoded version of this information. It is encoded in
|
||||
* protobuf format before writing to disk.
|
||||
*/
|
||||
message header {
|
||||
uint32 chunk_size = 1; // encryption block size
|
||||
bytes salt = 2; // master salt (nonces are derived from this)
|
||||
bytes pk = 3; // ephemeral curve PK
|
||||
bytes sender = 4; // sender signed artifacts
|
||||
repeated wrapped_key keys = 5; // list of wrapped receiver blocks
|
||||
}
|
||||
|
||||
/*
|
||||
* A file encryption key is wrapped by a recipient specific public
|
||||
* key. WrappedKey describes such a wrapped key.
|
||||
*/
|
||||
message wrapped_key {
|
||||
bytes d_key = 1; // encrypted data key
|
||||
bytes nonce = 2; // nonce used for encryption
|
||||
}
|
||||
|
|
@ -1,512 +0,0 @@
|
|||
// Code generated by protoc-gen-go-vtproto. DO NOT EDIT.
|
||||
// protoc-gen-go-vtproto version: v0.6.0
|
||||
// source: internal/pb/hdr.proto
|
||||
|
||||
package pb
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
protohelpers "github.com/planetscale/vtprotobuf/protohelpers"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
io "io"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
func (m *Header) MarshalVT() (dAtA []byte, err error) {
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
}
|
||||
size := m.SizeVT()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalToSizedBufferVT(dAtA[:size])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dAtA[:n], nil
|
||||
}
|
||||
|
||||
func (m *Header) MarshalToVT(dAtA []byte) (int, error) {
|
||||
size := m.SizeVT()
|
||||
return m.MarshalToSizedBufferVT(dAtA[:size])
|
||||
}
|
||||
|
||||
func (m *Header) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
|
||||
if m == nil {
|
||||
return 0, nil
|
||||
}
|
||||
i := len(dAtA)
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if m.unknownFields != nil {
|
||||
i -= len(m.unknownFields)
|
||||
copy(dAtA[i:], m.unknownFields)
|
||||
}
|
||||
if len(m.Keys) > 0 {
|
||||
for iNdEx := len(m.Keys) - 1; iNdEx >= 0; iNdEx-- {
|
||||
size, err := m.Keys[iNdEx].MarshalToSizedBufferVT(dAtA[:i])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i -= size
|
||||
i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
|
||||
i--
|
||||
dAtA[i] = 0x2a
|
||||
}
|
||||
}
|
||||
if len(m.Sender) > 0 {
|
||||
i -= len(m.Sender)
|
||||
copy(dAtA[i:], m.Sender)
|
||||
i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Sender)))
|
||||
i--
|
||||
dAtA[i] = 0x22
|
||||
}
|
||||
if len(m.Pk) > 0 {
|
||||
i -= len(m.Pk)
|
||||
copy(dAtA[i:], m.Pk)
|
||||
i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Pk)))
|
||||
i--
|
||||
dAtA[i] = 0x1a
|
||||
}
|
||||
if len(m.Salt) > 0 {
|
||||
i -= len(m.Salt)
|
||||
copy(dAtA[i:], m.Salt)
|
||||
i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Salt)))
|
||||
i--
|
||||
dAtA[i] = 0x12
|
||||
}
|
||||
if m.ChunkSize != 0 {
|
||||
i = protohelpers.EncodeVarint(dAtA, i, uint64(m.ChunkSize))
|
||||
i--
|
||||
dAtA[i] = 0x8
|
||||
}
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func (m *WrappedKey) MarshalVT() (dAtA []byte, err error) {
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
}
|
||||
size := m.SizeVT()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalToSizedBufferVT(dAtA[:size])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dAtA[:n], nil
|
||||
}
|
||||
|
||||
func (m *WrappedKey) MarshalToVT(dAtA []byte) (int, error) {
|
||||
size := m.SizeVT()
|
||||
return m.MarshalToSizedBufferVT(dAtA[:size])
|
||||
}
|
||||
|
||||
func (m *WrappedKey) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
|
||||
if m == nil {
|
||||
return 0, nil
|
||||
}
|
||||
i := len(dAtA)
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if m.unknownFields != nil {
|
||||
i -= len(m.unknownFields)
|
||||
copy(dAtA[i:], m.unknownFields)
|
||||
}
|
||||
if len(m.Nonce) > 0 {
|
||||
i -= len(m.Nonce)
|
||||
copy(dAtA[i:], m.Nonce)
|
||||
i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Nonce)))
|
||||
i--
|
||||
dAtA[i] = 0x12
|
||||
}
|
||||
if len(m.DKey) > 0 {
|
||||
i -= len(m.DKey)
|
||||
copy(dAtA[i:], m.DKey)
|
||||
i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.DKey)))
|
||||
i--
|
||||
dAtA[i] = 0xa
|
||||
}
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func (m *Header) SizeVT() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
var l int
|
||||
_ = l
|
||||
if m.ChunkSize != 0 {
|
||||
n += 1 + protohelpers.SizeOfVarint(uint64(m.ChunkSize))
|
||||
}
|
||||
l = len(m.Salt)
|
||||
if l > 0 {
|
||||
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
|
||||
}
|
||||
l = len(m.Pk)
|
||||
if l > 0 {
|
||||
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
|
||||
}
|
||||
l = len(m.Sender)
|
||||
if l > 0 {
|
||||
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
|
||||
}
|
||||
if len(m.Keys) > 0 {
|
||||
for _, e := range m.Keys {
|
||||
l = e.SizeVT()
|
||||
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
|
||||
}
|
||||
}
|
||||
n += len(m.unknownFields)
|
||||
return n
|
||||
}
|
||||
|
||||
func (m *WrappedKey) SizeVT() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
var l int
|
||||
_ = l
|
||||
l = len(m.DKey)
|
||||
if l > 0 {
|
||||
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
|
||||
}
|
||||
l = len(m.Nonce)
|
||||
if l > 0 {
|
||||
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
|
||||
}
|
||||
n += len(m.unknownFields)
|
||||
return n
|
||||
}
|
||||
|
||||
func (m *Header) UnmarshalVT(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
preIndex := iNdEx
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return protohelpers.ErrIntOverflow
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
fieldNum := int32(wire >> 3)
|
||||
wireType := int(wire & 0x7)
|
||||
if wireType == 4 {
|
||||
return fmt.Errorf("proto: Header: wiretype end group for non-group")
|
||||
}
|
||||
if fieldNum <= 0 {
|
||||
return fmt.Errorf("proto: Header: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||
}
|
||||
switch fieldNum {
|
||||
case 1:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field ChunkSize", wireType)
|
||||
}
|
||||
m.ChunkSize = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return protohelpers.ErrIntOverflow
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.ChunkSize |= uint32(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
case 2:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Salt", wireType)
|
||||
}
|
||||
var byteLen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return protohelpers.ErrIntOverflow
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
byteLen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if byteLen < 0 {
|
||||
return protohelpers.ErrInvalidLength
|
||||
}
|
||||
postIndex := iNdEx + byteLen
|
||||
if postIndex < 0 {
|
||||
return protohelpers.ErrInvalidLength
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Salt = append(m.Salt[:0], dAtA[iNdEx:postIndex]...)
|
||||
if m.Salt == nil {
|
||||
m.Salt = []byte{}
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 3:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Pk", wireType)
|
||||
}
|
||||
var byteLen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return protohelpers.ErrIntOverflow
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
byteLen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if byteLen < 0 {
|
||||
return protohelpers.ErrInvalidLength
|
||||
}
|
||||
postIndex := iNdEx + byteLen
|
||||
if postIndex < 0 {
|
||||
return protohelpers.ErrInvalidLength
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Pk = append(m.Pk[:0], dAtA[iNdEx:postIndex]...)
|
||||
if m.Pk == nil {
|
||||
m.Pk = []byte{}
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 4:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Sender", wireType)
|
||||
}
|
||||
var byteLen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return protohelpers.ErrIntOverflow
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
byteLen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if byteLen < 0 {
|
||||
return protohelpers.ErrInvalidLength
|
||||
}
|
||||
postIndex := iNdEx + byteLen
|
||||
if postIndex < 0 {
|
||||
return protohelpers.ErrInvalidLength
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Sender = append(m.Sender[:0], dAtA[iNdEx:postIndex]...)
|
||||
if m.Sender == nil {
|
||||
m.Sender = []byte{}
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 5:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Keys", wireType)
|
||||
}
|
||||
var msglen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return protohelpers.ErrIntOverflow
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
msglen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if msglen < 0 {
|
||||
return protohelpers.ErrInvalidLength
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
if postIndex < 0 {
|
||||
return protohelpers.ErrInvalidLength
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Keys = append(m.Keys, &WrappedKey{})
|
||||
if err := m.Keys[len(m.Keys)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := protohelpers.Skip(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
return protohelpers.ErrInvalidLength
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
|
||||
iNdEx += skippy
|
||||
}
|
||||
}
|
||||
|
||||
if iNdEx > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (m *WrappedKey) UnmarshalVT(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
preIndex := iNdEx
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return protohelpers.ErrIntOverflow
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
fieldNum := int32(wire >> 3)
|
||||
wireType := int(wire & 0x7)
|
||||
if wireType == 4 {
|
||||
return fmt.Errorf("proto: WrappedKey: wiretype end group for non-group")
|
||||
}
|
||||
if fieldNum <= 0 {
|
||||
return fmt.Errorf("proto: WrappedKey: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||
}
|
||||
switch fieldNum {
|
||||
case 1:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field DKey", wireType)
|
||||
}
|
||||
var byteLen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return protohelpers.ErrIntOverflow
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
byteLen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if byteLen < 0 {
|
||||
return protohelpers.ErrInvalidLength
|
||||
}
|
||||
postIndex := iNdEx + byteLen
|
||||
if postIndex < 0 {
|
||||
return protohelpers.ErrInvalidLength
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.DKey = append(m.DKey[:0], dAtA[iNdEx:postIndex]...)
|
||||
if m.DKey == nil {
|
||||
m.DKey = []byte{}
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 2:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Nonce", wireType)
|
||||
}
|
||||
var byteLen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return protohelpers.ErrIntOverflow
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
byteLen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if byteLen < 0 {
|
||||
return protohelpers.ErrInvalidLength
|
||||
}
|
||||
postIndex := iNdEx + byteLen
|
||||
if postIndex < 0 {
|
||||
return protohelpers.ErrInvalidLength
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Nonce = append(m.Nonce[:0], dAtA[iNdEx:postIndex]...)
|
||||
if m.Nonce == nil {
|
||||
m.Nonce = []byte{}
|
||||
}
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := protohelpers.Skip(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
return protohelpers.ErrInvalidLength
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
|
||||
iNdEx += skippy
|
||||
}
|
||||
}
|
||||
|
||||
if iNdEx > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
// helper routines to maintain backwards compat with gogo
|
||||
|
||||
package pb
|
||||
|
||||
|
||||
func (m *Header) Size() int {
|
||||
return m.SizeVT()
|
||||
}
|
||||
|
||||
func (m *Header) MarshalTo(buf []byte) (int, error) {
|
||||
return m.MarshalToVT(buf)
|
||||
}
|
||||
|
||||
func (m *Header) Unmarshal(buf []byte) error {
|
||||
return m.UnmarshalVT(buf)
|
||||
}
|
54
mk-rel.sh
54
mk-rel.sh
|
@ -1,54 +0,0 @@
|
|||
#! /usr/bin/env bash
|
||||
|
||||
Z=`basename $0`
|
||||
die() {
|
||||
echo "$Z: $@" 1>&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
warn() {
|
||||
echo "$Z: $@" 1>&2
|
||||
}
|
||||
|
||||
case $BASH_VERSION in
|
||||
4.*|5.*) ;;
|
||||
|
||||
*) die "I need bash 4.x to run!"
|
||||
;;
|
||||
esac
|
||||
|
||||
Rel=$PWD/releases
|
||||
Bindir=$Rel/bin
|
||||
mkdir -p $Bindir || die "can't make $Bindir"
|
||||
|
||||
pkgit() {
|
||||
local os=$1
|
||||
local cpu=$2
|
||||
local rev=$3
|
||||
local arch="$os-$cpu"
|
||||
local tgz="$Rel/sigtool-${rev}_${arch}.tar.gz"
|
||||
local bindir=$Bindir/$arch
|
||||
local bin=sigtool
|
||||
|
||||
if [ "$os" = "windows" ]; then
|
||||
bin=${bin}.exe
|
||||
fi
|
||||
|
||||
./build -V $rev -b $Bindir -s -a $arch || die "can't build $arch"
|
||||
(cd $bindir && tar cf - $bin) | gzip -9 > $tgz || die "can't tar $tgz"
|
||||
}
|
||||
|
||||
xrev=$(git describe --always --dirty --abbrev=12) || exit 1
|
||||
if echo $xrev | grep -q dirty; then
|
||||
die "won't build releases; repo dirty!"
|
||||
true
|
||||
fi
|
||||
|
||||
os="linux windows openbsd darwin"
|
||||
arch="amd64 arm64"
|
||||
|
||||
for xx in $os; do
|
||||
for yy in $arch; do
|
||||
pkgit $xx $yy $xrev
|
||||
done
|
||||
done
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
[](https://godoc.org/git.rgst.io/homelab/sigtool/v3/sign)
|
||||
[](https://godoc.org/github.com/opencoff/sigtool/sign)
|
||||
|
||||
# sigtool/sign - Ed25519 signature calculation and verification
|
||||
|
||||
This is a small library that makes it easier to create and serialize Ed25519 keys, and sign,
|
||||
verify files using those keys. The library uses mmap(2) to read and process very large files.
|
||||
|
||||
The companion program [sigtool](https://git.rgst.io/homelab/sigtool/v3) uses this library.
|
||||
The companion program [sigtool](https://github.com/opencoff/sigtool) uses this library.
|
||||
## License
|
||||
GPL v2.0
|
||||
|
|
33
sign/doc.go
33
sign/doc.go
|
@ -1,33 +0,0 @@
|
|||
// doc.go -- Documentation for sign & encrypt
|
||||
//
|
||||
// (c) 2016 Sudhi Herle <sudhi@herle.net>
|
||||
//
|
||||
// Licensing Terms: GPLv2
|
||||
//
|
||||
// If you need a commercial license for this work, please contact
|
||||
// the author.
|
||||
//
|
||||
// This software does not come with any express or implied
|
||||
// warranty; it is provided "as is". No claim is made to its
|
||||
// suitability for any purpose.
|
||||
|
||||
// Package sign implements Ed25519 signing, verification on files.
|
||||
// It builds upon golang.org/x/crypto/ed25519 by adding methods
|
||||
// for serializing and deserializing Ed25519 private & public keys.
|
||||
//
|
||||
// It can sign and verify very large files - it prehashes the files
|
||||
// with SHA-512 and then signs the SHA-512 checksum. The keys and signatures
|
||||
// are YAML files and so, human readable.
|
||||
//
|
||||
// It can encrypt files for multiple recipients - each of whom is identified
|
||||
// by their Ed25519 public key. The encryption by default generates ephmeral
|
||||
// Curve25519 keys and creates pair-wise shared secret for each recipient of
|
||||
// the encrypted file. The caller can optionally use a specific secret key
|
||||
// during the encryption process - this has the benefit of also authenticating
|
||||
// the sender (and the receiver can verify the sender if they possess the
|
||||
// corresponding public key).
|
||||
//
|
||||
// The sign, verify, encrypt, decrypt operations can use OpenSSH Ed25519 keys
|
||||
// *or* the keys generated by sigtool. This means, you can send encrypted
|
||||
// files to any recipient identified by their comment in `~/.ssh/authorized_keys`.
|
||||
package sign
|
914
sign/encrypt.go
914
sign/encrypt.go
|
@ -1,914 +0,0 @@
|
|||
// encrypt.go -- Ed25519 based encrypt/decrypt
|
||||
//
|
||||
// (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.
|
||||
//
|
||||
|
||||
// Implementation Notes for Encryption/Decryption:
|
||||
//
|
||||
// Header: has 3 parts:
|
||||
// - Fixed sized header
|
||||
// - Variable sized protobuf encoded header
|
||||
// - SHA256 sum of both above.
|
||||
//
|
||||
// Fixed size header:
|
||||
// - Magic: 7 bytes
|
||||
// - Version: 1 byte
|
||||
// - VLen: 4 byte
|
||||
//
|
||||
// Variable Length Segment:
|
||||
// - Protobuf encoded, per-recipient wrapped keys
|
||||
//
|
||||
// The variable length segment consists of one or more
|
||||
// recipients, each with their individually wrapped keys.
|
||||
//
|
||||
// The input data is encrypted with an expanded random 32-byte key:
|
||||
// - hkdf-sha512 of random key, salt, context
|
||||
// - the hkdf process yields a data-encryption key, nonce and hmac key.
|
||||
// - we use the header checksum as the 'salt' for HKDF; this ensures that
|
||||
// any modification of the header yields different keys
|
||||
//
|
||||
// We also calculate the cumulative hmac-sha256 of the plaintext blocks.
|
||||
// - When sender identity is present, we sign the final hmac and append
|
||||
// the signature as the "trailer".
|
||||
// - When sender identity is NOT present, we put random bytes as the
|
||||
// "signature". ie in either case, there is a trailer.
|
||||
//
|
||||
// Note: If the trailer is missing from a sigtool encrypted file - the
|
||||
// recipient has no guarantees of content immutability (ie tampering
|
||||
// from one of the _other_ recipients).
|
||||
//
|
||||
// The input data is broken up into "chunks"; each no larger than
|
||||
// maxChunkSize. The default block size is "chunkSize". Each block
|
||||
// is AEAD encrypted:
|
||||
// AEAD nonce = header.nonce || block#
|
||||
// AD of AEAD = chunk length+eof marker
|
||||
//
|
||||
// The encrypted block (includes the AEAD tag) length is written
|
||||
// as a big-endian 4-byte prefix. The high-order bit of this length
|
||||
// field is set for the last-block (denoting EOF).
|
||||
//
|
||||
|
||||
package sign
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/ed25519"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"crypto/subtle"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"golang.org/x/crypto/curve25519"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
|
||||
"git.rgst.io/homelab/sigtool/v3/internal/pb"
|
||||
)
|
||||
|
||||
// Encryption chunk size = 4MB
|
||||
const (
|
||||
// The latest version of the tool's output file format
|
||||
_SigtoolVersion = 3
|
||||
|
||||
chunkSize uint32 = 4 * 1048576 // 4 MB
|
||||
maxChunkSize uint32 = 1 << 30
|
||||
_EOF uint32 = 1 << 31
|
||||
|
||||
_Magic = "SigTool"
|
||||
_MagicLen = len(_Magic)
|
||||
_FixedHdrLen = _MagicLen + 1 + 4 // 1: version, 4: len of variable segment
|
||||
|
||||
_AesKeySize = 32
|
||||
_AEADNonceSize = 12
|
||||
_SaltSize = 32
|
||||
_RxNonceSize = 12 // nonce size of per-recipient encrypted blocks
|
||||
|
||||
_WrapReceiver = "Receiver Key"
|
||||
_WrapSender = "Sender Sig"
|
||||
_DataKeyExpansion = "Data Key Expansion"
|
||||
)
|
||||
|
||||
// Encryptor holds the encryption context
|
||||
type Encryptor struct {
|
||||
pb.Header
|
||||
key []byte // root key
|
||||
|
||||
nonce []byte // nonce for the data encrypting cipher
|
||||
buf []byte // I/O buf (chunk-sized)
|
||||
|
||||
ae cipher.AEAD
|
||||
hmac hash.Hash
|
||||
|
||||
// ephemeral key
|
||||
encSK []byte
|
||||
|
||||
// sender identity
|
||||
sender *PrivateKey
|
||||
|
||||
auth bool // set if the sender idetity is sent
|
||||
started bool
|
||||
stream bool
|
||||
}
|
||||
|
||||
// Create a new Encryption context for encrypting blocks of size 'blksize'.
|
||||
// If 'sk' is not nil, authenticate the sender to each receiver.
|
||||
func NewEncryptor(sk *PrivateKey, blksize uint64) (*Encryptor, error) {
|
||||
var blksz uint32
|
||||
|
||||
switch {
|
||||
case blksize == 0:
|
||||
blksz = chunkSize
|
||||
case blksize > uint64(maxChunkSize):
|
||||
blksz = maxChunkSize
|
||||
default:
|
||||
blksz = uint32(blksize)
|
||||
}
|
||||
|
||||
// generate ephemeral Curve25519 keys
|
||||
esk, epk, err := newSender()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("encrypt: %w", err)
|
||||
}
|
||||
|
||||
key := randBuf(_AesKeySize)
|
||||
salt := randBuf(_SaltSize)
|
||||
|
||||
e := &Encryptor{
|
||||
Header: pb.Header{
|
||||
ChunkSize: blksz,
|
||||
Salt: salt,
|
||||
Pk: epk,
|
||||
},
|
||||
|
||||
key: key,
|
||||
encSK: esk,
|
||||
sender: sk,
|
||||
}
|
||||
|
||||
if err = e.addSenderSig(sk); err != nil {
|
||||
return nil, fmt.Errorf("encrypt: %w", err)
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
// Add a new recipient to this encryption context.
|
||||
func (e *Encryptor) AddRecipient(pk *PublicKey) error {
|
||||
if e.started {
|
||||
return ErrEncStarted
|
||||
}
|
||||
|
||||
w, err := e.wrapKey(pk)
|
||||
if err == nil {
|
||||
e.Keys = append(e.Keys, w)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Encrypt the input stream 'rd' and write encrypted stream to 'wr'
|
||||
func (e *Encryptor) Encrypt(rd io.Reader, wr io.WriteCloser) error {
|
||||
if e.stream {
|
||||
return ErrEncIsStream
|
||||
}
|
||||
|
||||
if !e.started {
|
||||
err := e.start(wr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
buf := make([]byte, e.ChunkSize)
|
||||
|
||||
var i uint32
|
||||
var eof bool
|
||||
for !eof {
|
||||
n, err := io.ReadAtLeast(rd, buf, int(e.ChunkSize))
|
||||
if err != nil {
|
||||
switch err {
|
||||
case io.EOF, io.ErrClosedPipe, io.ErrUnexpectedEOF:
|
||||
eof = true
|
||||
default:
|
||||
return fmt.Errorf("encrypt: I/O read error: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if n >= 0 {
|
||||
err = e.encrypt(buf[:n], wr, i, eof)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
return wr.Close()
|
||||
}
|
||||
|
||||
// Begin the encryption process by writing the header
|
||||
func (e *Encryptor) start(wr io.Writer) error {
|
||||
varSize := e.Size()
|
||||
|
||||
buffer := make([]byte, _FixedHdrLen+varSize+sha256.Size)
|
||||
fixHdr := buffer[:_FixedHdrLen]
|
||||
varHdr := buffer[_FixedHdrLen : _FixedHdrLen+varSize]
|
||||
sumHdr := buffer[_FixedHdrLen+varSize:]
|
||||
|
||||
// Now assemble the fixed header
|
||||
copy(fixHdr[:], []byte(_Magic))
|
||||
fixHdr[_MagicLen] = _SigtoolVersion
|
||||
binary.BigEndian.PutUint32(fixHdr[_MagicLen+1:], uint32(varSize))
|
||||
|
||||
// Now marshal the variable portion
|
||||
_, err := e.MarshalTo(varHdr[:varSize])
|
||||
if err != nil {
|
||||
return fmt.Errorf("encrypt: can't marshal header: %w", err)
|
||||
}
|
||||
|
||||
h := sha256.New()
|
||||
h.Write(buffer[:_FixedHdrLen+varSize])
|
||||
cksum := h.Sum(sumHdr[:0])
|
||||
|
||||
// now make the data encryption keys, nonces etc.
|
||||
outbuf := make([]byte, sha256.Size+_AesKeySize+_AEADNonceSize)
|
||||
|
||||
// we mix the header checksum (and it captures the sigtool version, sender
|
||||
// identity, etc.)
|
||||
buf := expand(outbuf, e.key, cksum, []byte(_DataKeyExpansion))
|
||||
|
||||
var dkey, hmackey []byte
|
||||
|
||||
e.nonce, buf = buf[:_AEADNonceSize], buf[_AEADNonceSize:]
|
||||
dkey, buf = buf[:_AesKeySize], buf[_AesKeySize:]
|
||||
hmackey = buf
|
||||
|
||||
aes, err := aes.NewCipher(dkey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encrypt: %w", err)
|
||||
}
|
||||
|
||||
if e.ae, err = cipher.NewGCM(aes); err != nil {
|
||||
return fmt.Errorf("encrypt: %w", err)
|
||||
}
|
||||
|
||||
// Finally write out the header
|
||||
err = fullwrite(buffer, wr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encrypt: %w", err)
|
||||
}
|
||||
|
||||
e.hmac = hmac.New(sha256.New, hmackey)
|
||||
e.buf = make([]byte, e.ChunkSize+4+uint32(e.ae.Overhead()))
|
||||
e.started = true
|
||||
|
||||
debug("encrypt:\n\thdr-cksum: %x\n\taes-key: %x\n\tnonce: %x\n\thmac-key: %x\n",
|
||||
cksum, dkey, e.nonce, hmackey)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// encrypt exactly _one_ block of data
|
||||
// The nonce is constructed from the salt, block# and block-size.
|
||||
// This protects the output stream from re-ordering attacks and length
|
||||
// modification attacks. The encoded length & block number is used as
|
||||
// additional data in the AEAD construction.
|
||||
func (e *Encryptor) encrypt(pt []byte, wr io.Writer, i uint32, eof bool) error {
|
||||
var z uint32 = uint32(len(pt))
|
||||
var nonce [_AEADNonceSize]byte
|
||||
|
||||
// mark last block
|
||||
if eof {
|
||||
z |= _EOF
|
||||
}
|
||||
|
||||
copy(nonce[:], e.nonce)
|
||||
|
||||
// now change the upper bytes to track the block#; we use the len+eof as AD
|
||||
binary.BigEndian.PutUint32(nonce[:4], i)
|
||||
|
||||
// put the encoded length+eof at the start of the output buf
|
||||
b := e.buf[:4]
|
||||
ctbuf := e.buf[4:]
|
||||
|
||||
binary.BigEndian.PutUint32(b, z)
|
||||
ct := e.ae.Seal(ctbuf[:0], nonce[:], pt, b)
|
||||
|
||||
// total number of bytes written
|
||||
n := len(ct) + 4
|
||||
err := fullwrite(e.buf[:n], wr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encrypt: %w", err)
|
||||
}
|
||||
|
||||
e.hmac.Write(b)
|
||||
e.hmac.Write(pt)
|
||||
|
||||
if eof {
|
||||
return e.writeTrailer(wr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write a trailer:
|
||||
// - if authenticating sender, sign the hmac and put the signature in the trailer
|
||||
// - if not authenticating sender, write random bytes to the trailer
|
||||
func (e *Encryptor) writeTrailer(wr io.Writer) error {
|
||||
var tr []byte
|
||||
|
||||
switch e.auth {
|
||||
case true:
|
||||
var hmac [sha256.Size]byte
|
||||
|
||||
e.hmac.Sum(hmac[:0])
|
||||
|
||||
// We know sender is non null.
|
||||
sig, err := e.sender.SignMessage(hmac[:], "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("encrypt: trailer: %w", err)
|
||||
}
|
||||
tr = sig.Sig
|
||||
|
||||
case false:
|
||||
tr = randBuf(ed25519.SignatureSize)
|
||||
|
||||
}
|
||||
|
||||
if err := fullwrite(tr, wr); err != nil {
|
||||
return fmt.Errorf("encrypt: trailer %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decryptor holds the decryption context
|
||||
type Decryptor struct {
|
||||
pb.Header
|
||||
|
||||
ae cipher.AEAD
|
||||
hmac hash.Hash
|
||||
|
||||
sender *PublicKey
|
||||
|
||||
rd io.Reader
|
||||
buf []byte
|
||||
nonce []byte // nonce for the data decrypting cipher
|
||||
|
||||
key []byte // Decrypted root key
|
||||
hdrsum []byte // cached header checksum
|
||||
auth bool // flag set to true if sender signed the key
|
||||
eof bool
|
||||
stream bool
|
||||
}
|
||||
|
||||
// Create a new decryption context and if 'pk' is given, check that it matches
|
||||
// the sender
|
||||
func NewDecryptor(rd io.Reader) (*Decryptor, error) {
|
||||
var b [_FixedHdrLen]byte
|
||||
|
||||
_, err := io.ReadFull(rd, b[:])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decrypt: err while reading header: %w", err)
|
||||
}
|
||||
|
||||
if bytes.Compare(b[:_MagicLen], []byte(_Magic)) != 0 {
|
||||
return nil, ErrNotSigTool
|
||||
}
|
||||
|
||||
// Version check
|
||||
if b[_MagicLen] != _SigtoolVersion {
|
||||
return nil, fmt.Errorf("decrypt: Unsupported version %d; this tool only supports v%d",
|
||||
b[_MagicLen], _SigtoolVersion)
|
||||
}
|
||||
|
||||
varSize := binary.BigEndian.Uint32(b[_MagicLen+1:])
|
||||
|
||||
// sanity check on variable segment length
|
||||
if varSize > 1048576 {
|
||||
return nil, ErrHeaderTooBig
|
||||
}
|
||||
if varSize < 32 {
|
||||
return nil, ErrHeaderTooSmall
|
||||
}
|
||||
|
||||
// SHA256 is the trailer part of the file-header
|
||||
varBuf := make([]byte, varSize+sha256.Size)
|
||||
|
||||
_, err = io.ReadFull(rd, varBuf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decrypt: error while reading header: %w", err)
|
||||
}
|
||||
|
||||
// The checksum in the header
|
||||
verify := varBuf[varSize:]
|
||||
|
||||
// the checksum we calculated
|
||||
var csum [sha256.Size]byte
|
||||
|
||||
h := sha256.New()
|
||||
h.Write(b[:])
|
||||
h.Write(varBuf[:varSize])
|
||||
cksum := h.Sum(csum[:0])
|
||||
|
||||
if subtle.ConstantTimeCompare(verify, cksum) == 0 {
|
||||
return nil, ErrBadHeader
|
||||
}
|
||||
|
||||
d := &Decryptor{
|
||||
rd: rd,
|
||||
hdrsum: cksum,
|
||||
}
|
||||
|
||||
err = d.Unmarshal(varBuf[:varSize])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decrypt: decode error: %w", err)
|
||||
}
|
||||
|
||||
if d.ChunkSize == 0 || d.ChunkSize >= maxChunkSize {
|
||||
return nil, fmt.Errorf("decrypt: invalid chunkSize %d", d.ChunkSize)
|
||||
}
|
||||
|
||||
if len(d.Salt) != _SaltSize {
|
||||
return nil, fmt.Errorf("decrypt: invalid nonce length %d", len(d.Salt))
|
||||
}
|
||||
|
||||
if len(d.Keys) == 0 {
|
||||
return nil, ErrNoWrappedKeys
|
||||
}
|
||||
|
||||
// sanity check on the wrapped keys
|
||||
for i, w := range d.Keys {
|
||||
if len(w.DKey) <= _AesKeySize {
|
||||
return nil, fmt.Errorf("decrypt: wrapped key %d: wrong-size encrypted key", i)
|
||||
}
|
||||
}
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// Use Private Key 'sk' to decrypt the encrypted keys in the header and optionally validate
|
||||
// the sender
|
||||
func (d *Decryptor) SetPrivateKey(sk *PrivateKey, senderPk *PublicKey) error {
|
||||
var err error
|
||||
var key []byte
|
||||
|
||||
for i, w := range d.Keys {
|
||||
key, err = d.unwrapKey(w, sk)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decrypt: can't unwrap key %d: %w", i, err)
|
||||
}
|
||||
if key != nil {
|
||||
d.key = key
|
||||
d.sender = senderPk
|
||||
goto havekey
|
||||
}
|
||||
}
|
||||
|
||||
return ErrBadKey
|
||||
|
||||
havekey:
|
||||
if err := d.verifySender(key, senderPk); err != nil {
|
||||
return fmt.Errorf("decrypt: %w", err)
|
||||
}
|
||||
|
||||
outbuf := make([]byte, sha256.Size+_AesKeySize+_AEADNonceSize)
|
||||
|
||||
buf := expand(outbuf, d.key, d.hdrsum, []byte(_DataKeyExpansion))
|
||||
|
||||
var dkey, hmackey []byte
|
||||
|
||||
d.nonce, buf = buf[:_AEADNonceSize], buf[_AEADNonceSize:]
|
||||
dkey, buf = buf[:_AesKeySize], buf[_AesKeySize:]
|
||||
hmackey = buf
|
||||
|
||||
d.hmac = hmac.New(sha256.New, hmackey)
|
||||
|
||||
aes, err := aes.NewCipher(dkey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decrypt: %w", err)
|
||||
}
|
||||
|
||||
d.ae, err = cipher.NewGCM(aes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decrypt: %w", err)
|
||||
}
|
||||
|
||||
debug("decrypt:\n\thdr-cksum: %x\n\taes-key: %x\n\tnonce: %x\n\thmac-key: %x\n",
|
||||
d.hdrsum, dkey, d.nonce, hmackey)
|
||||
|
||||
// We have a separate on-stack buffer for reading the header (4 bytes).
|
||||
// Thus, the actual I/O buf will never be larger than the chunksize + AEAD Overhead
|
||||
d.buf = make([]byte, int(d.ChunkSize)+d.ae.Overhead())
|
||||
return nil
|
||||
}
|
||||
|
||||
// AuthenticatedSender returns true if the sender authenticated themselves
|
||||
// (the data-encryption key is signed).
|
||||
func (d *Decryptor) AuthenticatedSender() bool {
|
||||
return d.auth
|
||||
}
|
||||
|
||||
// Decrypt the file and write to 'wr'
|
||||
func (d *Decryptor) Decrypt(wr io.Writer) error {
|
||||
if d.key == nil {
|
||||
return ErrNoKey
|
||||
}
|
||||
|
||||
if d.stream {
|
||||
return ErrDecStarted
|
||||
}
|
||||
|
||||
if d.eof {
|
||||
return io.EOF
|
||||
}
|
||||
|
||||
var i uint32
|
||||
for i = 0; ; i++ {
|
||||
c, eof, err := d.decrypt(i)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(c) > 0 {
|
||||
err = fullwrite(c, wr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decrypt: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if eof {
|
||||
d.eof = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Decrypt exactly one chunk of data
|
||||
func (d *Decryptor) decrypt(i uint32) ([]byte, bool, error) {
|
||||
var ovh uint32 = uint32(d.ae.Overhead())
|
||||
var b [4]byte
|
||||
var nonce [_AEADNonceSize]byte
|
||||
|
||||
n, err := io.ReadFull(d.rd, b[:])
|
||||
if err != nil || n == 0 {
|
||||
return nil, false, fmt.Errorf("decrypt: premature EOF while reading header block %d", i)
|
||||
}
|
||||
|
||||
m := binary.BigEndian.Uint32(b[:])
|
||||
eof := (m & _EOF) > 0
|
||||
m &= (_EOF - 1)
|
||||
|
||||
// Sanity check - in case of corrupt header
|
||||
switch {
|
||||
case m > uint32(d.ChunkSize):
|
||||
return nil, false, fmt.Errorf("decrypt: chunksize is too large (%d)", m)
|
||||
|
||||
case m == 0:
|
||||
if !eof {
|
||||
return nil, false, fmt.Errorf("decrypt: block %d: zero-sized chunk without EOF", i)
|
||||
}
|
||||
return nil, eof, nil
|
||||
|
||||
default:
|
||||
}
|
||||
|
||||
// make the nonce - top 4 bytes are the counter
|
||||
copy(nonce[:], d.nonce)
|
||||
binary.BigEndian.PutUint32(nonce[:4], i)
|
||||
|
||||
z := m + ovh
|
||||
n, err = io.ReadFull(d.rd, d.buf[:z])
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("decrypt: premature EOF while reading block %d: %w", i, err)
|
||||
}
|
||||
|
||||
pt, err := d.ae.Open(d.buf[:0], nonce[:], d.buf[:n], b[:])
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("decrypt: can't decrypt chunk %d: %w", i, err)
|
||||
}
|
||||
|
||||
if uint32(len(pt)) != m {
|
||||
return nil, false, fmt.Errorf("decrypt: partial unsealed bytes; exp %d, saw %d", m, len(pt))
|
||||
}
|
||||
|
||||
d.hmac.Write(b[:])
|
||||
d.hmac.Write(pt)
|
||||
|
||||
if eof {
|
||||
return d.processTrailer(pt, eof)
|
||||
}
|
||||
|
||||
return pt, eof, nil
|
||||
}
|
||||
|
||||
func (d *Decryptor) processTrailer(pt []byte, eof bool) ([]byte, bool, error) {
|
||||
var rd [ed25519.SignatureSize]byte
|
||||
|
||||
_, err := io.ReadFull(d.rd, rd[:])
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("decrypt: premature EOF while reading trailer: %w", err)
|
||||
}
|
||||
|
||||
if !d.auth {
|
||||
// these are random bytes; ignore em
|
||||
return pt, eof, nil
|
||||
}
|
||||
|
||||
var hmac [sha256.Size]byte
|
||||
|
||||
cksum := d.hmac.Sum(hmac[:0])
|
||||
ss := &Signature{
|
||||
Sig: rd[:],
|
||||
}
|
||||
|
||||
if ok := d.sender.VerifyMessage(cksum, ss); !ok {
|
||||
return nil, eof, ErrBadTrailer
|
||||
}
|
||||
|
||||
return pt, eof, nil
|
||||
}
|
||||
|
||||
// optionally sign the checksum and encrypt everything
|
||||
func (e *Encryptor) addSenderSig(sk *PrivateKey) error {
|
||||
var zero [ed25519.SignatureSize]byte
|
||||
var auth bool
|
||||
sig := zero[:]
|
||||
|
||||
if e.sender != nil {
|
||||
var csum [sha256.Size]byte
|
||||
|
||||
// We capture essential meta-data from the sender; viz:
|
||||
// - Sender tool version
|
||||
// - Sender generated curve25519 PK
|
||||
// - session salt, root key
|
||||
|
||||
h := sha256.New()
|
||||
h.Write([]byte(_Magic))
|
||||
h.Write([]byte{_SigtoolVersion})
|
||||
h.Write(e.Pk)
|
||||
h.Write(e.Salt)
|
||||
h.Write(e.key)
|
||||
cksum := h.Sum(csum[:0])
|
||||
|
||||
xsig, err := e.sender.SignMessage(cksum, "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("wrap: can't sign: %w", err)
|
||||
}
|
||||
sig = xsig.Sig
|
||||
auth = true
|
||||
}
|
||||
|
||||
buf := make([]byte, _AesKeySize+_AEADNonceSize)
|
||||
buf = expand(buf, e.key, e.Salt, []byte(_WrapSender))
|
||||
|
||||
ekey, nonce := buf[:_AesKeySize], buf[_AesKeySize:]
|
||||
|
||||
aes, err := aes.NewCipher(ekey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("senderId: %w", err)
|
||||
}
|
||||
|
||||
ae, err := cipher.NewGCM(aes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("senderId: %w", err)
|
||||
}
|
||||
|
||||
outbuf := make([]byte, ed25519.SignatureSize+ae.Overhead())
|
||||
buf = ae.Seal(outbuf[:0], nonce, sig, nil)
|
||||
|
||||
e.auth = auth
|
||||
e.Sender = buf
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// unwrap sender's signature using 'key' and extract the signature
|
||||
// Optionally, verify the signature using the sender's PK (if provided).
|
||||
func (d *Decryptor) verifySender(key []byte, senderPk *PublicKey) error {
|
||||
outbuf := make([]byte, _AEADNonceSize+_AesKeySize)
|
||||
buf := expand(outbuf, key, d.Salt, []byte(_WrapSender))
|
||||
|
||||
ekey, nonce := buf[:_AesKeySize], buf[_AesKeySize:]
|
||||
|
||||
aes, err := aes.NewCipher(ekey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unwrap: %w", err)
|
||||
}
|
||||
|
||||
ae, err := cipher.NewGCM(aes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unwrap: %w", err)
|
||||
}
|
||||
|
||||
var sigbuf [ed25519.SignatureSize]byte
|
||||
var zero [ed25519.SignatureSize]byte
|
||||
|
||||
sig, err := ae.Open(sigbuf[:0], nonce, d.Sender, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unwrap: can't open sender info: %w", err)
|
||||
}
|
||||
|
||||
// Did the sender actually sign anything?
|
||||
if subtle.ConstantTimeCompare(zero[:], sig) == 0 {
|
||||
if senderPk == nil {
|
||||
return ErrNoSenderPK
|
||||
}
|
||||
|
||||
var csum [sha256.Size]byte
|
||||
|
||||
h := sha256.New()
|
||||
h.Write([]byte(_Magic))
|
||||
h.Write([]byte{_SigtoolVersion})
|
||||
h.Write(d.Pk)
|
||||
h.Write(d.Salt)
|
||||
h.Write(key)
|
||||
cksum := h.Sum(csum[:0])
|
||||
|
||||
ss := &Signature{
|
||||
Sig: sig,
|
||||
}
|
||||
|
||||
if ok := senderPk.VerifyMessage(cksum, ss); !ok {
|
||||
return ErrBadSender
|
||||
}
|
||||
|
||||
// we set this to indicate that the sender authenticated themselves;
|
||||
d.auth = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Wrap data encryption key 'k' with the sender's PK and our ephemeral curve SK
|
||||
//
|
||||
// basically, we do a scalarmult: Ephemeral encryption/decryption SK x receiver PK
|
||||
func (e *Encryptor) wrapKey(pk *PublicKey) (*pb.WrappedKey, error) {
|
||||
rxPK := pk.ToCurve25519PK()
|
||||
sekrit, err := curve25519.X25519(e.encSK, rxPK)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("wrap: %w", err)
|
||||
}
|
||||
|
||||
var shasum [sha256.Size]byte
|
||||
|
||||
rbuf := randBuf(_RxNonceSize)
|
||||
|
||||
h := sha256.New()
|
||||
h.Write(e.Salt)
|
||||
h.Write(rbuf[:])
|
||||
h.Sum(shasum[:0])
|
||||
|
||||
out := make([]byte, _AesKeySize+_RxNonceSize)
|
||||
buf := expand(out[:], sekrit, shasum[:], []byte(_WrapReceiver))
|
||||
|
||||
kek, nonce := buf[:_AesKeySize], buf[_AesKeySize:]
|
||||
|
||||
aes, err := aes.NewCipher(kek)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("wrap: %w", err)
|
||||
}
|
||||
|
||||
ae, err := cipher.NewGCM(aes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("wrap: %w", err)
|
||||
}
|
||||
|
||||
ekey := make([]byte, ae.Overhead()+len(e.key))
|
||||
w := &pb.WrappedKey{
|
||||
DKey: ae.Seal(ekey[:0], nonce, e.key, pk.Pk),
|
||||
Nonce: rbuf,
|
||||
}
|
||||
|
||||
return w, nil
|
||||
}
|
||||
|
||||
// Unwrap a wrapped key using the receivers Ed25519 secret key 'sk' and
|
||||
// senders ephemeral PublicKey
|
||||
func (d *Decryptor) unwrapKey(w *pb.WrappedKey, sk *PrivateKey) ([]byte, error) {
|
||||
ourSK := sk.ToCurve25519SK()
|
||||
sekrit, err := curve25519.X25519(ourSK, d.Pk)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unwrap: %w", err)
|
||||
}
|
||||
|
||||
var shasum [sha256.Size]byte
|
||||
|
||||
h := sha256.New()
|
||||
h.Write(d.Salt)
|
||||
h.Write(w.Nonce)
|
||||
h.Sum(shasum[:0])
|
||||
|
||||
out := make([]byte, _AesKeySize+_RxNonceSize)
|
||||
buf := expand(out[:], sekrit, shasum[:], []byte(_WrapReceiver))
|
||||
|
||||
kek, nonce := buf[:_AesKeySize], buf[_AesKeySize:]
|
||||
|
||||
aes, err := aes.NewCipher(kek)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("wrap: %w", err)
|
||||
}
|
||||
|
||||
ae, err := cipher.NewGCM(aes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("wrap: %w", err)
|
||||
}
|
||||
|
||||
want := _AesKeySize + ae.Overhead()
|
||||
if len(w.DKey) != want {
|
||||
return nil, fmt.Errorf("unwrap: incorrect decrypt bytes (need %d, saw %d)", want, len(w.DKey))
|
||||
}
|
||||
|
||||
pk := sk.PublicKey()
|
||||
dkey := make([]byte, _AesKeySize) // decrypted data decryption key
|
||||
|
||||
// we indicate incorrect receiver SK by returning a nil key
|
||||
dkey, err = ae.Open(dkey[:0], nonce, w.DKey, pk.Pk)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// we have successfully found the correct recipient
|
||||
return dkey, nil
|
||||
}
|
||||
|
||||
// Write _all_ bytes of buffer 'buf'
|
||||
func fullwrite(buf []byte, wr io.Writer) error {
|
||||
n := len(buf)
|
||||
|
||||
for n > 0 {
|
||||
m, err := wr.Write(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
n -= m
|
||||
buf = buf[m:]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// generate a KEK from a shared DH key and a Pub Key
|
||||
func expand(out []byte, shared, salt, ad []byte) []byte {
|
||||
h := hkdf.New(sha512.New, shared, salt, ad)
|
||||
_, err := io.ReadFull(h, out)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("hkdf: failed to generate %d bytes: %s", len(out), err))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func newSender() (sk, pk []byte, err error) {
|
||||
var csk [32]byte
|
||||
|
||||
randRead(csk[:])
|
||||
clamp(csk[:])
|
||||
pk, err = curve25519.X25519(csk[:], curve25519.Basepoint)
|
||||
sk = csk[:]
|
||||
return
|
||||
}
|
||||
|
||||
// do sha256 on a list of byte slices
|
||||
func sha256Slices(v ...[]byte) []byte {
|
||||
h := sha256.New()
|
||||
for _, x := range v {
|
||||
h.Write(x)
|
||||
}
|
||||
return h.Sum(nil)[:]
|
||||
}
|
||||
|
||||
var _debug int = 0
|
||||
|
||||
// Enable debugging of this module;
|
||||
// level > 0 elicits debug messages on os.Stderr
|
||||
func Debug(level int) {
|
||||
_debug = level
|
||||
}
|
||||
|
||||
func debug(s string, v ...interface{}) {
|
||||
if _debug <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
z := fmt.Sprintf(s, v...)
|
||||
if n := len(z); z[n-1] != '\n' {
|
||||
z += "\n"
|
||||
}
|
||||
os.Stderr.WriteString(z)
|
||||
os.Stderr.Sync()
|
||||
}
|
||||
|
||||
// EOF
|
|
@ -1,477 +0,0 @@
|
|||
// crypt_test.go -- Test harness for encrypt/decrypt bits
|
||||
//
|
||||
// (c) 2016 Sudhi Herle <sudhi@herle.net>
|
||||
//
|
||||
// Licensing Terms: GPLv2
|
||||
//
|
||||
// If you need a commercial license for this work, please contact
|
||||
// the author.
|
||||
//
|
||||
// This software does not come with any express or implied
|
||||
// warranty; it is provided "as is". No claim is made to its
|
||||
// suitability for any purpose.
|
||||
|
||||
package sign
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type Buffer struct {
|
||||
bytes.Buffer
|
||||
}
|
||||
|
||||
func (b *Buffer) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// one sender, one receiver no verification of sender
|
||||
func TestEncryptSimple(t *testing.T) {
|
||||
assert := newAsserter(t)
|
||||
|
||||
sk, err := NewPrivateKey()
|
||||
assert(err == nil, "SK gen failed: %s", err)
|
||||
|
||||
pk := sk.PublicKey()
|
||||
|
||||
var blkSize int = 1024
|
||||
var size int = (blkSize * 10)
|
||||
|
||||
// cleartext
|
||||
buf := make([]byte, size)
|
||||
for i := 0; i < len(buf); i++ {
|
||||
buf[i] = byte(i & 0xff)
|
||||
}
|
||||
|
||||
ee, err := NewEncryptor(nil, uint64(blkSize))
|
||||
assert(err == nil, "encryptor create fail: %s", err)
|
||||
|
||||
err = ee.AddRecipient(pk)
|
||||
assert(err == nil, "can't add recipient: %s", err)
|
||||
|
||||
rd := bytes.NewBuffer(buf)
|
||||
wr := Buffer{}
|
||||
|
||||
err = ee.Encrypt(rd, &wr)
|
||||
assert(err == nil, "encrypt fail: %s", err)
|
||||
|
||||
rd = bytes.NewBuffer(wr.Bytes())
|
||||
|
||||
dd, err := NewDecryptor(rd)
|
||||
assert(err == nil, "decryptor create fail: %s", err)
|
||||
|
||||
err = dd.SetPrivateKey(sk, nil)
|
||||
assert(err == nil, "decryptor can't add SK: %s", err)
|
||||
|
||||
wr = Buffer{}
|
||||
err = dd.Decrypt(&wr)
|
||||
assert(err == nil, "decrypt fail: %s", err)
|
||||
|
||||
b := wr.Bytes()
|
||||
assert(len(b) == len(buf), "decrypt length mismatch: exp %d, saw %d", len(buf), len(b))
|
||||
|
||||
assert(byteEq(b, buf), "decrypt content mismatch")
|
||||
}
|
||||
|
||||
// one sender, one receiver - small blocks
|
||||
func TestEncryptSmallSizes(t *testing.T) {
|
||||
assert := newAsserter(t)
|
||||
|
||||
sk, err := NewPrivateKey()
|
||||
assert(err == nil, "SK gen failed: %s", err)
|
||||
|
||||
pk := sk.PublicKey()
|
||||
|
||||
var blkSize int = 8
|
||||
var size int = (blkSize * 4)
|
||||
|
||||
// cleartext
|
||||
bigbuf := make([]byte, size)
|
||||
for i := 0; i < len(bigbuf); i++ {
|
||||
bigbuf[i] = byte(i & 0xff)
|
||||
}
|
||||
|
||||
// encrypt progressively larger bufs
|
||||
for i := 1; i < len(bigbuf); i++ {
|
||||
buf := bigbuf[:i]
|
||||
|
||||
ee, err := NewEncryptor(nil, uint64(blkSize))
|
||||
assert(err == nil, "encryptor-%d create fail: %s", i, err)
|
||||
|
||||
err = ee.AddRecipient(pk)
|
||||
assert(err == nil, "encryptor-%d: can't add recipient: %s", i, err)
|
||||
|
||||
rd := bytes.NewBuffer(buf)
|
||||
wr := Buffer{}
|
||||
|
||||
err = ee.Encrypt(rd, &wr)
|
||||
assert(err == nil, "encrypt-%d fail: %s", i, err)
|
||||
|
||||
rd = bytes.NewBuffer(wr.Bytes())
|
||||
|
||||
dd, err := NewDecryptor(rd)
|
||||
assert(err == nil, "decryptor-%d create fail: %s", i, err)
|
||||
|
||||
err = dd.SetPrivateKey(sk, nil)
|
||||
assert(err == nil, "decryptor-%d can't add SK: %s", i, err)
|
||||
|
||||
wr = Buffer{}
|
||||
err = dd.Decrypt(&wr)
|
||||
assert(err == nil, "decrypt-%d fail: %s", i, err)
|
||||
|
||||
b := wr.Bytes()
|
||||
assert(len(b) == len(buf), "decrypt-%d length mismatch: exp %d, saw %d", i, len(buf), len(b))
|
||||
|
||||
assert(byteEq(b, buf), "decrypt-%d content mismatch", i)
|
||||
}
|
||||
}
|
||||
|
||||
// test corrupted header or corrupted input
|
||||
func TestEncryptCorrupted(t *testing.T) {
|
||||
assert := newAsserter(t)
|
||||
|
||||
sk, err := NewPrivateKey()
|
||||
assert(err == nil, "SK gen failed: %s", err)
|
||||
|
||||
pk := sk.PublicKey()
|
||||
|
||||
var blkSize int = 1024
|
||||
var size int = (blkSize * 23) + randmod(blkSize)
|
||||
|
||||
// cleartext
|
||||
buf := make([]byte, size)
|
||||
for i := 0; i < len(buf); i++ {
|
||||
buf[i] = byte(i & 0xff)
|
||||
}
|
||||
|
||||
ee, err := NewEncryptor(nil, uint64(blkSize))
|
||||
assert(err == nil, "encryptor create fail: %s", err)
|
||||
|
||||
err = ee.AddRecipient(pk)
|
||||
assert(err == nil, "can't add recipient: %s", err)
|
||||
|
||||
rd := bytes.NewReader(buf)
|
||||
wr := Buffer{}
|
||||
|
||||
err = ee.Encrypt(rd, &wr)
|
||||
assert(err == nil, "encrypt fail: %s", err)
|
||||
|
||||
rb := wr.Bytes()
|
||||
n := len(rb)
|
||||
|
||||
// corrupt the input
|
||||
for i := 0; i < n; i++ {
|
||||
j := randint() % n
|
||||
rb[j] = byte(randint() & 0xff)
|
||||
}
|
||||
|
||||
rd = bytes.NewReader(rb)
|
||||
dd, err := NewDecryptor(rd)
|
||||
assert(err != nil, "decryptor works on bad input")
|
||||
assert(dd == nil, "decryptor not nil for bad input")
|
||||
}
|
||||
|
||||
// one sender, one receiver with verification of sender
|
||||
func TestEncryptSenderVerified(t *testing.T) {
|
||||
assert := newAsserter(t)
|
||||
|
||||
sender, err := NewPrivateKey()
|
||||
assert(err == nil, "sender SK gen failed: %s", err)
|
||||
|
||||
receiver, err := NewPrivateKey()
|
||||
assert(err == nil, "receiver SK gen failed: %s", err)
|
||||
|
||||
var blkSize int = 1024
|
||||
var size int = (blkSize * 23) + randmod(blkSize)
|
||||
|
||||
// cleartext
|
||||
buf := make([]byte, size)
|
||||
for i := 0; i < len(buf); i++ {
|
||||
buf[i] = byte(i & 0xff)
|
||||
}
|
||||
|
||||
ee, err := NewEncryptor(sender, uint64(blkSize))
|
||||
assert(err == nil, "encryptor create fail: %s", err)
|
||||
|
||||
err = ee.AddRecipient(receiver.PublicKey())
|
||||
assert(err == nil, "can't add recipient: %s", err)
|
||||
|
||||
rd := bytes.NewBuffer(buf)
|
||||
wr := Buffer{}
|
||||
|
||||
err = ee.Encrypt(rd, &wr)
|
||||
assert(err == nil, "encrypt fail: %s", err)
|
||||
|
||||
rd = bytes.NewBuffer(wr.Bytes())
|
||||
|
||||
dd, err := NewDecryptor(rd)
|
||||
assert(err == nil, "decryptor create fail: %s", err)
|
||||
|
||||
randkey, err := NewPrivateKey()
|
||||
assert(err == nil, "rand SK gen failed: %s", err)
|
||||
|
||||
// first send a wrong sender PK
|
||||
err = dd.SetPrivateKey(receiver, randkey.PublicKey())
|
||||
assert(err != nil, "decryptor failed to verify sender")
|
||||
|
||||
// then the correct sender PK
|
||||
err = dd.SetPrivateKey(receiver, sender.PublicKey())
|
||||
assert(err == nil, "decryptor can't add SK: %s", err)
|
||||
|
||||
wr = Buffer{}
|
||||
err = dd.Decrypt(&wr)
|
||||
assert(err == nil, "decrypt fail: %s", err)
|
||||
|
||||
b := wr.Bytes()
|
||||
assert(len(b) == len(buf), "decrypt length mismatch: exp %d, saw %d", len(buf), len(b))
|
||||
|
||||
assert(byteEq(b, buf), "decrypt content mismatch")
|
||||
}
|
||||
|
||||
// one sender, multiple receivers, each decrypting the blob
|
||||
func TestEncryptMultiReceiver(t *testing.T) {
|
||||
assert := newAsserter(t)
|
||||
|
||||
sender, err := NewPrivateKey()
|
||||
assert(err == nil, "sender SK gen failed: %s", err)
|
||||
|
||||
var blkSize int = 1024
|
||||
var size int = (blkSize * 23) + randmod(blkSize)
|
||||
|
||||
// cleartext
|
||||
buf := make([]byte, size)
|
||||
for i := 0; i < len(buf); i++ {
|
||||
buf[i] = byte(i & 0xff)
|
||||
}
|
||||
|
||||
ee, err := NewEncryptor(sender, uint64(blkSize))
|
||||
assert(err == nil, "encryptor create fail: %s", err)
|
||||
|
||||
n := 4
|
||||
rx := make([]*PrivateKey, n)
|
||||
for i := 0; i < n; i++ {
|
||||
r, err := NewPrivateKey()
|
||||
assert(err == nil, "can't make receiver SK %d: %s", i, err)
|
||||
rx[i] = r
|
||||
|
||||
err = ee.AddRecipient(r.PublicKey())
|
||||
assert(err == nil, "can't add recipient %d: %s", i, err)
|
||||
}
|
||||
|
||||
rd := bytes.NewBuffer(buf)
|
||||
wr := Buffer{}
|
||||
|
||||
err = ee.Encrypt(rd, &wr)
|
||||
assert(err == nil, "encrypt fail: %s", err)
|
||||
|
||||
encBytes := wr.Bytes()
|
||||
for i := 0; i < n; i++ {
|
||||
rd = bytes.NewBuffer(encBytes)
|
||||
|
||||
dd, err := NewDecryptor(rd)
|
||||
assert(err == nil, "decryptor %d create fail: %s", i, err)
|
||||
|
||||
err = dd.SetPrivateKey(rx[i], sender.PublicKey())
|
||||
assert(err == nil, "decryptor can't add SK %d: %s", i, err)
|
||||
|
||||
wr = Buffer{}
|
||||
err = dd.Decrypt(&wr)
|
||||
assert(err == nil, "decrypt %d fail: %s", i, err)
|
||||
|
||||
b := wr.Bytes()
|
||||
assert(len(b) == len(buf), "decrypt %d length mismatch: exp %d, saw %d", i, len(buf), len(b))
|
||||
|
||||
assert(byteEq(b, buf), "decrypt %d content mismatch", i)
|
||||
}
|
||||
}
|
||||
|
||||
// Test stream write and read
|
||||
func TestStreamIO(t *testing.T) {
|
||||
assert := newAsserter(t)
|
||||
|
||||
receiver, err := NewPrivateKey()
|
||||
assert(err == nil, "receiver keypair gen failed: %s", err)
|
||||
|
||||
var blkSize int = 1024
|
||||
var size int = (blkSize * 10)
|
||||
|
||||
// cleartext
|
||||
buf := make([]byte, size)
|
||||
for i := 0; i < len(buf); i++ {
|
||||
buf[i] = byte(i & 0xff)
|
||||
}
|
||||
|
||||
ee, err := NewEncryptor(nil, uint64(blkSize))
|
||||
assert(err == nil, "encryptor create fail: %s", err)
|
||||
|
||||
err = ee.AddRecipient(receiver.PublicKey())
|
||||
assert(err == nil, "can't add recipient: %s", err)
|
||||
|
||||
wr := Buffer{}
|
||||
wio, err := ee.NewStreamWriter(&wr)
|
||||
assert(err == nil, "can't start stream writer: %s", err)
|
||||
|
||||
// chunksize for writing to stream
|
||||
csize := 19
|
||||
rbuf := buf
|
||||
for len(rbuf) > 0 {
|
||||
m := csize
|
||||
if len(rbuf) < m {
|
||||
m = len(rbuf)
|
||||
}
|
||||
|
||||
n, err := wio.Write(rbuf[:m])
|
||||
assert(err == nil, "stream write failed: %s", err)
|
||||
assert(n == m, "stream write mismatch: exp %d, saw %d", m, n)
|
||||
|
||||
rbuf = rbuf[m:]
|
||||
}
|
||||
err = wio.Close()
|
||||
assert(err == nil, "stream close failed: %s", err)
|
||||
|
||||
_, err = wio.Write(buf[:csize])
|
||||
assert(err != nil, "stream write accepted I/O after close: %s", err)
|
||||
|
||||
rd := bytes.NewBuffer(wr.Bytes())
|
||||
|
||||
dd, err := NewDecryptor(rd)
|
||||
assert(err == nil, "decryptor create fail: %s", err)
|
||||
|
||||
err = dd.SetPrivateKey(receiver, nil)
|
||||
assert(err == nil, "decryptor can't add SK: %s", err)
|
||||
|
||||
rio, err := dd.NewStreamReader()
|
||||
assert(err == nil, "stream reader failed: %s", err)
|
||||
|
||||
rbuf = make([]byte, csize)
|
||||
wr = Buffer{}
|
||||
n := 0
|
||||
for {
|
||||
m, err := rio.Read(rbuf)
|
||||
assert(err == nil || err == io.EOF, "streamread fail: %s", err)
|
||||
|
||||
if m > 0 {
|
||||
wr.Write(rbuf[:m])
|
||||
n += m
|
||||
}
|
||||
if err == io.EOF || m == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
b := wr.Bytes()
|
||||
assert(n == len(b), "streamread: bad buflen; exp %d, saw %d", n, len(b))
|
||||
assert(n == len(buf), "streamread: decrypt len mismatch; exp %d, saw %d", len(buf), n)
|
||||
|
||||
assert(byteEq(b, buf), "decrypt content mismatch")
|
||||
|
||||
}
|
||||
|
||||
// Test stream write and read with small sizes
|
||||
func TestSmallSizeStreamIO(t *testing.T) {
|
||||
assert := newAsserter(t)
|
||||
|
||||
receiver, err := NewPrivateKey()
|
||||
assert(err == nil, "receiver SK gen failed: %s", err)
|
||||
|
||||
var blkSize int = 8
|
||||
var size int = blkSize * 10
|
||||
|
||||
// cleartext
|
||||
bigbuf := make([]byte, size)
|
||||
for i := 0; i < len(bigbuf); i++ {
|
||||
bigbuf[i] = byte(i & 0xff)
|
||||
}
|
||||
|
||||
for i := 1; i < len(bigbuf); i++ {
|
||||
buf := bigbuf[:i]
|
||||
t.Logf("small-size-stream: size %d, chunksize %d\n", i, blkSize)
|
||||
|
||||
ee, err := NewEncryptor(nil, uint64(blkSize))
|
||||
assert(err == nil, "encryptor create fail: %s", err)
|
||||
|
||||
err = ee.AddRecipient(receiver.PublicKey())
|
||||
assert(err == nil, "can't add recipient: %s", err)
|
||||
|
||||
wr := Buffer{}
|
||||
wio, err := ee.NewStreamWriter(&wr)
|
||||
assert(err == nil, "can't start stream writer: %s", err)
|
||||
|
||||
// chunksize for writing to stream
|
||||
csize := blkSize - 1
|
||||
rbuf := buf
|
||||
for len(rbuf) > 0 {
|
||||
m := csize
|
||||
if len(rbuf) < m {
|
||||
m = len(rbuf)
|
||||
}
|
||||
|
||||
n, err := wio.Write(rbuf[:m])
|
||||
assert(err == nil, "stream write failed: %s", err)
|
||||
assert(n == m, "stream write mismatch: exp %d, saw %d", m, n)
|
||||
|
||||
rbuf = rbuf[m:]
|
||||
}
|
||||
err = wio.Close()
|
||||
assert(err == nil, "stream close failed: %s", err)
|
||||
|
||||
_, err = wio.Write(buf[:csize])
|
||||
assert(err != nil, "stream write accepted I/O after close: %s", err)
|
||||
|
||||
rd := bytes.NewBuffer(wr.Bytes())
|
||||
|
||||
dd, err := NewDecryptor(rd)
|
||||
assert(err == nil, "decryptor create fail: %s", err)
|
||||
|
||||
err = dd.SetPrivateKey(receiver, nil)
|
||||
assert(err == nil, "decryptor can't add SK: %s", err)
|
||||
|
||||
rio, err := dd.NewStreamReader()
|
||||
assert(err == nil, "stream reader failed: %s", err)
|
||||
|
||||
rbuf = make([]byte, csize)
|
||||
wr = Buffer{}
|
||||
n := 0
|
||||
for {
|
||||
m, err := rio.Read(rbuf)
|
||||
assert(err == nil || err == io.EOF, "streamread fail: %s", err)
|
||||
|
||||
if m > 0 {
|
||||
wr.Write(rbuf[:m])
|
||||
n += m
|
||||
}
|
||||
if err == io.EOF || m == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
b := wr.Bytes()
|
||||
assert(n == len(b), "streamread: bad buflen; exp %d, saw %d", n, len(b))
|
||||
assert(n == len(buf), "streamread: decrypt len mismatch; exp %d, saw %d", len(buf), n)
|
||||
|
||||
assert(byteEq(b, buf), "decrypt content mismatch")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func randint() int {
|
||||
var b [4]byte
|
||||
|
||||
_, err := io.ReadFull(rand.Reader, b[:])
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("can't read 4 rand bytes: %s", err))
|
||||
}
|
||||
|
||||
u := binary.BigEndian.Uint32(b[:])
|
||||
|
||||
return int(u & 0x7fffffff)
|
||||
}
|
||||
|
||||
func randmod(m int) int {
|
||||
return randint() % m
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
// errors.go - list of all exportable errors in this module
|
||||
//
|
||||
// (c) 2016 Sudhi Herle <sudhi@herle.net>
|
||||
//
|
||||
// Licensing Terms: GPLv2
|
||||
//
|
||||
// If you need a commercial license for this work, please contact
|
||||
// the author.
|
||||
//
|
||||
// This software does not come with any express or implied
|
||||
// warranty; it is provided "as is". No claim is made to its
|
||||
// suitability for any purpose.
|
||||
//
|
||||
|
||||
package sign
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrClosed = errors.New("encrypt: stream already closed")
|
||||
ErrNoKey = errors.New("decrypt: no private key set for decryption")
|
||||
ErrEncStarted = errors.New("encrypt: can't add new recipient after encryption has started")
|
||||
ErrDecStarted = errors.New("decrypt: can't add new recipient after decryption has started")
|
||||
ErrEncIsStream = errors.New("encrypt: can't use Encrypt() after using streaming I/O")
|
||||
ErrNotSigTool = errors.New("decrypt: not a sigtool encrypted file?")
|
||||
ErrHeaderTooBig = errors.New("decrypt: header too large (max 1048576)")
|
||||
ErrHeaderTooSmall = errors.New("decrypt: header too small (min 32)")
|
||||
ErrBadHeader = errors.New("decrypt: header corrupted")
|
||||
ErrNoWrappedKeys = errors.New("decrypt: no wrapped keys in encrypted file")
|
||||
ErrBadKey = errors.New("decrypt: wrong key")
|
||||
ErrBadTrailer = errors.New("decrypt: message integrity failed (bad trailer)")
|
||||
ErrBadSender = errors.New("unwrap: sender verification failed")
|
||||
ErrNoSenderPK = errors.New("unwrap: missing sender public key")
|
||||
|
||||
ErrIncorrectPassword = errors.New("ssh: invalid passphrase")
|
||||
ErrNoPEMFound = errors.New("ssh: no PEM block found")
|
||||
ErrBadPublicKey = errors.New("ssh: malformed public key")
|
||||
ErrKeyTooShort = errors.New("ssh: public key too short")
|
||||
ErrBadTrailers = errors.New("ssh: trailing junk in public key")
|
||||
ErrBadFormat = errors.New("ssh: invalid openssh private key format")
|
||||
ErrBadLength = errors.New("ssh: private key unexpected length")
|
||||
ErrBadPadding = errors.New("ssh: padding not as expected")
|
||||
)
|
|
@ -1,67 +0,0 @@
|
|||
// iomisc.go -- misc i/o functions
|
||||
//
|
||||
// (c) 2016 Sudhi Herle <sudhi@herle.net>
|
||||
//
|
||||
// Licensing Terms: GPLv2
|
||||
//
|
||||
// If you need a commercial license for this work, please contact
|
||||
// the author.
|
||||
//
|
||||
// This software does not come with any express or implied
|
||||
// warranty; it is provided "as is". No claim is made to its
|
||||
// suitability for any purpose.
|
||||
|
||||
package sign
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"github.com/opencoff/go-fio"
|
||||
"github.com/opencoff/go-mmap"
|
||||
"hash"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Simple function to reliably write data to a file.
|
||||
// Does MORE than ioutil.WriteFile() - in that it doesn't trash the
|
||||
// existing file with an incomplete write.
|
||||
func writeFile(fn string, b []byte, ovwrite bool, mode uint32) error {
|
||||
var opts uint32
|
||||
if ovwrite {
|
||||
opts |= fio.OPT_OVERWRITE
|
||||
}
|
||||
sf, err := fio.NewSafeFile(fn, opts, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.FileMode(mode))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer sf.Abort()
|
||||
if _, err = sf.Write(b); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return sf.Close()
|
||||
}
|
||||
|
||||
// Generate file checksum out of hash function h
|
||||
func fileCksum(fn string, h hash.Hash) ([]byte, error) {
|
||||
fd, err := os.Open(fn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't open %s: %s", fn, err)
|
||||
}
|
||||
|
||||
defer fd.Close()
|
||||
|
||||
sz, err := mmap.Reader(fd, func(b []byte) error {
|
||||
h.Write(b)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var b [8]byte
|
||||
binary.BigEndian.PutUint64(b[:], uint64(sz))
|
||||
h.Write(b[:])
|
||||
|
||||
return h.Sum(nil)[:], nil
|
||||
}
|
529
sign/keys.go
529
sign/keys.go
|
@ -1,529 +0,0 @@
|
|||
// keys.go -- Ed25519 keys management
|
||||
//
|
||||
// (c) 2016 Sudhi Herle <sudhi@herle.net>
|
||||
//
|
||||
// Licensing Terms: GPLv2
|
||||
//
|
||||
// If you need a commercial license for this work, please contact
|
||||
// the author.
|
||||
//
|
||||
// This software does not come with any express or implied
|
||||
// warranty; it is provided "as is". No claim is made to its
|
||||
// suitability for any purpose.
|
||||
|
||||
// This file implements:
|
||||
// - key generation, and key I/O
|
||||
// - sign/verify of files and byte strings
|
||||
|
||||
package sign
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
|
||||
Ed "crypto/ed25519"
|
||||
|
||||
"golang.org/x/crypto/scrypt"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Private Ed25519 key
|
||||
type PrivateKey struct {
|
||||
Sk []byte
|
||||
|
||||
// Encryption key: Curve25519 point corresponding to this Ed25519 key
|
||||
ck []byte
|
||||
|
||||
// Cached copy of the public key
|
||||
pk *PublicKey
|
||||
}
|
||||
|
||||
// Public Ed25519 key
|
||||
type PublicKey struct {
|
||||
Pk []byte
|
||||
|
||||
// Comment string
|
||||
Comment string
|
||||
|
||||
// Curve25519 point corresponding to this Ed25519 key
|
||||
ck []byte
|
||||
|
||||
hash []byte
|
||||
}
|
||||
|
||||
// Length of Ed25519 Public Key Hash
|
||||
const PKHashLength = 16
|
||||
|
||||
// constants we use in this module
|
||||
const (
|
||||
// Scrypt parameters
|
||||
_N int = 1 << 19
|
||||
_r int = 8
|
||||
_p int = 1
|
||||
|
||||
// Algorithm used in the encrypted private key
|
||||
sk_algo = "scrypt-sha256"
|
||||
sig_algo = "sha512-ed25519"
|
||||
)
|
||||
|
||||
// Encrypted Private key
|
||||
type serializedPrivKey struct {
|
||||
Comment string `yaml:"comment,omitempty"`
|
||||
|
||||
// Encrypted Sk
|
||||
Esk string `yaml:"esk"`
|
||||
Salt string `yaml:"salt,omitempty"`
|
||||
|
||||
// Algorithm used for checksum and KDF
|
||||
Algo string `yaml:"algo,omitempty"`
|
||||
|
||||
// These are params for scrypt.Key()
|
||||
// CPU Cost parameter; must be a power of 2
|
||||
N int `yaml:"Z,flow,omitempty"`
|
||||
|
||||
// r * p should be less than 2^30
|
||||
R int `yaml:"r,flow,omitempty"`
|
||||
P int `yaml:"p,flow,omitempty"`
|
||||
}
|
||||
|
||||
// serialized representation of public key
|
||||
type serializedPubKey struct {
|
||||
Comment string `yaml:"comment,omitempty"`
|
||||
Pk string `yaml:"pk"`
|
||||
Hash string `yaml:"hash"`
|
||||
}
|
||||
|
||||
// Serialized signature
|
||||
type signature struct {
|
||||
Comment string `yaml:"comment,omitempty"`
|
||||
Pkhash string `yaml:"pkhash,omitempty"`
|
||||
Signature string `yaml:"signature"`
|
||||
}
|
||||
|
||||
// given a public key, generate a deterministic short-hash of it.
|
||||
func pkhash(pk []byte) []byte {
|
||||
z := sha256.Sum256(pk)
|
||||
return z[:PKHashLength]
|
||||
}
|
||||
|
||||
// NewPrivateKey generates a new Ed25519 private key
|
||||
func NewPrivateKey() (*PrivateKey, error) {
|
||||
pkb, skb, err := Ed.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sk := &PrivateKey{
|
||||
Sk: []byte(skb),
|
||||
pk: &PublicKey{
|
||||
Pk: []byte(pkb),
|
||||
hash: pkhash([]byte(pkb)),
|
||||
},
|
||||
}
|
||||
return sk, nil
|
||||
}
|
||||
|
||||
// Read the private key in 'fn', optionally decrypting it using
|
||||
// password 'pw' and create new instance of PrivateKey
|
||||
func ReadPrivateKey(fn string, getpw func() ([]byte, error)) (*PrivateKey, error) {
|
||||
yml, err := ioutil.ReadFile(fn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var sk PrivateKey
|
||||
if err = sk.UnmarshalBinary(yml, getpw); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &sk, nil
|
||||
}
|
||||
|
||||
// Make a private key from bytes 'yml' using optional caller provided
|
||||
// getpw() function to read the password if needed.
|
||||
// are assumed to be serialized version of the private key.
|
||||
func MakePrivateKey(yml []byte, getpw func() ([]byte, error)) (*PrivateKey, error) {
|
||||
var sk PrivateKey
|
||||
|
||||
err := sk.UnmarshalBinary(yml, getpw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &sk, nil
|
||||
}
|
||||
|
||||
// make a PrivateKey from a byte array containing ed25519 raw SK
|
||||
func makePrivateKeyFromBytes(sk *PrivateKey, buf []byte) error {
|
||||
if len(buf) != 64 {
|
||||
return fmt.Errorf("private key is malformed (len %d!)", len(buf))
|
||||
}
|
||||
|
||||
skb := make([]byte, 64)
|
||||
copy(skb, buf)
|
||||
|
||||
edsk := Ed.PrivateKey(skb)
|
||||
edpk := edsk.Public().(Ed.PublicKey)
|
||||
|
||||
pk := &PublicKey{
|
||||
Pk: []byte(edpk),
|
||||
hash: pkhash([]byte(edpk)),
|
||||
}
|
||||
sk.Sk = skb
|
||||
sk.pk = pk
|
||||
return nil
|
||||
}
|
||||
|
||||
// Make a private key from 64-bytes of extended Ed25519 key
|
||||
func PrivateKeyFromBytes(buf []byte) (*PrivateKey, error) {
|
||||
var sk PrivateKey
|
||||
if err := makePrivateKeyFromBytes(&sk, buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &sk, nil
|
||||
}
|
||||
|
||||
// Given a secret key, return the corresponding Public Key
|
||||
func (sk *PrivateKey) PublicKey() *PublicKey {
|
||||
return sk.pk
|
||||
}
|
||||
|
||||
// Convert an Ed25519 Private Key to Curve25519 Private key
|
||||
func (sk *PrivateKey) ToCurve25519SK() []byte {
|
||||
if sk.ck == nil {
|
||||
var ek [64]byte
|
||||
|
||||
h := sha512.New()
|
||||
h.Write(sk.Sk[:32])
|
||||
h.Sum(ek[:0])
|
||||
|
||||
sk.ck = clamp(ek[:32])
|
||||
}
|
||||
|
||||
return sk.ck
|
||||
}
|
||||
|
||||
// Serialize the private key to file 'fn' using human readable
|
||||
// 'comment' and encrypt the key with supplied passphrase 'pw'.
|
||||
func (sk *PrivateKey) Serialize(fn, comment string, ovwrite bool, pw []byte) error {
|
||||
b, err := sk.MarshalBinary(comment, pw)
|
||||
if err == nil {
|
||||
return writeFile(fn, b, ovwrite, 0600)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// MarshalBinary marshals the private key with a caller provided
|
||||
// passphrase 'pw' and human readable 'comment'
|
||||
func (sk *PrivateKey) MarshalBinary(comment string, pw []byte) ([]byte, error) {
|
||||
// expand the password into 64 bytes
|
||||
pass := sha512.Sum512(pw)
|
||||
salt := make([]byte, 32)
|
||||
|
||||
randRead(salt)
|
||||
|
||||
// "32" == Length of AES-256 key
|
||||
key, err := scrypt.Key(pass[:], salt, _N, _r, _p, 32)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshal: can't derive scrypt key: %s", err)
|
||||
}
|
||||
|
||||
aes, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshal: %s", err)
|
||||
}
|
||||
|
||||
ae, err := cipher.NewGCM(aes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshal: %s", err)
|
||||
}
|
||||
|
||||
tl := ae.Overhead()
|
||||
buf := make([]byte, tl+len(sk.Sk))
|
||||
esk := ae.Seal(buf[:0], salt[:ae.NonceSize()], sk.Sk, nil)
|
||||
|
||||
enc := base64.StdEncoding.EncodeToString
|
||||
|
||||
ssk := serializedPrivKey{
|
||||
Comment: comment,
|
||||
Esk: enc(esk),
|
||||
Salt: enc(salt),
|
||||
Algo: sk_algo,
|
||||
N: _N,
|
||||
R: _r,
|
||||
P: _p,
|
||||
}
|
||||
|
||||
// We won't protect the Scrypt parameters with the hash above
|
||||
// because it is not needed. If the parameters are wrong, the
|
||||
// derived key will be wrong and thus, the hash will not match.
|
||||
|
||||
return yaml.Marshal(&ssk)
|
||||
}
|
||||
|
||||
// UnmarshalBinary unmarshals the private key and optionally invokes the
|
||||
// caller provided getpw() function to read the password if needed. If the
|
||||
// input byte stream 'b' is an OpenSSH ed25519 key, this function transparently
|
||||
// decodes it.
|
||||
func (sk *PrivateKey) UnmarshalBinary(b []byte, getpw func() ([]byte, error)) error {
|
||||
if bytes.Index(b, []byte("OPENSSH PRIVATE KEY-")) > 0 {
|
||||
xk, err := parseSSHPrivateKey(b, getpw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*sk = *xk
|
||||
return nil
|
||||
}
|
||||
|
||||
var pw []byte
|
||||
if getpw != nil {
|
||||
var err error
|
||||
pw, err = getpw()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// We take short passwords and extend them
|
||||
pwb := sha512.Sum512(pw)
|
||||
|
||||
var ssk serializedPrivKey
|
||||
|
||||
err := yaml.Unmarshal(b, &ssk)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshal priv key: can't parse YAML: %s", err)
|
||||
}
|
||||
|
||||
if len(ssk.Salt) == 0 || len(ssk.Esk) == 0 {
|
||||
return fmt.Errorf("unmarshal priv key: not YAML format")
|
||||
}
|
||||
|
||||
b64 := base64.StdEncoding.DecodeString
|
||||
|
||||
salt, err := b64(ssk.Salt)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshal priv key: can't decode salt: %s", err)
|
||||
}
|
||||
|
||||
esk, err := b64(ssk.Esk)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshal priv key: can't decode key: %s", err)
|
||||
}
|
||||
|
||||
// "32" == Length of AES-256 key
|
||||
key, err := scrypt.Key(pwb[:], salt, ssk.N, ssk.R, ssk.P, 32)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshal priv key: can't derive key: %s", err)
|
||||
}
|
||||
|
||||
aes, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshal priv key: aes failure: %s", err)
|
||||
}
|
||||
|
||||
ae, err := cipher.NewGCM(aes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshal priv key: aes failure: %s", err)
|
||||
}
|
||||
|
||||
skb := make([]byte, 64)
|
||||
skb, err = ae.Open(skb[:0], salt[:ae.NonceSize()], esk, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshal priv key: wrong password")
|
||||
}
|
||||
|
||||
return makePrivateKeyFromBytes(sk, skb)
|
||||
}
|
||||
|
||||
// --- Public Key Methods ---
|
||||
|
||||
// Read the public key from 'fn' and create new instance of
|
||||
// PublicKey
|
||||
func ReadPublicKey(fn string) (*PublicKey, error) {
|
||||
var err error
|
||||
var yml []byte
|
||||
|
||||
if yml, err = ioutil.ReadFile(fn); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var pk PublicKey
|
||||
if err = pk.UnmarshalBinary(yml); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pk, nil
|
||||
}
|
||||
|
||||
// Parse a serialized public in 'yml' and return the resulting
|
||||
// public key instance
|
||||
func MakePublicKey(yml []byte) (*PublicKey, error) {
|
||||
var pk PublicKey
|
||||
if err := pk.UnmarshalBinary(yml); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pk, nil
|
||||
}
|
||||
|
||||
// Make a public key from a string
|
||||
func MakePublicKeyFromString(s string) (*PublicKey, error) {
|
||||
// first try to decode it as a openssh key
|
||||
if pk2, err := parseEncPubKey([]byte(s), "command-line-pk"); err == nil {
|
||||
return pk2, nil
|
||||
}
|
||||
|
||||
// Now try to decode as an sigtool key
|
||||
b64 := base64.StdEncoding.DecodeString
|
||||
|
||||
pkb, err := b64(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var pk PublicKey
|
||||
err = makePublicKeyFromBytes(&pk, pkb)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pk, nil
|
||||
}
|
||||
|
||||
func makePublicKeyFromBytes(pk *PublicKey, b []byte) error {
|
||||
if len(b) != 32 {
|
||||
return fmt.Errorf("public key is malformed (len %d!)", len(b))
|
||||
}
|
||||
|
||||
pk.Pk = make([]byte, 32)
|
||||
pk.hash = pkhash(b)
|
||||
copy(pk.Pk, b)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Make a public key from a byte string
|
||||
func PublicKeyFromBytes(b []byte) (*PublicKey, error) {
|
||||
var pk PublicKey
|
||||
if err := makePublicKeyFromBytes(&pk, b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pk, nil
|
||||
}
|
||||
|
||||
// Serialize a PublicKey into file 'fn' with a human readable 'comment'.
|
||||
// If 'ovwrite' is true, overwrite the file if it exists.
|
||||
func (pk *PublicKey) Serialize(fn, comment string, ovwrite bool) error {
|
||||
out, err := pk.MarshalBinary(comment)
|
||||
if err == nil {
|
||||
return writeFile(fn, out, ovwrite, 0644)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// from github.com/FiloSottile/age
|
||||
var curve25519P, _ = new(big.Int).SetString("57896044618658097711785492504343953926634992332820282019728792003956564819949", 10)
|
||||
|
||||
// Convert an Ed25519 Public Key to Curve25519 public key
|
||||
// from github.com/FiloSottile/age
|
||||
func (pk *PublicKey) ToCurve25519PK() []byte {
|
||||
if pk.ck != nil {
|
||||
return pk.ck
|
||||
}
|
||||
|
||||
// ed25519.PublicKey is a little endian representation of the y-coordinate,
|
||||
// with the most significant bit set based on the sign of the x-ccordinate.
|
||||
bigEndianY := make([]byte, Ed.PublicKeySize)
|
||||
for i, b := range pk.Pk {
|
||||
bigEndianY[Ed.PublicKeySize-i-1] = b
|
||||
}
|
||||
bigEndianY[0] &= 0b0111_1111
|
||||
|
||||
// The Montgomery u-coordinate is derived through the bilinear map
|
||||
//
|
||||
// u = (1 + y) / (1 - y)
|
||||
//
|
||||
// See https://blog.filippo.io/using-ed25519-keys-for-encryption.
|
||||
y := new(big.Int).SetBytes(bigEndianY)
|
||||
denom := big.NewInt(1)
|
||||
denom.ModInverse(denom.Sub(denom, y), curve25519P) // 1 / (1 - y)
|
||||
u := y.Mul(y.Add(y, big.NewInt(1)), denom)
|
||||
u.Mod(u, curve25519P)
|
||||
|
||||
out := make([]byte, 32)
|
||||
uBytes := u.Bytes()
|
||||
n := len(uBytes)
|
||||
for i, b := range uBytes {
|
||||
out[n-i-1] = b
|
||||
}
|
||||
|
||||
pk.ck = out
|
||||
return out
|
||||
}
|
||||
|
||||
// Public Key Hash
|
||||
func (pk *PublicKey) Hash() []byte {
|
||||
return pk.hash
|
||||
}
|
||||
|
||||
// MarshalBinary marshals a PublicKey into a byte array
|
||||
func (pk *PublicKey) MarshalBinary(comment string) ([]byte, error) {
|
||||
b64 := base64.StdEncoding.EncodeToString
|
||||
spk := &serializedPubKey{
|
||||
Comment: comment,
|
||||
Pk: b64(pk.Pk),
|
||||
Hash: b64(pk.hash),
|
||||
}
|
||||
|
||||
return yaml.Marshal(spk)
|
||||
}
|
||||
|
||||
// UnmarshalBinary constructs a PublicKey from a previously
|
||||
// marshaled byte stream instance. In addition, it is also
|
||||
// capable of parsing an OpenSSH ed25519 public key.
|
||||
func (pk *PublicKey) UnmarshalBinary(yml []byte) error {
|
||||
|
||||
// first try to parse as a ssh key
|
||||
if xk, err := parseSSHPublicKey(yml); err == nil {
|
||||
*pk = *xk
|
||||
return nil
|
||||
}
|
||||
|
||||
// OK Yaml it is.
|
||||
|
||||
var spk serializedPubKey
|
||||
var err error
|
||||
|
||||
if err = yaml.Unmarshal(yml, &spk); err != nil {
|
||||
return fmt.Errorf("can't parse YAML: %s", err)
|
||||
}
|
||||
|
||||
if len(spk.Pk) == 0 {
|
||||
return fmt.Errorf("sign: not a YAML public key")
|
||||
}
|
||||
|
||||
b64 := base64.StdEncoding.DecodeString
|
||||
var pkb []byte
|
||||
|
||||
if pkb, err = b64(spk.Pk); err != nil {
|
||||
return fmt.Errorf("can't decode YAML:Pk: %s", err)
|
||||
}
|
||||
|
||||
return makePublicKeyFromBytes(pk, pkb)
|
||||
}
|
||||
|
||||
// -- Internal Utility Functions --
|
||||
|
||||
func clamp(k []byte) []byte {
|
||||
k[0] &= 248
|
||||
k[31] &= 127
|
||||
k[31] |= 64
|
||||
return k
|
||||
}
|
||||
|
||||
// EOF
|
||||
// vim: noexpandtab:ts=8:sw=8:tw=92:
|
45
sign/rand.go
45
sign/rand.go
|
@ -1,45 +0,0 @@
|
|||
// rand.go - utility functions to generate random quantities
|
||||
//
|
||||
// (c) 2018 Sudhi Herle <sudhi@herle.net>
|
||||
//
|
||||
// Licensing Terms: GPLv2
|
||||
//
|
||||
// If you need a commercial license for this work, please contact
|
||||
// the author.
|
||||
//
|
||||
// This software does not come with any express or implied
|
||||
// warranty; it is provided "as is". No claim is made to its
|
||||
// suitability for any purpose.
|
||||
|
||||
package sign
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
func randu32() uint32 {
|
||||
var b [4]byte
|
||||
|
||||
_, err := io.ReadFull(rand.Reader, b[:])
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("can't read 4 rand bytes: %s", err))
|
||||
}
|
||||
|
||||
return binary.LittleEndian.Uint32(b[:])
|
||||
}
|
||||
|
||||
func randRead(b []byte) []byte {
|
||||
_, err := io.ReadFull(rand.Reader, b)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("can't read %d bytes of random data: %s", len(b), err))
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func randBuf(sz int) []byte {
|
||||
b := make([]byte, sz)
|
||||
return randRead(b)
|
||||
}
|
460
sign/sign.go
460
sign/sign.go
|
@ -11,56 +11,309 @@
|
|||
// warranty; it is provided "as is". No claim is made to its
|
||||
// suitability for any purpose.
|
||||
|
||||
// This file implements:
|
||||
// - key generation, and key I/O
|
||||
// - sign/verify of files and byte strings
|
||||
|
||||
// Package sign implements Ed25519 signing, verification on files.
|
||||
// It builds upon golang.org/x/crypto/ed25519 by adding methods
|
||||
// for serializing and deserializing Ed25519 private & public keys.
|
||||
// In addition, it works with large files - by precalculating their
|
||||
// SHA512 checksum in mmap'd mode and sending the 64 byte signature
|
||||
// for Ed25519 signing.
|
||||
package sign
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"crypto/subtle"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
Ed "crypto/ed25519"
|
||||
Ed "golang.org/x/crypto/ed25519"
|
||||
"golang.org/x/crypto/scrypt"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
"github.com/opencoff/go-utils"
|
||||
)
|
||||
|
||||
// Private Ed25519 key
|
||||
type PrivateKey struct {
|
||||
Sk []byte
|
||||
|
||||
// Cached copy of the public key
|
||||
// In reality, it is a pointer to Sk[32:]
|
||||
pk []byte
|
||||
}
|
||||
|
||||
// Public Ed25519 key
|
||||
type PublicKey struct {
|
||||
Pk []byte
|
||||
}
|
||||
|
||||
// Ed25519 key pair
|
||||
type Keypair struct {
|
||||
Sec PrivateKey
|
||||
Pub PublicKey
|
||||
}
|
||||
|
||||
// An Ed25519 Signature
|
||||
type Signature struct {
|
||||
Sig []byte // Ed25519 sig bytes
|
||||
Sig []byte // 32 byte digital signature
|
||||
pkhash []byte // [0:16] SHA256 hash of public key needed for verification
|
||||
}
|
||||
|
||||
// Algorithm used in the encrypted private key
|
||||
const sk_algo = "scrypt-sha256"
|
||||
const sig_algo = "sha512-ed25519"
|
||||
|
||||
// Scrypt parameters
|
||||
const _N = 1 << 17
|
||||
const _r = 16
|
||||
const _p = 1
|
||||
|
||||
// Encrypted Private key
|
||||
type encPrivKey struct {
|
||||
// Encrypted Sk
|
||||
Esk []byte
|
||||
|
||||
// parameters for Sk serialization
|
||||
Salt []byte
|
||||
|
||||
// Algorithm used for checksum and KDF
|
||||
Algo string
|
||||
|
||||
// Checksum to verify passphrase before we xor it
|
||||
Verify []byte
|
||||
|
||||
// These are params for scrypt.Key()
|
||||
// CPU Cost parameter; must be a power of 2
|
||||
N uint32
|
||||
// r * p should be less than 2^30
|
||||
r uint32
|
||||
p uint32
|
||||
}
|
||||
|
||||
// Serialized representation of private key
|
||||
type serializedPrivKey struct {
|
||||
Comment string `yaml:"comment,omitempty"`
|
||||
Esk string `yaml:"esk"`
|
||||
Salt string `yaml:"salt,omitempty"`
|
||||
Algo string `yaml:"algo,omitempty"`
|
||||
Verify string `yaml:"verify,omitempty"`
|
||||
N uint32 `yaml:"Z,flow,omitempty"`
|
||||
R uint32 `yaml:"r,flow,omitempty"`
|
||||
P uint32 `yaml:"p,flow,omitempty"`
|
||||
}
|
||||
|
||||
// serialized representation of public key
|
||||
type serializedPubKey struct {
|
||||
Comment string `yaml:"comment,omitempty"`
|
||||
Pk string `yaml:"pk"`
|
||||
}
|
||||
|
||||
// Serialized signature
|
||||
type signature struct {
|
||||
Comment string `yaml:"comment,omitempty"`
|
||||
Pkhash string `yaml:"pkhash,omitempty"`
|
||||
Signature string `yaml:"signature"`
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
p, s, err := Ed.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Can't generate Ed25519 keys: %s", err)
|
||||
}
|
||||
|
||||
pk.Pk = []byte(p)
|
||||
sk.Sk = []byte(s)
|
||||
|
||||
return kp, nil
|
||||
}
|
||||
|
||||
// Serialize the keypair to two separate files. The basename of the
|
||||
// file is 'bn'; the public key goes in $bn.pub and the private key
|
||||
// goes in $bn.key.
|
||||
// If password is non-empty, then the private key is encrypted
|
||||
// before writing to disk.
|
||||
func (kp *Keypair) Serialize(bn, comment string, pw string) error {
|
||||
|
||||
sk := &kp.Sec
|
||||
pk := &kp.Pub
|
||||
|
||||
skf := fmt.Sprintf("%s.key", bn)
|
||||
pkf := fmt.Sprintf("%s.pub", bn)
|
||||
|
||||
err := pk.serialize(pkf, comment)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Can't serialize to %s: %s", pkf, err)
|
||||
}
|
||||
|
||||
err = sk.serialize(skf, comment, pw)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Can't serialize to %s: %s", pkf, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read the private key in 'fn', optionally decrypting it using
|
||||
// password 'pw' and create new instance of PrivateKey
|
||||
func ReadPrivateKey(fn string, pw string) (*PrivateKey, error) {
|
||||
yml, err := ioutil.ReadFile(fn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return MakePrivateKey(yml, pw)
|
||||
}
|
||||
|
||||
// Make a private key from bytes 'yml' and password 'pw'. The bytes
|
||||
// are assumed to be serialized version of the private key.
|
||||
func MakePrivateKey(yml []byte, pw string) (*PrivateKey, error) {
|
||||
var ssk serializedPrivKey
|
||||
|
||||
err := yaml.Unmarshal(yml, &ssk)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't parse YAML: %s", err)
|
||||
}
|
||||
|
||||
esk := &encPrivKey{N: ssk.N, r: ssk.R, p: ssk.P, Algo: ssk.Algo}
|
||||
b64 := base64.StdEncoding.DecodeString
|
||||
|
||||
esk.Esk, err = b64(ssk.Esk)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't decode YAML:Esk: %s", err)
|
||||
}
|
||||
|
||||
esk.Salt, err = b64(ssk.Salt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't decode YAML:Salt: %s", err)
|
||||
}
|
||||
|
||||
esk.Verify, err = b64(ssk.Verify)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't decode YAML:Verify: %s", err)
|
||||
}
|
||||
|
||||
sk := &PrivateKey{}
|
||||
|
||||
// We take short passwords and extend them
|
||||
pwb := sha512.Sum512([]byte(pw))
|
||||
|
||||
xork, err := scrypt.Key(pwb[:], esk.Salt, int(esk.N), int(esk.r), int(esk.p), len(esk.Esk))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't derive key: %s", err)
|
||||
}
|
||||
|
||||
hh := sha256.New()
|
||||
hh.Write(esk.Salt)
|
||||
hh.Write(xork)
|
||||
ck := hh.Sum(nil)
|
||||
|
||||
if subtle.ConstantTimeCompare(esk.Verify, ck) != 1 {
|
||||
return nil, fmt.Errorf("incorrect private key password")
|
||||
}
|
||||
|
||||
// Everything works. Now, decode the key
|
||||
sk.Sk = make([]byte, len(esk.Esk))
|
||||
for i := 0; i < len(esk.Esk); i++ {
|
||||
sk.Sk[i] = esk.Esk[i] ^ xork[i]
|
||||
}
|
||||
|
||||
return sk, nil
|
||||
}
|
||||
|
||||
// Serialize the private key to a file
|
||||
// Format: YAML
|
||||
// All []byte are in base64 (RawEncoding)
|
||||
func (sk *PrivateKey) serialize(fn, comment string, pw string) error {
|
||||
|
||||
b64 := base64.StdEncoding.EncodeToString
|
||||
esk := &encPrivKey{}
|
||||
ssk := &serializedPrivKey{Comment: comment}
|
||||
|
||||
// Even with an empty password, we still encrypt and store.
|
||||
|
||||
// expand the password into 64 bytes
|
||||
pwb := sha512.Sum512([]byte(pw))
|
||||
|
||||
esk.N = _N
|
||||
esk.r = _r
|
||||
esk.p = _p
|
||||
|
||||
esk.Salt = make([]byte, 32)
|
||||
esk.Esk = make([]byte, len(sk.Sk))
|
||||
|
||||
_, err := rand.Read(esk.Salt)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Can't read random salt: %s", err)
|
||||
}
|
||||
|
||||
xork, err := scrypt.Key(pwb[:], esk.Salt, int(esk.N), int(esk.r), int(esk.p), len(sk.Sk))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Can't derive scrypt key: %s", err)
|
||||
}
|
||||
|
||||
hh := sha256.New()
|
||||
hh.Write(esk.Salt)
|
||||
hh.Write(xork)
|
||||
esk.Verify = hh.Sum(nil)
|
||||
|
||||
// We won't protect the Scrypt parameters with the hash above
|
||||
// because it is not needed. If the parameters are wrong, the
|
||||
// derived key will be wrong and thus, the hash will not match.
|
||||
|
||||
esk.Algo = sk_algo // global var
|
||||
|
||||
// Finally setup the encrypted key
|
||||
for i := 0; i < len(sk.Sk); i++ {
|
||||
esk.Esk[i] = sk.Sk[i] ^ xork[i]
|
||||
}
|
||||
|
||||
ssk.Esk = b64(esk.Esk)
|
||||
ssk.Salt = b64(esk.Salt)
|
||||
ssk.Verify = b64(esk.Verify)
|
||||
ssk.Algo = esk.Algo
|
||||
ssk.N = esk.N
|
||||
ssk.R = esk.r
|
||||
ssk.P = esk.p
|
||||
|
||||
out, err := yaml.Marshal(ssk)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't marahal to YAML: %s", err)
|
||||
}
|
||||
|
||||
return writeFile(fn, out, 0600)
|
||||
}
|
||||
|
||||
// Sign a prehashed Message; return the signature as opaque bytes
|
||||
// Signature is an YAML file:
|
||||
//
|
||||
// Comment: source file path
|
||||
// Signature: Ed25519 signature
|
||||
func (sk *PrivateKey) SignMessage(ck []byte, comment string) (*Signature, error) {
|
||||
h := sha512.New()
|
||||
h.Write([]byte("sigtool signed message"))
|
||||
h.Write(ck)
|
||||
ck = h.Sum(nil)[:]
|
||||
|
||||
x := Ed.PrivateKey(sk.Sk)
|
||||
|
||||
sig, err := x.Sign(rand.Reader, ck, crypto.Hash(0))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't sign %x: %s", ck, err)
|
||||
}
|
||||
|
||||
ss := &Signature{
|
||||
Sig: sig,
|
||||
pkhash: make([]byte, len(sk.pk.hash)),
|
||||
}
|
||||
esk := Ed.PrivateKey(sk.Sk) // type cast
|
||||
epk := esk.Public() // interface
|
||||
xpk := epk.(Ed.PublicKey) // type assertion
|
||||
pk := []byte(xpk) // cast
|
||||
pkh := sha256.Sum256(pk)
|
||||
|
||||
copy(ss.pkhash, sk.pk.hash)
|
||||
return ss, nil
|
||||
return &Signature{Sig: sig, pkhash: pkh[:16]}, nil
|
||||
}
|
||||
|
||||
// Read and sign a file
|
||||
|
@ -88,13 +341,12 @@ func ReadSignature(fn string) (*Signature, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
var sig Signature
|
||||
return makeSignature(&sig, yml)
|
||||
return MakeSignature(yml)
|
||||
}
|
||||
|
||||
// Parse serialized signature from bytes 'b' and construct a
|
||||
// Signature object
|
||||
func makeSignature(sig *Signature, b []byte) (*Signature, error) {
|
||||
func MakeSignature(b []byte) (*Signature, error) {
|
||||
var ss signature
|
||||
err := yaml.Unmarshal(b, &ss)
|
||||
if err != nil {
|
||||
|
@ -113,33 +365,29 @@ func makeSignature(sig *Signature, b []byte) (*Signature, error) {
|
|||
return nil, fmt.Errorf("can't decode Base64:Pkhash <%s>: %s", ss.Pkhash, err)
|
||||
}
|
||||
|
||||
sig.Sig = s
|
||||
sig.pkhash = p
|
||||
return sig, nil
|
||||
return &Signature{Sig: s, pkhash: p}, nil
|
||||
}
|
||||
|
||||
// MarshalBinary marshals a signature into a byte stream with
|
||||
// an optional caller supplied comment.
|
||||
func (sig *Signature) MarshalBinary(comment string) ([]byte, error) {
|
||||
// Serialize a signature suitable for storing in durable media
|
||||
func (sig *Signature) Serialize(comment string) ([]byte, error) {
|
||||
|
||||
sigs := base64.StdEncoding.EncodeToString(sig.Sig)
|
||||
pks := base64.StdEncoding.EncodeToString(sig.pkhash)
|
||||
ss := &signature{Comment: comment, Pkhash: pks, Signature: sigs}
|
||||
|
||||
return yaml.Marshal(ss)
|
||||
out, err := yaml.Marshal(ss)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't marshal signature of %x to YAML: %s", sig.Sig, err)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary constructs a Signature from a previously
|
||||
// serialized bytestream
|
||||
func (sig *Signature) UnmarshalBinary(b []byte) error {
|
||||
_, err := makeSignature(sig, b)
|
||||
return err
|
||||
}
|
||||
|
||||
// Serialize a signature suitable for storing in durable media
|
||||
func (sig *Signature) Serialize(fn, comment string, ovwrite bool) error {
|
||||
b, err := sig.MarshalBinary(comment)
|
||||
// SerializeFile serializes the signature to an output file 'f'
|
||||
func (sig *Signature) SerializeFile(fn, comment string) error {
|
||||
b, err := sig.Serialize(comment)
|
||||
if err == nil {
|
||||
err = writeFile(fn, b, ovwrite, 0644)
|
||||
err = writeFile(fn, b, 0644)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
@ -148,30 +396,146 @@ func (sig *Signature) Serialize(fn, comment string, ovwrite bool) error {
|
|||
// the signature. It does this by comparing the hash of 'pk' against
|
||||
// 'Pkhash' of 'sig'.
|
||||
func (sig *Signature) IsPKMatch(pk *PublicKey) bool {
|
||||
return subtle.ConstantTimeCompare(pk.hash, sig.pkhash) == 1
|
||||
h := sha256.Sum256(pk.Pk)
|
||||
return subtle.ConstantTimeCompare(h[:16], sig.pkhash) == 1
|
||||
}
|
||||
|
||||
// --- Public Key Methods ---
|
||||
|
||||
// Read the public key from 'fn' and create new instance of
|
||||
// PublicKey
|
||||
func ReadPublicKey(fn string) (*PublicKey, error) {
|
||||
var err error
|
||||
var yml []byte
|
||||
|
||||
if yml, err = ioutil.ReadFile(fn); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return MakePublicKey(yml)
|
||||
}
|
||||
|
||||
// Parse a serialized public in 'yml' and return the resulting
|
||||
// public key instance
|
||||
func MakePublicKey(yml []byte) (*PublicKey, error) {
|
||||
var spk serializedPubKey
|
||||
var err error
|
||||
|
||||
if err = yaml.Unmarshal(yml, &spk); err != nil {
|
||||
return nil, fmt.Errorf("can't parse YAML: %s", err)
|
||||
}
|
||||
|
||||
pk := &PublicKey{}
|
||||
b64 := base64.StdEncoding.DecodeString
|
||||
|
||||
if pk.Pk, err = b64(spk.Pk); err != nil {
|
||||
return nil, fmt.Errorf("can't decode YAML:Pk: %s", err)
|
||||
}
|
||||
|
||||
// Simple sanity checks
|
||||
if len(pk.Pk) == 0 {
|
||||
return nil, fmt.Errorf("public key data is empty?")
|
||||
}
|
||||
|
||||
return pk, nil
|
||||
}
|
||||
|
||||
// Serialize Public Keys
|
||||
func (pk *PublicKey) serialize(fn, comment string) error {
|
||||
b64 := base64.StdEncoding.EncodeToString
|
||||
spk := &serializedPubKey{Comment: comment}
|
||||
|
||||
spk.Pk = b64(pk.Pk)
|
||||
|
||||
out, err := yaml.Marshal(spk)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Can't marahal to YAML: %s", err)
|
||||
}
|
||||
|
||||
return writeFile(fn, out, 0644)
|
||||
}
|
||||
|
||||
// Verify a signature 'sig' for file 'fn' against public key 'pk'
|
||||
// Return True if signature matches, False otherwise
|
||||
func (pk *PublicKey) VerifyFile(fn string, sig *Signature) (bool, error) {
|
||||
|
||||
ck, err := fileCksum(fn, sha512.New())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return pk.VerifyMessage(ck, sig), nil
|
||||
return pk.VerifyMessage(ck, sig)
|
||||
}
|
||||
|
||||
// Verify a signature 'sig' for a pre-calculated checksum 'ck' against public key 'pk'
|
||||
// Return True if signature matches, False otherwise
|
||||
func (pk *PublicKey) VerifyMessage(ck []byte, sig *Signature) bool {
|
||||
h := sha512.New()
|
||||
h.Write([]byte("sigtool signed message"))
|
||||
h.Write(ck)
|
||||
ck = h.Sum(nil)[:]
|
||||
func (pk *PublicKey) VerifyMessage(ck []byte, sig *Signature) (bool, error) {
|
||||
|
||||
x := Ed.PublicKey(pk.Pk)
|
||||
return Ed.Verify(x, ck, sig.Sig)
|
||||
return Ed.Verify(x, ck, sig.Sig), nil
|
||||
}
|
||||
|
||||
// -- Internal Utility Functions --
|
||||
|
||||
// Unlink a file.
|
||||
func unlink(f string) {
|
||||
st, err := os.Stat(f)
|
||||
if err == nil {
|
||||
if !st.Mode().IsRegular() {
|
||||
panic(fmt.Sprintf("%s can't be unlinked. Not a regular file?", f))
|
||||
}
|
||||
|
||||
os.Remove(f)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Simple function to reliably write data to a file.
|
||||
// Does MORE than ioutil.WriteFile() - in that it doesn't trash the
|
||||
// existing file with an incomplete write.
|
||||
func writeFile(fn string, b []byte, mode uint32) error {
|
||||
tmp := fmt.Sprintf("%s.tmp", fn)
|
||||
unlink(tmp)
|
||||
|
||||
fd, err := os.OpenFile(tmp, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.FileMode(mode))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Can't create file %s: %s", tmp, err)
|
||||
}
|
||||
|
||||
_, err = fd.Write(b)
|
||||
if err != nil {
|
||||
fd.Close()
|
||||
// XXX Do we delete the tmp file?
|
||||
return fmt.Errorf("Can't write %v bytes to %s: %s", len(b), tmp, err)
|
||||
}
|
||||
|
||||
fd.Close() // we ignore close(2) errors; unrecoverable anyway.
|
||||
|
||||
os.Rename(tmp, fn)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Generate file checksum out of hash function h
|
||||
func fileCksum(fn string, h hash.Hash) ([]byte, error) {
|
||||
|
||||
fd, err := os.Open(fn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't open %s: %s", fn, err)
|
||||
}
|
||||
|
||||
defer fd.Close()
|
||||
|
||||
sz, err := utils.MmapReader(fd, 0, 0, h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var b [8]byte
|
||||
binary.BigEndian.PutUint64(b[:], uint64(sz))
|
||||
h.Write(b[:])
|
||||
|
||||
return h.Sum(nil), nil
|
||||
}
|
||||
|
||||
// EOF
|
||||
// vim: noexpandtab:ts=8:sw=8:tw=92:
|
||||
|
|
|
@ -14,13 +14,40 @@
|
|||
package sign
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/subtle"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"testing"
|
||||
// module under test
|
||||
//"github.com/sign"
|
||||
)
|
||||
|
||||
func newAsserter(t *testing.T) func(cond bool, msg string, args ...interface{}) {
|
||||
return func(cond bool, msg string, args ...interface{}) {
|
||||
if cond {
|
||||
return
|
||||
}
|
||||
|
||||
_, file, line, ok := runtime.Caller(1)
|
||||
if !ok {
|
||||
file = "???"
|
||||
line = 0
|
||||
}
|
||||
|
||||
s := fmt.Sprintf(msg, args...)
|
||||
t.Fatalf("%s: %d: Assertion failed: %s\n", file, line, s)
|
||||
}
|
||||
}
|
||||
|
||||
// Return true if two byte arrays are equal
|
||||
func byteEq(x, y []byte) bool {
|
||||
return subtle.ConstantTimeCompare(x, y) == 1
|
||||
}
|
||||
|
||||
// Return a temp dir in a temp-dir
|
||||
func tempdir(t *testing.T) string {
|
||||
assert := newAsserter(t)
|
||||
|
@ -28,7 +55,7 @@ func tempdir(t *testing.T) string {
|
|||
var b [10]byte
|
||||
|
||||
dn := os.TempDir()
|
||||
randRead(b[:])
|
||||
rand.Read(b[:])
|
||||
|
||||
tmp := path.Join(dn, fmt.Sprintf("%x", b[:]))
|
||||
err := os.MkdirAll(tmp, 0755)
|
||||
|
@ -38,22 +65,6 @@ func tempdir(t *testing.T) string {
|
|||
return tmp
|
||||
}
|
||||
|
||||
var fixedPw = []byte("abc")
|
||||
var badPw = []byte("def")
|
||||
var nilPw []byte
|
||||
|
||||
// return a hardcoded password
|
||||
func hardcodedPw() ([]byte, error) {
|
||||
return fixedPw, nil
|
||||
}
|
||||
|
||||
func wrongPw() ([]byte, error) {
|
||||
return badPw, nil
|
||||
}
|
||||
func emptyPw() ([]byte, error) {
|
||||
return nilPw, nil
|
||||
}
|
||||
|
||||
// Return true if file exists, false otherwise
|
||||
func fileExists(fn string) bool {
|
||||
st, err := os.Stat(fn)
|
||||
|
@ -81,109 +92,119 @@ p: 1
|
|||
`
|
||||
|
||||
// #1. Create new key pair, and read them back.
|
||||
func TestSignSimple(t *testing.T) {
|
||||
func Test0(t *testing.T) {
|
||||
assert := newAsserter(t)
|
||||
|
||||
sk, err := NewPrivateKey()
|
||||
assert(err == nil, "NewPrivateKey() fail")
|
||||
kp, err := NewKeypair()
|
||||
assert(err == nil, "NewKeyPair() fail")
|
||||
|
||||
pk := sk.PublicKey()
|
||||
|
||||
dn := t.TempDir()
|
||||
dn := tempdir(t)
|
||||
bn := fmt.Sprintf("%s/t0", dn)
|
||||
|
||||
err = kp.Serialize(bn, "", "abc")
|
||||
assert(err == nil, "keyPair.Serialize() fail")
|
||||
|
||||
pkf := fmt.Sprintf("%s.pub", bn)
|
||||
skf := fmt.Sprintf("%s.key", bn)
|
||||
|
||||
err = pk.Serialize(pkf, "", true)
|
||||
assert(err == nil, "can't serialize pk %s", pkf)
|
||||
|
||||
// try to overwrite
|
||||
err = pk.Serialize(pkf, "", false)
|
||||
assert(err != nil, "pk %s overwritten!", pkf)
|
||||
|
||||
err = sk.Serialize(skf, "", true, fixedPw)
|
||||
assert(err == nil, "can't serialize sk %s", skf)
|
||||
|
||||
err = sk.Serialize(skf, "", false, nilPw)
|
||||
assert(err != nil, "sk %s overwritten!", skf)
|
||||
|
||||
// We must find these two files
|
||||
assert(fileExists(pkf), "missing pkf %s", pkf)
|
||||
assert(fileExists(skf), "missing skf %s", skf)
|
||||
assert(fileExists(pkf), "missing pkf")
|
||||
assert(fileExists(skf), "missing skf")
|
||||
|
||||
npk, err := ReadPublicKey(pkf)
|
||||
// send wrong file and see what happens
|
||||
pk, err := ReadPublicKey(skf)
|
||||
assert(err != nil, "bad PK ReadPK fail")
|
||||
|
||||
pk, err = ReadPublicKey(pkf)
|
||||
assert(err == nil, "ReadPK() fail")
|
||||
|
||||
// send the public key as private key
|
||||
nsk, err := ReadPrivateKey(pkf, emptyPw)
|
||||
assert(err != nil, "bad SK ReadSK fail: %s", err)
|
||||
// -ditto- for Sk
|
||||
sk, err := ReadPrivateKey(pkf, "")
|
||||
assert(err != nil, "bad SK ReadSK fail")
|
||||
|
||||
nsk, err = ReadPrivateKey(skf, emptyPw)
|
||||
assert(err != nil, "ReadSK() worked with empty pw")
|
||||
sk, err = ReadPrivateKey(skf, "")
|
||||
assert(err != nil, "ReadSK() empty pw fail")
|
||||
|
||||
nsk, err = ReadPrivateKey(skf, wrongPw)
|
||||
assert(err != nil, "ReadSK() worked with wrong pw")
|
||||
sk, err = ReadPrivateKey(skf, "abcdef")
|
||||
assert(err != nil, "ReadSK() wrong pw fail")
|
||||
|
||||
badf := fmt.Sprintf("%s/badf.key", dn)
|
||||
err = ioutil.WriteFile(badf, []byte(badsk), 0600)
|
||||
assert(err == nil, "can't write badsk: %s", err)
|
||||
assert(err == nil, "write badsk")
|
||||
|
||||
nsk, err = ReadPrivateKey(badf, hardcodedPw)
|
||||
assert(err != nil, "decoded bad SK")
|
||||
sk, err = ReadPrivateKey(badf, "abc")
|
||||
assert(err != nil, "badsk read fail")
|
||||
|
||||
// Finally, with correct password it should work.
|
||||
nsk, err = ReadPrivateKey(skf, hardcodedPw)
|
||||
assert(err == nil, "ReadSK() correct pw fail: %s", err)
|
||||
sk, err = ReadPrivateKey(skf, "abc")
|
||||
assert(err == nil, "ReadSK() correct pw fail")
|
||||
|
||||
// And, deserialized keys should be identical
|
||||
assert(byteEq(pk.Pk, npk.Pk), "pkbytes unequal")
|
||||
assert(byteEq(sk.Sk, nsk.Sk), "skbytes unequal")
|
||||
assert(byteEq(pk.Pk, kp.Pub.Pk), "pkbytes unequal")
|
||||
assert(byteEq(sk.Sk, kp.Sec.Sk), "skbytes unequal")
|
||||
|
||||
os.RemoveAll(dn)
|
||||
}
|
||||
|
||||
// #2. Create new key pair, sign a rand buffer and verify
|
||||
func TestSignRandBuf(t *testing.T) {
|
||||
func Test1(t *testing.T) {
|
||||
assert := newAsserter(t)
|
||||
|
||||
sk, err := NewPrivateKey()
|
||||
assert(err == nil, "NewPrivateKey() fail: %s", err)
|
||||
kp, err := NewKeypair()
|
||||
assert(err == nil, "NewKeyPair() fail")
|
||||
|
||||
var ck [64]byte // simulates sha512 sum
|
||||
|
||||
randRead(ck[:])
|
||||
rand.Read(ck[:])
|
||||
|
||||
pk := sk.PublicKey()
|
||||
pk := &kp.Pub
|
||||
sk := &kp.Sec
|
||||
|
||||
ss, err := sk.SignMessage(ck[:], "")
|
||||
assert(err == nil, "sk.sign fail: %s", err)
|
||||
assert(err == nil, "sk.sign fail")
|
||||
assert(ss != nil, "sig is null")
|
||||
|
||||
// verify sig
|
||||
assert(ss.IsPKMatch(pk), "pk match fail")
|
||||
|
||||
// Corrupt the pkhash and see
|
||||
randRead(ss.pkhash)
|
||||
rand.Read(ss.pkhash[:])
|
||||
assert(!ss.IsPKMatch(pk), "corrupt pk match fail")
|
||||
|
||||
// Incorrect checksum == should fail verification
|
||||
ok := pk.VerifyMessage(ck[:16], ss)
|
||||
ok, err := pk.VerifyMessage(ck[:16], ss)
|
||||
assert(err == nil, "bad ck verify err fail")
|
||||
assert(!ok, "bad ck verify fail")
|
||||
|
||||
// proper checksum == should work
|
||||
ok = pk.VerifyMessage(ck[:], ss)
|
||||
ok, err = pk.VerifyMessage(ck[:], ss)
|
||||
assert(err == nil, "verify err")
|
||||
assert(ok, "verify fail")
|
||||
|
||||
// Now sign a file
|
||||
dn := t.TempDir()
|
||||
dn := tempdir(t)
|
||||
bn := fmt.Sprintf("%s/k", dn)
|
||||
|
||||
pkf := fmt.Sprintf("%s.pub", bn)
|
||||
skf := fmt.Sprintf("%s.key", bn)
|
||||
|
||||
err = kp.Serialize(bn, "", "")
|
||||
assert(err == nil, "keyPair.Serialize() fail")
|
||||
|
||||
// Now read the private key and sign
|
||||
sk, err = ReadPrivateKey(skf, "")
|
||||
assert(err == nil, "readSK fail")
|
||||
|
||||
pk, err = ReadPublicKey(pkf)
|
||||
assert(err == nil, "ReadPK fail")
|
||||
|
||||
var buf [8192]byte
|
||||
|
||||
zf := fmt.Sprintf("%s/file.dat", dn)
|
||||
fd, err := os.OpenFile(zf, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
assert(err == nil, "file.dat creat file: %s", err)
|
||||
assert(err == nil, "file.dat creat file")
|
||||
|
||||
for i := 0; i < 8; i++ {
|
||||
randRead(buf[:])
|
||||
rand.Read(buf[:])
|
||||
n, err := fd.Write(buf[:])
|
||||
assert(err == nil, fmt.Sprintf("file.dat write fail: %s", err))
|
||||
assert(n == 8192, fmt.Sprintf("file.dat i/o fail: exp 8192 saw %v", n))
|
||||
|
@ -192,31 +213,27 @@ func TestSignRandBuf(t *testing.T) {
|
|||
fd.Close()
|
||||
|
||||
sig, err := sk.SignFile(zf)
|
||||
assert(err == nil, "file.dat sign fail: %s", err)
|
||||
assert(err == nil, "file.dat sign fail")
|
||||
assert(sig != nil, "file.dat sign nil")
|
||||
|
||||
ok, err = pk.VerifyFile(zf, sig)
|
||||
assert(err == nil, "file.dat verify fail: %s", err)
|
||||
assert(err == nil, "file.dat verify fail")
|
||||
assert(ok, "file.dat verify false")
|
||||
|
||||
// Now, serialize the signature and read it back
|
||||
sf := fmt.Sprintf("%s/file.sig", dn)
|
||||
err = sig.Serialize(sf, "", true)
|
||||
assert(err == nil, "sig serialize fail: %s", err)
|
||||
|
||||
// now try to overwrite it
|
||||
err = sig.Serialize(sf, "", false)
|
||||
assert(err != nil, "sig serialize overwrote?!")
|
||||
err = sig.SerializeFile(sf, "")
|
||||
assert(err == nil, "sig serialize fail")
|
||||
|
||||
s2, err := ReadSignature(sf)
|
||||
assert(err == nil, "file.sig read fail: %s", err)
|
||||
assert(err == nil, "file.sig read fail")
|
||||
assert(s2 != nil, "file.sig sig nil")
|
||||
|
||||
assert(byteEq(s2.Sig, sig.Sig), "sig compare fail")
|
||||
|
||||
// If we give a wrong file, verify must fail
|
||||
st, err := os.Stat(zf)
|
||||
assert(err == nil, "file.dat stat fail: %s", err)
|
||||
assert(err == nil, "file.dat stat fail")
|
||||
|
||||
n := st.Size()
|
||||
assert(n == 8192*8, "file.dat size fail")
|
||||
|
@ -224,38 +241,34 @@ func TestSignRandBuf(t *testing.T) {
|
|||
os.Truncate(zf, n-1)
|
||||
|
||||
st, err = os.Stat(zf)
|
||||
assert(err == nil, "file.dat stat2 fail: %s", err)
|
||||
assert(err == nil, "file.dat stat2 fail")
|
||||
assert(st.Size() == (n-1), "truncate fail")
|
||||
|
||||
// Now verify this corrupt file
|
||||
ok, err = pk.VerifyFile(zf, sig)
|
||||
assert(err == nil, "file.dat corrupt i/o fail: %s", err)
|
||||
assert(err == nil, "file.dat corrupt i/o fail")
|
||||
assert(!ok, "file.dat corrupt verify false")
|
||||
|
||||
os.RemoveAll(dn)
|
||||
}
|
||||
|
||||
|
||||
func Benchmark_Keygen(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = NewPrivateKey()
|
||||
_, _ = NewKeypair()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func Benchmark_Sig(b *testing.B) {
|
||||
var sizes = [...]uint{
|
||||
16,
|
||||
32,
|
||||
64,
|
||||
1024,
|
||||
4096,
|
||||
256 * 1024,
|
||||
1048576,
|
||||
4 * 1048576,
|
||||
}
|
||||
|
||||
b.StopTimer()
|
||||
sk, _ := NewPrivateKey()
|
||||
pk := sk.PublicKey()
|
||||
kp, _ := NewKeypair()
|
||||
var sig *Signature
|
||||
for _, sz := range sizes {
|
||||
buf := randbuf(sz)
|
||||
|
@ -264,12 +277,12 @@ func Benchmark_Sig(b *testing.B) {
|
|||
|
||||
b.ResetTimer()
|
||||
|
||||
b.Run(s0, func(b *testing.B) {
|
||||
sig = benchSign(b, buf, sk)
|
||||
b.Run(s0, func (b *testing.B) {
|
||||
sig = benchSign(b, buf, &kp.Sec)
|
||||
})
|
||||
|
||||
b.Run(s1, func(b *testing.B) {
|
||||
benchVerify(b, buf, sig, pk)
|
||||
b.Run(s1, func (b *testing.B) {
|
||||
benchVerify(b, buf, sig, &kp.Pub)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -287,9 +300,10 @@ func benchVerify(b *testing.B, buf []byte, sig *Signature, pk *PublicKey) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
func randbuf(sz uint) []byte {
|
||||
b := make([]byte, sz)
|
||||
randRead(b)
|
||||
rand.Read(b)
|
||||
return b
|
||||
}
|
||||
|
||||
|
|
384
sign/ssh.go
384
sign/ssh.go
|
@ -1,384 +0,0 @@
|
|||
// ssh.go - support for reading ssh private and public keys
|
||||
//
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This file is a bastardization of github.com/ScaleFT/sshkeys and
|
||||
// golang.org/x/crypto/ssh/keys.go
|
||||
//
|
||||
// It is licensed under the terms of the original go source code
|
||||
// OR the Apache 2.0 license (terms of sshkeys).
|
||||
//
|
||||
// Changes from that version:
|
||||
// - don't use password but call a func() to get the password as needed
|
||||
// - narrowly scope the key support for ONLY ed25519 keys
|
||||
// - support reading multiple public keys from authorized_keys
|
||||
|
||||
package sign
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/dchest/bcrypt_pbkdf"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
const keySizeAES256 = 32
|
||||
|
||||
// ParseEncryptedRawPrivateKey returns a private key from an
|
||||
// encrypted ed25519 private key.
|
||||
func parseSSHPrivateKey(data []byte, getpw func() ([]byte, error)) (*PrivateKey, error) {
|
||||
block, _ := pem.Decode(data)
|
||||
if block == nil {
|
||||
return nil, ErrNoPEMFound
|
||||
}
|
||||
|
||||
if x509.IsEncryptedPEMBlock(block) {
|
||||
return nil, fmt.Errorf("ssh: no support for legacy PEM encrypted keys")
|
||||
}
|
||||
|
||||
switch block.Type {
|
||||
case "OPENSSH PRIVATE KEY":
|
||||
return parseOpenSSHPrivateKey(block.Bytes, getpw)
|
||||
default:
|
||||
return nil, fmt.Errorf("ssh: unsupported key type %q", block.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func parseSSHPublicKey(in []byte) (*PublicKey, error) {
|
||||
splitter := regexp.MustCompile("[ \\t]+")
|
||||
v := splitter.Split(string(in), -1)
|
||||
if len(v) != 3 {
|
||||
return nil, ErrBadPublicKey
|
||||
}
|
||||
|
||||
return parseEncPubKey([]byte(v[1]), v[2])
|
||||
}
|
||||
|
||||
// parse a wire encoded public key
|
||||
func parseEncPubKey(in []byte, comm string) (*PublicKey, error) {
|
||||
in, err := base64.StdEncoding.DecodeString(string(in))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
algo, in, ok := parseString(in)
|
||||
if !ok {
|
||||
return nil, ErrKeyTooShort
|
||||
|
||||
}
|
||||
if string(algo) != ssh.KeyAlgoED25519 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var w struct {
|
||||
KeyBytes []byte
|
||||
Rest []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
if err := ssh.Unmarshal(in, &w); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(w.Rest) > 0 {
|
||||
return nil, ErrBadTrailers
|
||||
}
|
||||
|
||||
var pk PublicKey
|
||||
|
||||
if err = makePublicKeyFromBytes(&pk, w.KeyBytes); err == nil {
|
||||
pk.Comment = strings.TrimSpace(comm)
|
||||
return &pk, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func parseString(in []byte) (out, rest []byte, ok bool) {
|
||||
if len(in) < 4 {
|
||||
return
|
||||
}
|
||||
length := binary.BigEndian.Uint32(in)
|
||||
in = in[4:]
|
||||
if uint32(len(in)) < length {
|
||||
return
|
||||
}
|
||||
out = in[:length]
|
||||
rest = in[length:]
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// parseAuthorizedKey parses a public key in OpenSSH binary format and decodes it.
|
||||
// removed.
|
||||
func parseAuthorizedKey(in []byte) (*PublicKey, error) {
|
||||
in = bytes.TrimSpace(in)
|
||||
i := bytes.IndexAny(in, " \t")
|
||||
if i == -1 {
|
||||
i = len(in)
|
||||
}
|
||||
|
||||
pk, err := parseEncPubKey(in[:i], string(in[i:]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pk, nil
|
||||
}
|
||||
|
||||
// ParseAuthorizedKeys parses a public key from an authorized_keys
|
||||
// file used in OpenSSH according to the sshd(8) manual page.
|
||||
func ParseAuthorizedKeys(in []byte) ([]*PublicKey, error) {
|
||||
var pka []*PublicKey
|
||||
var rest []byte
|
||||
|
||||
for len(in) > 0 {
|
||||
end := bytes.IndexByte(in, '\n')
|
||||
if end != -1 {
|
||||
rest = in[end+1:]
|
||||
in = in[:end]
|
||||
} else {
|
||||
rest = nil
|
||||
}
|
||||
|
||||
end = bytes.IndexByte(in, '\r')
|
||||
if end != -1 {
|
||||
in = in[:end]
|
||||
}
|
||||
|
||||
in = bytes.TrimSpace(in)
|
||||
if len(in) == 0 || in[0] == '#' {
|
||||
in = rest
|
||||
continue
|
||||
}
|
||||
|
||||
i := bytes.IndexAny(in, " \t")
|
||||
if i == -1 {
|
||||
in = rest
|
||||
continue
|
||||
}
|
||||
|
||||
if pk, err := parseAuthorizedKey(in[i:]); err == nil {
|
||||
if pk != nil {
|
||||
pka = append(pka, pk)
|
||||
}
|
||||
in = rest
|
||||
continue
|
||||
}
|
||||
|
||||
// No key type recognised. Maybe there's an options field at
|
||||
// the beginning.
|
||||
var b byte
|
||||
inQuote := false
|
||||
var candidateOptions []string
|
||||
optionStart := 0
|
||||
for i, b = range in {
|
||||
isEnd := !inQuote && (b == ' ' || b == '\t')
|
||||
if (b == ',' && !inQuote) || isEnd {
|
||||
if i-optionStart > 0 {
|
||||
candidateOptions = append(candidateOptions, string(in[optionStart:i]))
|
||||
}
|
||||
optionStart = i + 1
|
||||
}
|
||||
if isEnd {
|
||||
break
|
||||
}
|
||||
if b == '"' && (i == 0 || (i > 0 && in[i-1] != '\\')) {
|
||||
inQuote = !inQuote
|
||||
}
|
||||
}
|
||||
for i < len(in) && (in[i] == ' ' || in[i] == '\t') {
|
||||
i++
|
||||
}
|
||||
if i == len(in) {
|
||||
// Invalid line: unmatched quote
|
||||
in = rest
|
||||
continue
|
||||
}
|
||||
|
||||
in = in[i:]
|
||||
i = bytes.IndexAny(in, " \t")
|
||||
if i == -1 {
|
||||
in = rest
|
||||
continue
|
||||
}
|
||||
|
||||
if pk, err := parseAuthorizedKey(in[i:]); err == nil {
|
||||
if pk != nil {
|
||||
pka = append(pka, pk)
|
||||
}
|
||||
}
|
||||
|
||||
in = rest
|
||||
continue
|
||||
}
|
||||
|
||||
return pka, nil
|
||||
}
|
||||
|
||||
const opensshv1Magic = "openssh-key-v1"
|
||||
|
||||
type opensshHeader struct {
|
||||
CipherName string
|
||||
KdfName string
|
||||
KdfOpts string
|
||||
NumKeys uint32
|
||||
PubKey string
|
||||
PrivKeyBlock string
|
||||
}
|
||||
|
||||
type opensshKey struct {
|
||||
Check1 uint32
|
||||
Check2 uint32
|
||||
Keytype string
|
||||
Rest []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
type opensshED25519 struct {
|
||||
Pub []byte
|
||||
Priv []byte
|
||||
Comment string
|
||||
Pad []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
func parseOpenSSHPrivateKey(data []byte, getpw func() ([]byte, error)) (*PrivateKey, error) {
|
||||
magic := append([]byte(opensshv1Magic), 0)
|
||||
if !bytes.Equal(magic, data[0:len(magic)]) {
|
||||
return nil, ErrBadFormat
|
||||
}
|
||||
remaining := data[len(magic):]
|
||||
|
||||
w := opensshHeader{}
|
||||
|
||||
if err := ssh.Unmarshal(remaining, &w); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if w.NumKeys != 1 {
|
||||
return nil, fmt.Errorf("ssh: NumKeys must be 1: %d", w.NumKeys)
|
||||
}
|
||||
|
||||
var privateKeyBytes []byte
|
||||
var encrypted bool
|
||||
|
||||
switch {
|
||||
// OpenSSH supports bcrypt KDF w/ AES256-CBC or AES256-CTR mode
|
||||
case w.KdfName == "bcrypt" && w.CipherName == "aes256-cbc":
|
||||
pw, err := getpw()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
iv, block, err := extractBcryptIvBlock(pw, &w)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cbc := cipher.NewCBCDecrypter(block, iv)
|
||||
privateKeyBytes = []byte(w.PrivKeyBlock)
|
||||
cbc.CryptBlocks(privateKeyBytes, privateKeyBytes)
|
||||
|
||||
encrypted = true
|
||||
|
||||
case w.KdfName == "bcrypt" && w.CipherName == "aes256-ctr":
|
||||
pw, err := getpw()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
iv, block, err := extractBcryptIvBlock(pw, &w)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stream := cipher.NewCTR(block, iv)
|
||||
privateKeyBytes = []byte(w.PrivKeyBlock)
|
||||
stream.XORKeyStream(privateKeyBytes, privateKeyBytes)
|
||||
|
||||
encrypted = true
|
||||
|
||||
case w.KdfName == "none" && w.CipherName == "none":
|
||||
privateKeyBytes = []byte(w.PrivKeyBlock)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("ssh: unknown Cipher/KDF: %s:%s", w.CipherName, w.KdfName)
|
||||
}
|
||||
|
||||
pk1 := opensshKey{}
|
||||
|
||||
if err := ssh.Unmarshal(privateKeyBytes, &pk1); err != nil {
|
||||
if encrypted {
|
||||
return nil, ErrIncorrectPassword
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if pk1.Check1 != pk1.Check2 {
|
||||
return nil, ErrIncorrectPassword
|
||||
}
|
||||
|
||||
// we only handle ed25519 and rsa keys currently
|
||||
switch pk1.Keytype {
|
||||
case ssh.KeyAlgoED25519:
|
||||
key := opensshED25519{}
|
||||
|
||||
err := ssh.Unmarshal(pk1.Rest, &key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(key.Priv) != ed25519.PrivateKeySize {
|
||||
return nil, ErrBadLength
|
||||
}
|
||||
|
||||
for i, b := range key.Pad {
|
||||
if int(b) != i+1 {
|
||||
return nil, ErrBadPadding
|
||||
}
|
||||
}
|
||||
|
||||
var sk PrivateKey
|
||||
if err = makePrivateKeyFromBytes(&sk, key.Priv); err == nil {
|
||||
return &sk, nil
|
||||
}
|
||||
return nil, err
|
||||
default:
|
||||
return nil, fmt.Errorf("ssh: unhandled key type: %v", pk1.Keytype)
|
||||
}
|
||||
}
|
||||
|
||||
func extractBcryptIvBlock(passphrase []byte, w *opensshHeader) ([]byte, cipher.Block, error) {
|
||||
cipherKeylen := keySizeAES256
|
||||
cipherIvLen := aes.BlockSize
|
||||
|
||||
var opts struct {
|
||||
Salt []byte
|
||||
Rounds uint32
|
||||
}
|
||||
|
||||
if err := ssh.Unmarshal([]byte(w.KdfOpts), &opts); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
kdfdata, err := bcrypt_pbkdf.Key(passphrase, opts.Salt, int(opts.Rounds), cipherKeylen+cipherIvLen)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
iv := kdfdata[cipherKeylen : cipherIvLen+cipherKeylen]
|
||||
aeskey := kdfdata[0:cipherKeylen]
|
||||
block, err := aes.NewCipher(aeskey)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return iv, block, nil
|
||||
}
|
162
sign/stream.go
162
sign/stream.go
|
@ -1,162 +0,0 @@
|
|||
// stream.go - Streaming io.Reader, io.Writer interface to encryption/decryption
|
||||
//
|
||||
// (c) 2016 Sudhi Herle <sudhi@herle.net>
|
||||
//
|
||||
// Licensing Terms: GPLv2
|
||||
//
|
||||
// If you need a commercial license for this work, please contact
|
||||
// the author.
|
||||
//
|
||||
// This software does not come with any express or implied
|
||||
// warranty; it is provided "as is". No claim is made to its
|
||||
// suitability for any purpose.
|
||||
//
|
||||
|
||||
package sign
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// encWriter buffers partial writes until a full chunk is accumulated.
|
||||
// It's methods implement the io.WriteCloser interface.
|
||||
type encWriter struct {
|
||||
buf []byte
|
||||
n int // # of bytes written
|
||||
wr io.WriteCloser
|
||||
e *Encryptor
|
||||
blk uint32
|
||||
err error
|
||||
}
|
||||
|
||||
var _ io.WriteCloser = &encWriter{}
|
||||
|
||||
// NewStreamWriter begins stream encryption to an underlying destination writer 'wr'.
|
||||
// It returns an io.WriteCloser.
|
||||
func (e *Encryptor) NewStreamWriter(wr io.WriteCloser) (io.WriteCloser, error) {
|
||||
if !e.started {
|
||||
err := e.start(wr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
w := &encWriter{
|
||||
buf: make([]byte, e.ChunkSize),
|
||||
wr: wr,
|
||||
e: e,
|
||||
}
|
||||
|
||||
e.stream = true
|
||||
return w, nil
|
||||
}
|
||||
|
||||
// Write implements the io.Writer interface
|
||||
func (w *encWriter) Write(b []byte) (int, error) {
|
||||
if w.err != nil {
|
||||
return 0, w.err
|
||||
}
|
||||
|
||||
n := len(b)
|
||||
if n == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
max := int(w.e.ChunkSize)
|
||||
for len(b) > 0 {
|
||||
buf := w.buf[w.n:]
|
||||
z := copy(buf, b)
|
||||
b = b[z:]
|
||||
w.n += z
|
||||
|
||||
// We only flush if we have more data remaining in the input buffer.
|
||||
// This way, we don't flush a potentially last block here; that happens
|
||||
// when the caller eventually closes the stream.
|
||||
if w.n == max && len(b) > 0 {
|
||||
w.err = w.e.encrypt(w.buf, w.wr, w.blk, false)
|
||||
if w.err != nil {
|
||||
return 0, w.err
|
||||
}
|
||||
|
||||
w.n = 0
|
||||
w.blk += 1
|
||||
}
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Close implements the io.Close interface
|
||||
func (w *encWriter) Close() error {
|
||||
if w.err != nil {
|
||||
return w.err
|
||||
}
|
||||
|
||||
err := w.e.encrypt(w.buf[:w.n], w.wr, w.blk, true)
|
||||
if err != nil {
|
||||
w.err = err
|
||||
return err
|
||||
}
|
||||
|
||||
w.n = 0
|
||||
w.err = ErrClosed
|
||||
return w.wr.Close()
|
||||
}
|
||||
|
||||
// encReader buffers partial reads and it's methods implement the io.Reader interface.
|
||||
type encReader struct {
|
||||
buf []byte
|
||||
unread []byte
|
||||
d *Decryptor
|
||||
blk uint32
|
||||
}
|
||||
|
||||
var _ io.Reader = &encReader{}
|
||||
|
||||
// NewStreamReader returns an io.Reader to read from the decrypted stream
|
||||
func (d *Decryptor) NewStreamReader() (io.Reader, error) {
|
||||
if d.key == nil {
|
||||
return nil, ErrNoKey
|
||||
}
|
||||
|
||||
if d.eof {
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
d.stream = true
|
||||
return &encReader{
|
||||
buf: make([]byte, d.ChunkSize),
|
||||
d: d,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Read implements io.Reader interface
|
||||
func (r *encReader) Read(b []byte) (int, error) {
|
||||
if r.d.eof && len(r.unread) == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
if len(r.unread) > 0 {
|
||||
n := copy(b, r.unread)
|
||||
r.unread = r.unread[n:]
|
||||
return n, nil
|
||||
}
|
||||
|
||||
buf, eof, err := r.d.decrypt(r.blk)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
r.blk += 1
|
||||
|
||||
n := copy(b, buf)
|
||||
buf = buf[n:]
|
||||
|
||||
copy(r.buf, buf)
|
||||
r.unread = r.buf[:len(buf)]
|
||||
|
||||
if eof {
|
||||
r.d.eof = true
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
// utils_test.go -- Test harness utilities for sign
|
||||
//
|
||||
// (c) 2016 Sudhi Herle <sudhi@herle.net>
|
||||
//
|
||||
// Licensing Terms: GPLv2
|
||||
//
|
||||
// If you need a commercial license for this work, please contact
|
||||
// the author.
|
||||
//
|
||||
// This software does not come with any express or implied
|
||||
// warranty; it is provided "as is". No claim is made to its
|
||||
// suitability for any purpose.
|
||||
|
||||
package sign
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func newAsserter(t *testing.T) func(cond bool, msg string, args ...interface{}) {
|
||||
return func(cond bool, msg string, args ...interface{}) {
|
||||
if cond {
|
||||
return
|
||||
}
|
||||
|
||||
_, file, line, ok := runtime.Caller(1)
|
||||
if !ok {
|
||||
file = "???"
|
||||
line = 0
|
||||
}
|
||||
|
||||
s := fmt.Sprintf(msg, args...)
|
||||
t.Fatalf("%s: %d: Assertion failed: %s\n", file, line, s)
|
||||
}
|
||||
}
|
||||
|
||||
// Return true if two byte arrays are equal
|
||||
func byteEq(x, y []byte) bool {
|
||||
return subtle.ConstantTimeCompare(x, y) == 1
|
||||
}
|
333
sigtool.go
Normal file
333
sigtool.go
Normal file
|
@ -0,0 +1,333 @@
|
|||
// 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"
|
||||
|
||||
"github.com/opencoff/go-utils"
|
||||
flag "github.com/opencoff/pflag"
|
||||
"github.com/opencoff/sigtool/sign"
|
||||
)
|
||||
|
||||
// This will be filled in by "build"
|
||||
var Version string = "1.1"
|
||||
|
||||
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\n", Z, Version)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if help {
|
||||
usage(0)
|
||||
}
|
||||
|
||||
args := mf.Args()
|
||||
if len(args) < 1 {
|
||||
warn("Insufficient arguments. Try '%s -h'", Z)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
switch args[0] {
|
||||
case "gen", "generate", "g":
|
||||
gen(args[1:])
|
||||
|
||||
case "sign", "s":
|
||||
signify(args[1:])
|
||||
|
||||
case "verify", "v":
|
||||
verify(args[1:])
|
||||
|
||||
case "help", "":
|
||||
usage(0)
|
||||
|
||||
default:
|
||||
die("Unknown command %s", args[0])
|
||||
}
|
||||
}
|
||||
|
||||
// Run the generate command
|
||||
func gen(args []string) {
|
||||
|
||||
var pw, help, force bool
|
||||
var comment string
|
||||
var envpw string
|
||||
|
||||
fs := flag.NewFlagSet("generate", flag.ExitOnError)
|
||||
fs.BoolVarP(&help, "help", "h", false, "Show this help and exit")
|
||||
fs.BoolVarP(&pw, "password", "p", false, "Ask for passphrase to encrypt the private key")
|
||||
fs.StringVarP(&comment, "comment", "c", "", "Use `C` as the text comment for the keys")
|
||||
fs.StringVarP(&envpw, "env-password", "E", "", "Use passphrase from environment variable `E`")
|
||||
fs.BoolVarP(&force, "force", "F", false, "Overwrite the output file if it exists")
|
||||
|
||||
fs.Parse(args)
|
||||
|
||||
if help {
|
||||
fs.SetOutput(os.Stdout)
|
||||
fmt.Printf(`%s generate|gen|g [options] file-prefix
|
||||
|
||||
Generate a new Ed25519 public+private key pair and write public key to
|
||||
FILE-PREFIX.pub and private key to FILE-PREFIX.key.
|
||||
|
||||
Options:
|
||||
`, Z)
|
||||
fs.PrintDefaults()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
args = fs.Args()
|
||||
if len(args) < 1 {
|
||||
die("Insufficient arguments to 'generate'. Try '%s generate -h' ..", Z)
|
||||
}
|
||||
|
||||
bn := args[0]
|
||||
|
||||
if exists(bn) && !force {
|
||||
die("Public/Private key files (%s.key, %s.pub) exist. Won't overwrite!", bn, bn)
|
||||
}
|
||||
|
||||
var pws string
|
||||
var err error
|
||||
|
||||
if len(envpw) > 0 {
|
||||
pws = os.Getenv(envpw)
|
||||
} else if pw {
|
||||
pws, err = utils.Askpass("Enter passphrase for private key", true)
|
||||
if err != nil {
|
||||
die("%s", err)
|
||||
}
|
||||
}
|
||||
|
||||
kp, err := sign.NewKeypair()
|
||||
if err != nil {
|
||||
die("%s", err)
|
||||
}
|
||||
|
||||
err = kp.Serialize(bn, comment, pws)
|
||||
if err != nil {
|
||||
die("%s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Run the 'sign' command.
|
||||
func signify(args []string) {
|
||||
var pw, help bool
|
||||
var output string
|
||||
var envpw string
|
||||
|
||||
fs := flag.NewFlagSet("sign", flag.ExitOnError)
|
||||
fs.BoolVarP(&help, "help", "h", false, "Show this help and exit")
|
||||
fs.BoolVarP(&pw, "password", "p", false, "Ask for passphrase to decrypt the private key")
|
||||
fs.StringVarP(&envpw, "env-password", "E", "", "Use passphrase from environment variable `E`")
|
||||
fs.StringVarP(&output, "output", "o", "", "Write signature to file `F`")
|
||||
|
||||
fs.Parse(args)
|
||||
|
||||
if help {
|
||||
fs.SetOutput(os.Stdout)
|
||||
fmt.Printf(`%s sign|s [options] privkey file
|
||||
|
||||
Sign FILE with a Ed25519 private key PRIVKEY and write signature to FILE.sig
|
||||
|
||||
Options:
|
||||
`, Z)
|
||||
fs.PrintDefaults()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
args = fs.Args()
|
||||
if len(args) < 2 {
|
||||
die("Insufficient arguments to 'sign'. Try '%s sign -h' ..", Z)
|
||||
}
|
||||
|
||||
kn := args[0]
|
||||
fn := args[1]
|
||||
outf := fmt.Sprintf("%s.sig", fn)
|
||||
|
||||
var pws string
|
||||
var err error
|
||||
|
||||
if len(envpw) > 0 {
|
||||
pws = os.Getenv(envpw)
|
||||
} else if pw {
|
||||
pws, err = utils.Askpass("Enter passphrase for private key", false)
|
||||
if err != nil {
|
||||
die("%s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(output) > 0 {
|
||||
outf = output
|
||||
}
|
||||
|
||||
pk, err := sign.ReadPrivateKey(kn, pws)
|
||||
if err != nil {
|
||||
die("%s", err)
|
||||
}
|
||||
|
||||
sig, err := pk.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
|
||||
`, 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()
|
||||
}
|
||||
|
||||
// vim: ft=go:sw=8:ts=8:noexpandtab:tw=98:
|
399
src/crypt.go
399
src/crypt.go
|
@ -1,399 +0,0 @@
|
|||
// crypt.go -- Encrypt/decrypt command handling
|
||||
//
|
||||
// (c) 2016 Sudhi Herle <sudhi@herle.net>
|
||||
//
|
||||
// Licensing Terms: GPLv2
|
||||
//
|
||||
// If you need a commercial license for this work, please contact
|
||||
// the author.
|
||||
//
|
||||
// This software does not come with any express or implied
|
||||
// warranty; it is provided "as is". No claim is made to its
|
||||
// suitability for any purpose.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"git.rgst.io/homelab/sigtool/v3/sign"
|
||||
"github.com/opencoff/go-fio"
|
||||
"github.com/opencoff/go-utils"
|
||||
flag "github.com/opencoff/pflag"
|
||||
)
|
||||
|
||||
// sigtool encrypt [-i|--identity my.key] to.pub [to.pub] [ssh.pub] inputfile|- [-o output]
|
||||
|
||||
func encrypt(args []string) {
|
||||
fs := flag.NewFlagSet("encrypt", flag.ExitOnError)
|
||||
fs.Usage = func() {
|
||||
encryptUsage(fs)
|
||||
}
|
||||
|
||||
var outfile string
|
||||
var keyfile string
|
||||
var szstr string = "128k"
|
||||
var envpw string
|
||||
var nopw, force bool
|
||||
var blksize uint64
|
||||
|
||||
fs.StringVarP(&outfile, "outfile", "o", "", "Write the output to file `F`")
|
||||
fs.StringVarP(&keyfile, "sign", "s", "", "Sign using private key `S`")
|
||||
fs.BoolVarP(&nopw, "no-password", "", false, "Don't ask for passphrase to decrypt the private key")
|
||||
fs.StringVarP(&envpw, "env-password", "E", "", "Use passphrase from environment variable `E`")
|
||||
fs.StringVarP(&szstr, "block-size", "B", szstr, "Use `S` as the encryption block size")
|
||||
fs.BoolVarP(&force, "overwrite", "", false, "Overwrite the output file if it exists")
|
||||
|
||||
err := fs.Parse(args)
|
||||
if err != nil {
|
||||
Die("%s", err)
|
||||
}
|
||||
|
||||
if blksize, err = utils.ParseSize(szstr); err != nil {
|
||||
Die("%s", err)
|
||||
}
|
||||
|
||||
var pws, infile string
|
||||
var sk *sign.PrivateKey
|
||||
|
||||
if len(keyfile) > 0 {
|
||||
sk, err = sign.ReadPrivateKey(keyfile, func() ([]byte, error) {
|
||||
if nopw {
|
||||
return nil, nil
|
||||
}
|
||||
if len(envpw) > 0 {
|
||||
pws = os.Getenv(envpw)
|
||||
} else {
|
||||
pws, err = utils.Askpass("Enter passphrase for private key", false)
|
||||
if err != nil {
|
||||
Die("%s", err)
|
||||
}
|
||||
}
|
||||
return []byte(pws), nil
|
||||
})
|
||||
if err != nil {
|
||||
Die("%s", err)
|
||||
}
|
||||
}
|
||||
|
||||
args = fs.Args()
|
||||
if len(args) < 2 {
|
||||
Die("Insufficient args. Try '%s --help'", os.Args[0])
|
||||
}
|
||||
|
||||
var infd io.Reader = os.Stdin
|
||||
var outfd io.WriteCloser = os.Stdout
|
||||
var inf *os.File
|
||||
|
||||
if len(args) > 1 {
|
||||
infile = args[len(args)-1]
|
||||
if infile != "-" {
|
||||
inf := mustOpen(infile, os.O_RDONLY)
|
||||
defer inf.Close()
|
||||
|
||||
infd = inf
|
||||
}
|
||||
}
|
||||
|
||||
// Lets try to read the authorized files
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
Die("can't find homedir for this user")
|
||||
}
|
||||
|
||||
authkeys := fmt.Sprintf("%s/.ssh/authorized_keys", home)
|
||||
authdata, err := ioutil.ReadFile(authkeys)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
Die("can't open %s: %s", authkeys, err)
|
||||
}
|
||||
}
|
||||
|
||||
pka, err := sign.ParseAuthorizedKeys(authdata)
|
||||
keymap := make(map[string]*sign.PublicKey)
|
||||
|
||||
for _, pk := range pka {
|
||||
keymap[pk.Comment] = pk
|
||||
}
|
||||
|
||||
if len(outfile) > 0 && outfile != "-" {
|
||||
var mode os.FileMode = 0600 // conservative output mode
|
||||
|
||||
if inf != nil {
|
||||
var err error
|
||||
var ist, ost os.FileInfo
|
||||
|
||||
if ost, err = os.Stat(outfile); err != nil {
|
||||
Die("can't stat %s: %s", outfile, err)
|
||||
}
|
||||
|
||||
if ist, err = inf.Stat(); err != nil {
|
||||
Die("can't stat %s: %s", infile, err)
|
||||
}
|
||||
|
||||
if os.SameFile(ist, ost) {
|
||||
Die("won't create output file: same as input file!")
|
||||
}
|
||||
mode = ist.Mode()
|
||||
}
|
||||
|
||||
var opts uint32
|
||||
if force {
|
||||
opts |= fio.OPT_OVERWRITE
|
||||
}
|
||||
sf, err := fio.NewSafeFile(outfile, opts, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode)
|
||||
if err != nil {
|
||||
Die("%s", err)
|
||||
}
|
||||
|
||||
AtExit(sf.Abort)
|
||||
defer sf.Abort()
|
||||
outfd = sf
|
||||
}
|
||||
|
||||
en, err := sign.NewEncryptor(sk, blksize)
|
||||
if err != nil {
|
||||
Die("%s", err)
|
||||
}
|
||||
|
||||
errs := 0
|
||||
for i := 0; i < len(args)-1; i++ {
|
||||
var err error
|
||||
var pk *sign.PublicKey
|
||||
|
||||
fn := args[i]
|
||||
if strings.Index(fn, "@") > 0 {
|
||||
var ok bool
|
||||
pk, ok = keymap[fn]
|
||||
if !ok {
|
||||
Warn("can't find user %s in %s", fn, authkeys)
|
||||
errs += 1
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
pk, err = sign.ReadPublicKey(fn)
|
||||
if err != nil {
|
||||
Warn("%s", err)
|
||||
errs += 1
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
err = en.AddRecipient(pk)
|
||||
if err != nil {
|
||||
Die("%s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if errs > 0 {
|
||||
Die("Too many errors!")
|
||||
}
|
||||
|
||||
err = en.Encrypt(infd, outfd)
|
||||
if err != nil {
|
||||
Die("%s", err)
|
||||
}
|
||||
outfd.Close()
|
||||
}
|
||||
|
||||
type nullWriter struct{}
|
||||
|
||||
func (w *nullWriter) Write(p []byte) (int, error) {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (w *nullWriter) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ io.WriteCloser = &nullWriter{}
|
||||
|
||||
// sigtool decrypt a.key [file] [-o output]
|
||||
func decrypt(args []string) {
|
||||
fs := flag.NewFlagSet("decrypt", flag.ExitOnError)
|
||||
fs.Usage = func() {
|
||||
decryptUsage(fs)
|
||||
}
|
||||
|
||||
var envpw string
|
||||
var outfile string
|
||||
var pubkey string
|
||||
var nopw, test, force bool
|
||||
|
||||
fs.StringVarP(&outfile, "outfile", "o", "", "Write the output to file `F`")
|
||||
fs.BoolVarP(&nopw, "no-password", "", false, "Don't ask for passphrase to decrypt the private key")
|
||||
fs.StringVarP(&envpw, "env-password", "E", "", "Use passphrase from environment variable `E`")
|
||||
fs.StringVarP(&pubkey, "verify-sender", "v", "", "Verify that the sender matches public key in `F`")
|
||||
fs.BoolVarP(&test, "test", "t", false, "Test the encrypted file against the given key without writing to output")
|
||||
fs.BoolVarP(&force, "overwrite", "", false, "Overwrite the output file if it exists")
|
||||
|
||||
err := fs.Parse(args)
|
||||
if err != nil {
|
||||
Die("%s", err)
|
||||
}
|
||||
|
||||
args = fs.Args()
|
||||
if len(args) < 1 {
|
||||
Die("Insufficient args. Try '%s --help'", os.Args[0])
|
||||
}
|
||||
|
||||
var infd io.Reader = os.Stdin
|
||||
var outfd io.WriteCloser = os.Stdout
|
||||
var inf *os.File
|
||||
var infile string
|
||||
|
||||
keyfile := args[0]
|
||||
sk, err := sign.ReadPrivateKey(keyfile, func() ([]byte, error) {
|
||||
var pws string
|
||||
if nopw {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if len(envpw) > 0 {
|
||||
pws = os.Getenv(envpw)
|
||||
} else {
|
||||
pws, err = utils.Askpass("Enter passphrase for private key", false)
|
||||
if err != nil {
|
||||
Die("%s", err)
|
||||
}
|
||||
}
|
||||
return []byte(pws), nil
|
||||
})
|
||||
if err != nil {
|
||||
Die("%s", err)
|
||||
}
|
||||
|
||||
var pk *sign.PublicKey
|
||||
|
||||
if len(pubkey) > 0 {
|
||||
pk, err = sign.ReadPublicKey(pubkey)
|
||||
if err != nil {
|
||||
Die("%s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(args) > 1 {
|
||||
infile = args[1]
|
||||
if infile != "-" {
|
||||
inf := mustOpen(infile, os.O_RDONLY)
|
||||
defer inf.Close()
|
||||
|
||||
infd = inf
|
||||
}
|
||||
}
|
||||
|
||||
if test {
|
||||
outfd = &nullWriter{}
|
||||
} else if len(outfile) > 0 && outfile != "-" {
|
||||
var mode os.FileMode = 0600 // conservative mode
|
||||
|
||||
if inf != nil {
|
||||
var ist, ost os.FileInfo
|
||||
var err error
|
||||
|
||||
if ost, err = os.Stat(outfile); err != nil {
|
||||
Die("can't stat %s: %s", outfile, err)
|
||||
}
|
||||
if ist, err = inf.Stat(); err != nil {
|
||||
Die("can't stat %s: %s", infile, err)
|
||||
}
|
||||
if os.SameFile(ist, ost) {
|
||||
Die("won't create output file: same as input file!")
|
||||
}
|
||||
mode = ist.Mode()
|
||||
}
|
||||
|
||||
var opts uint32
|
||||
if force {
|
||||
opts |= fio.OPT_OVERWRITE
|
||||
}
|
||||
sf, err := fio.NewSafeFile(outfile, opts, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode)
|
||||
if err != nil {
|
||||
Die("%s", err)
|
||||
}
|
||||
|
||||
AtExit(sf.Abort)
|
||||
defer sf.Abort()
|
||||
outfd = sf
|
||||
}
|
||||
|
||||
d, err := sign.NewDecryptor(infd)
|
||||
if err != nil {
|
||||
Die("%s", err)
|
||||
}
|
||||
|
||||
err = d.SetPrivateKey(sk, pk)
|
||||
if err != nil {
|
||||
Die("%s", err)
|
||||
}
|
||||
|
||||
if pk == nil && d.AuthenticatedSender() {
|
||||
var fn string = infile
|
||||
if len(fn) == 0 || fn == "-" {
|
||||
fn = "<stdin>"
|
||||
}
|
||||
Warn("%s: Missing sender Public Key; can't authenticate sender ..", fn)
|
||||
}
|
||||
|
||||
if err = d.Decrypt(outfd); err != nil {
|
||||
Die("%s", err)
|
||||
}
|
||||
|
||||
outfd.Close()
|
||||
|
||||
if test {
|
||||
Warn("Enc file OK")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func encryptUsage(fs *flag.FlagSet) {
|
||||
fmt.Printf(`%s encrypt: Encrypt a file to one or more recipients.
|
||||
|
||||
Usage: %s encrypt [options] to [to ...] infile|-
|
||||
|
||||
Where TO is the public key of the recipient; it can be one of:
|
||||
|
||||
- a file referring to an SSH or sigtool public key.
|
||||
- string of the form 'a@b' - in which case the user's default
|
||||
ssh/authorized_keys is consulted to find the comment matching
|
||||
'a@b' - in which case the user's ssh authorized_keys file is consulted to
|
||||
find the comment matching the string.
|
||||
|
||||
INFILE is an input file to be encrypted. If the input file is '-' then %s
|
||||
reads from STDIN. Unless '-o' is used, %s writes the encrypted output to STDOUT.
|
||||
|
||||
Options:
|
||||
`, Z, Z, Z, Z)
|
||||
|
||||
fs.PrintDefaults()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func decryptUsage(fs *flag.FlagSet) {
|
||||
fmt.Printf(`%s decrypt: Decrypt a file.
|
||||
|
||||
Usage: %s decrypt [options] key [infile]
|
||||
|
||||
Where KEY is the private key to be used for decryption and INFILE is
|
||||
the encrypted input file. If INFILE is not provided, %s reads
|
||||
from STDIN. Unless '-o' is used, %s writes the decrypted output to STDOUT.
|
||||
|
||||
Options:
|
||||
`, Z, Z, Z, Z)
|
||||
|
||||
fs.PrintDefaults()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func mustOpen(fn string, flag int) *os.File {
|
||||
fdk, err := os.OpenFile(fn, flag, 0600)
|
||||
if err != nil {
|
||||
Die("can't open file %s: %s", fn, err)
|
||||
}
|
||||
return fdk
|
||||
}
|
55
src/die.go
55
src/die.go
|
@ -1,55 +0,0 @@
|
|||
// die.go -- die() and warn()
|
||||
//
|
||||
// (c) 2016 Sudhi Herle <sudhi@herle.net>
|
||||
//
|
||||
// Licensing Terms: GPLv2
|
||||
//
|
||||
// If you need a commercial license for this work, please contact
|
||||
// the author.
|
||||
//
|
||||
// This software does not come with any express or implied
|
||||
// warranty; it is provided "as is". No claim is made to its
|
||||
// suitability for any purpose.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
var atExit []func()
|
||||
|
||||
// Die prints an error message to stderr
|
||||
// and exits the program after calling all the registered
|
||||
// at-exit functions.
|
||||
func Die(f string, v ...interface{}) {
|
||||
Warn(f, v...)
|
||||
Exit(1)
|
||||
}
|
||||
|
||||
// Warn prints an error message to stderr
|
||||
func Warn(f string, v ...interface{}) {
|
||||
z := fmt.Sprintf("%s: %s", os.Args[0], f)
|
||||
s := fmt.Sprintf(z, v...)
|
||||
if n := len(s); s[n-1] != '\n' {
|
||||
s += "\n"
|
||||
}
|
||||
|
||||
os.Stderr.WriteString(s)
|
||||
os.Stderr.Sync()
|
||||
}
|
||||
|
||||
// AtExit registers a function to be called before the program exits.
|
||||
func AtExit(f func()) {
|
||||
atExit = append(atExit, f)
|
||||
}
|
||||
|
||||
// Exit invokes the registered atexit handlers and exits with the
|
||||
// given code.
|
||||
func Exit(v int) {
|
||||
for _, f := range atExit {
|
||||
f()
|
||||
}
|
||||
os.Exit(v)
|
||||
}
|
100
src/gen.go
100
src/gen.go
|
@ -1,100 +0,0 @@
|
|||
// gen.go -- generate keys
|
||||
//
|
||||
// (c) 2016 Sudhi Herle <sudhi@herle.net>
|
||||
//
|
||||
// Licensing Terms: GPLv2
|
||||
//
|
||||
// If you need a commercial license for this work, please contact
|
||||
// the author.
|
||||
//
|
||||
// This software does not come with any express or implied
|
||||
// warranty; it is provided "as is". No claim is made to its
|
||||
// suitability for any purpose.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"git.rgst.io/homelab/sigtool/v3/sign"
|
||||
"github.com/opencoff/go-utils"
|
||||
flag "github.com/opencoff/pflag"
|
||||
)
|
||||
|
||||
// Run the generate command
|
||||
func gen(args []string) {
|
||||
var nopw, help, force bool
|
||||
var comment string
|
||||
var envpw string
|
||||
|
||||
fs := flag.NewFlagSet("generate", flag.ExitOnError)
|
||||
fs.BoolVarP(&help, "help", "h", false, "Show this help and exit")
|
||||
fs.BoolVarP(&nopw, "no-password", "", false, "Don't ask for a password for the private key")
|
||||
fs.StringVarP(&comment, "comment", "c", "", "Use `C` as the text comment for the keys")
|
||||
fs.StringVarP(&envpw, "env-password", "E", "", "Use passphrase from environment variable `E`")
|
||||
fs.BoolVarP(&force, "overwrite", "", false, "Overwrite the output file if it exists")
|
||||
|
||||
fs.Parse(args)
|
||||
|
||||
if help {
|
||||
fs.SetOutput(os.Stdout)
|
||||
fmt.Printf(`%s generate|gen|g [options] file-prefix
|
||||
|
||||
Generate a new Ed25519 public+private key pair and write public key to
|
||||
FILE-PREFIX.pub and private key to FILE-PREFIX.key.
|
||||
|
||||
Options:
|
||||
`, Z)
|
||||
fs.PrintDefaults()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
args = fs.Args()
|
||||
if len(args) < 1 {
|
||||
Die("Insufficient arguments to 'generate'. Try '%s generate -h' ..", Z)
|
||||
}
|
||||
|
||||
bn := args[0]
|
||||
|
||||
pkn := fmt.Sprintf("%s.pub", path.Clean(bn))
|
||||
skn := fmt.Sprintf("%s.key", path.Clean(bn))
|
||||
|
||||
if !force {
|
||||
if exists(pkn) || exists(skn) {
|
||||
Die("Public/Private key files (%s, %s) exist. won't overwrite!", skn, pkn)
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
var pw []byte
|
||||
|
||||
if !nopw {
|
||||
var pws string
|
||||
if len(envpw) > 0 {
|
||||
pws = os.Getenv(envpw)
|
||||
} else {
|
||||
pws, err = utils.Askpass("Enter passphrase for private key", true)
|
||||
if err != nil {
|
||||
Die("%s", err)
|
||||
}
|
||||
}
|
||||
|
||||
pw = []byte(pws)
|
||||
}
|
||||
|
||||
sk, err := sign.NewPrivateKey()
|
||||
if err != nil {
|
||||
Die("%s", err)
|
||||
}
|
||||
|
||||
if err = sk.Serialize(skn, comment, force, pw); err != nil {
|
||||
Die("%s", err)
|
||||
}
|
||||
|
||||
pk := sk.PublicKey()
|
||||
if err = pk.Serialize(pkn, comment, force); err != nil {
|
||||
Die("%s", err)
|
||||
}
|
||||
}
|
116
src/sign.go
116
src/sign.go
|
@ -1,116 +0,0 @@
|
|||
// sign.go -- 'sign' command implementation
|
||||
//
|
||||
// (c) 2016 Sudhi Herle <sudhi@herle.net>
|
||||
//
|
||||
// Licensing Terms: GPLv2
|
||||
//
|
||||
// If you need a commercial license for this work, please contact
|
||||
// the author.
|
||||
//
|
||||
// This software does not come with any express or implied
|
||||
// warranty; it is provided "as is". No claim is made to its
|
||||
// suitability for any purpose.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"git.rgst.io/homelab/sigtool/v3/sign"
|
||||
"github.com/opencoff/go-fio"
|
||||
"github.com/opencoff/go-utils"
|
||||
flag "github.com/opencoff/pflag"
|
||||
)
|
||||
|
||||
// Run the 'sign' command.
|
||||
func signify(args []string) {
|
||||
var nopw, help, force bool
|
||||
var output string
|
||||
var envpw string
|
||||
|
||||
fs := flag.NewFlagSet("sign", flag.ExitOnError)
|
||||
fs.BoolVarP(&help, "help", "h", false, "Show this help and exit")
|
||||
fs.BoolVarP(&nopw, "no-password", "", false, "Don't ask for a password for the private key")
|
||||
fs.StringVarP(&envpw, "env-password", "E", "", "Use passphrase from environment variable `E`")
|
||||
fs.StringVarP(&output, "output", "o", "", "Write signature to file `F`")
|
||||
fs.BoolVarP(&force, "overwrite", "", false, "Overwrite previous signature file if it exists")
|
||||
|
||||
fs.Parse(args)
|
||||
|
||||
if help {
|
||||
fs.SetOutput(os.Stdout)
|
||||
fmt.Printf(`%s sign|s [options] privkey file
|
||||
|
||||
Sign FILE with a Ed25519 private key PRIVKEY and write signature to FILE.sig
|
||||
|
||||
Options:
|
||||
`, Z)
|
||||
fs.PrintDefaults()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
args = fs.Args()
|
||||
if len(args) < 2 {
|
||||
Die("Insufficient arguments to 'sign'. Try '%s sign -h' ..", Z)
|
||||
}
|
||||
|
||||
kn := args[0]
|
||||
fn := args[1]
|
||||
outf := fmt.Sprintf("%s.sig", fn)
|
||||
|
||||
var err error
|
||||
|
||||
if len(output) > 0 {
|
||||
outf = output
|
||||
}
|
||||
|
||||
var fd io.WriteCloser = os.Stdout
|
||||
|
||||
if outf != "-" {
|
||||
var opts uint32
|
||||
if force {
|
||||
opts |= fio.OPT_OVERWRITE
|
||||
}
|
||||
sf, err := fio.NewSafeFile(outf, opts, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
Die("can't create sig file: %s", err)
|
||||
}
|
||||
|
||||
// we unlink and remove temp on any error
|
||||
AtExit(sf.Abort)
|
||||
defer sf.Abort()
|
||||
fd = sf
|
||||
}
|
||||
|
||||
sk, err := sign.ReadPrivateKey(kn, func() ([]byte, error) {
|
||||
if nopw {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var pws string
|
||||
if len(envpw) > 0 {
|
||||
pws = os.Getenv(envpw)
|
||||
} else {
|
||||
pws, err = utils.Askpass("Enter passphrase for private key", false)
|
||||
if err != nil {
|
||||
Die("%s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return []byte(pws), nil
|
||||
})
|
||||
if err != nil {
|
||||
Die("%s", err)
|
||||
}
|
||||
|
||||
sig, err := sk.SignFile(fn)
|
||||
if err != nil {
|
||||
Die("%s", err)
|
||||
}
|
||||
|
||||
sigbytes, err := sig.MarshalBinary(fmt.Sprintf("input=%s", fn))
|
||||
fd.Write(sigbytes)
|
||||
fd.Close()
|
||||
}
|
129
src/sigtool.go
129
src/sigtool.go
|
@ -1,129 +0,0 @@
|
|||
// sigtool.go -- Tool to generate, manage Ed25519 keys and
|
||||
// signatures.
|
||||
//
|
||||
// (c) 2016 Sudhi Herle <sudhi@herle.net>
|
||||
//
|
||||
// Licensing Terms: GPLv2
|
||||
//
|
||||
// If you need a commercial license for this work, please contact
|
||||
// the author.
|
||||
//
|
||||
// This software does not come with any express or implied
|
||||
// warranty; it is provided "as is". No claim is made to its
|
||||
// suitability for any purpose.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"git.rgst.io/homelab/sigtool/v3/sign"
|
||||
"github.com/opencoff/go-utils"
|
||||
flag "github.com/opencoff/pflag"
|
||||
)
|
||||
|
||||
var Z string = path.Base(os.Args[0])
|
||||
|
||||
func main() {
|
||||
var ver, help, debug bool
|
||||
|
||||
mf := flag.NewFlagSet(Z, flag.ExitOnError)
|
||||
mf.SetInterspersed(false)
|
||||
mf.BoolVarP(&ver, "version", "v", false, "Show version info and exit")
|
||||
mf.BoolVarP(&help, "help", "h", false, "Show help info exit")
|
||||
mf.BoolVarP(&debug, "debug", "", false, "Enable debug mode")
|
||||
mf.Parse(os.Args[1:])
|
||||
|
||||
if ver {
|
||||
fmt.Printf("%s - %s [%s]\n", Z, ProductVersion, RepoVersion)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if help {
|
||||
usage(0)
|
||||
}
|
||||
|
||||
args := mf.Args()
|
||||
if len(args) < 1 {
|
||||
Die("Insufficient arguments. Try '%s -h'", Z)
|
||||
}
|
||||
|
||||
cmds := map[string]func(args []string){
|
||||
"generate": gen,
|
||||
"sign": signify,
|
||||
"verify": verify,
|
||||
"encrypt": encrypt,
|
||||
"decrypt": decrypt,
|
||||
|
||||
"help": func(_ []string) {
|
||||
usage(0)
|
||||
},
|
||||
}
|
||||
|
||||
words := make([]string, 0, len(cmds))
|
||||
for k := range cmds {
|
||||
words = append(words, k)
|
||||
}
|
||||
|
||||
ab := utils.Abbrev(words)
|
||||
canon, ok := ab[strings.ToLower(args[0])]
|
||||
if !ok {
|
||||
Die("Unknown command %s", args[0])
|
||||
}
|
||||
|
||||
cmd := cmds[canon]
|
||||
if cmd == nil {
|
||||
Die("can't map command %s", canon)
|
||||
}
|
||||
|
||||
if debug {
|
||||
sign.Debug(1)
|
||||
}
|
||||
|
||||
cmd(args[1:])
|
||||
|
||||
// always call Exit so that at-exit handlers are called.
|
||||
Exit(0)
|
||||
}
|
||||
|
||||
// Verify signature on a given file
|
||||
|
||||
func usage(c int) {
|
||||
x := fmt.Sprintf(`%s is a tool to generate, sign and verify files with Ed25519 signatures.
|
||||
|
||||
Usage: %s [global-options] command [options] arg [args..]
|
||||
|
||||
Global options:
|
||||
-h, --help Show help and exit
|
||||
-v, --version Show version info and exit
|
||||
--debug Enable debug (DANGEROUS)
|
||||
|
||||
Commands:
|
||||
generate, g Generate a new Ed25519 keypair
|
||||
sign, s Sign a file with a private key
|
||||
verify, v Verify a signature against a file and a public key
|
||||
encrypt, e Encrypt an input file to one or more recipients
|
||||
decrypt, d Decrypt a file with a private key
|
||||
`, Z, Z)
|
||||
|
||||
os.Stdout.Write([]byte(x))
|
||||
os.Exit(c)
|
||||
}
|
||||
|
||||
// Return true if $bn.key or $bn.pub exist; false otherwise
|
||||
func exists(nm string) bool {
|
||||
if _, err := os.Stat(nm); err == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// This will be filled in by "build"
|
||||
var RepoVersion string = "UNDEFINED"
|
||||
var ProductVersion string = "UNDEFINED"
|
||||
|
||||
// vim: ft=go:sw=8:ts=8:noexpandtab:tw=98:
|
|
@ -1,96 +0,0 @@
|
|||
// verify.go -- Verify signatures
|
||||
//
|
||||
// (c) 2016 Sudhi Herle <sudhi@herle.net>
|
||||
//
|
||||
// Licensing Terms: GPLv2
|
||||
//
|
||||
// If you need a commercial license for this work, please contact
|
||||
// the author.
|
||||
//
|
||||
// This software does not come with any express or implied
|
||||
// warranty; it is provided "as is". No claim is made to its
|
||||
// suitability for any purpose.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"git.rgst.io/homelab/sigtool/v3/sign"
|
||||
flag "github.com/opencoff/pflag"
|
||||
)
|
||||
|
||||
func verify(args []string) {
|
||||
var help, quiet bool
|
||||
|
||||
fs := flag.NewFlagSet("verify", flag.ExitOnError)
|
||||
fs.BoolVarP(&help, "help", "h", false, "Show this help and exit")
|
||||
fs.BoolVarP(&quiet, "quiet", "q", false, "Don't show any output; exit with status code only")
|
||||
|
||||
fs.Parse(args)
|
||||
|
||||
if help {
|
||||
fs.SetOutput(os.Stdout)
|
||||
fmt.Printf(`%s verify|v [options] pubkey sig file
|
||||
|
||||
Verify an Ed25519 signature in SIG of FILE using a public key PUBKEY.
|
||||
The pubkey can be one of:
|
||||
- a file: either OpenSSH ed25519 pubkey or a sigtool pubkey
|
||||
- a string: the raw OpenSSH or sigtool pubkey
|
||||
|
||||
%s will first parse it as a string before trying to parse it as a file.
|
||||
|
||||
Options:
|
||||
`, Z, Z)
|
||||
fs.PrintDefaults()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
args = fs.Args()
|
||||
if len(args) < 3 {
|
||||
Die("Insufficient arguments to 'verify'. Try '%s verify -h' ..", Z)
|
||||
}
|
||||
|
||||
pn := args[0]
|
||||
sn := args[1]
|
||||
fn := args[2]
|
||||
|
||||
// We first try to read the public key as a base64/openssh string
|
||||
pk, err := sign.MakePublicKeyFromString(pn)
|
||||
if err != nil {
|
||||
pk, err = sign.ReadPublicKey(pn)
|
||||
if err != nil {
|
||||
Die("%s", err)
|
||||
}
|
||||
}
|
||||
|
||||
sig, err := sign.ReadSignature(sn)
|
||||
if err != nil {
|
||||
Die("Can't read signature '%s': %s", sn, err)
|
||||
}
|
||||
|
||||
if !sig.IsPKMatch(pk) {
|
||||
Die("Wrong public key '%s' for verifying '%s'", pn, sn)
|
||||
}
|
||||
|
||||
ok, err := pk.VerifyFile(fn, sig)
|
||||
if err != nil {
|
||||
Die("%s", err)
|
||||
}
|
||||
|
||||
exit := 0
|
||||
if !ok {
|
||||
exit = 1
|
||||
}
|
||||
|
||||
if !quiet {
|
||||
if ok {
|
||||
fmt.Printf("%s: Signature %s verified\n", fn, sn)
|
||||
} else {
|
||||
fmt.Printf("%s: Signature %s verification failure\n", fn, sn)
|
||||
}
|
||||
}
|
||||
|
||||
os.Exit(exit)
|
||||
}
|
124
tests.sh
124
tests.sh
|
@ -1,124 +0,0 @@
|
|||
#! /usr/bin/env bash
|
||||
# simple round-trip tests to verify the tool
|
||||
# Usage:
|
||||
# $0 [bin=/path/to/sigtool] [tmpdir=/path/to/workdir]
|
||||
|
||||
Z=`basename $0`
|
||||
die() {
|
||||
echo "$Z: $@" 1>&2
|
||||
echo "$Z: Test output in $tmpdir .." 1>&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# cmd line args processing
|
||||
for a in $*; do
|
||||
key=${a%=*}
|
||||
val=${a#*=}
|
||||
case $key in
|
||||
bin)
|
||||
bin=$val
|
||||
;;
|
||||
|
||||
tmpdir)
|
||||
tmpdir=$val
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Ignoring $key .."
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$bin" ]; then
|
||||
arch=`./build --print-arch`
|
||||
bin=./bin/$arch/sigtool
|
||||
|
||||
[ -x $bin ] || ./build || die "can't find & build sigtool"
|
||||
fi
|
||||
|
||||
[ -z "$tmpdir" ] && tmpdir=/tmp/sigtool$$
|
||||
|
||||
mkdir -p $tmpdir || die "can't mkdir $tmpdir"
|
||||
|
||||
# env name for reading the password
|
||||
passenv=FOO
|
||||
|
||||
# this is the password for SKs
|
||||
FOO=bar
|
||||
|
||||
|
||||
#trap "rm -rf $tmpdir" EXIT
|
||||
|
||||
bn=$tmpdir/foo
|
||||
sig=$tmpdir/$Z.sig
|
||||
pk=$bn.pub
|
||||
sk=$bn.key
|
||||
bn2=$tmpdir/bar
|
||||
pk2=$bn2.pub
|
||||
sk2=$bn2.key
|
||||
|
||||
encout=$tmpdir/$Z.enc
|
||||
decout=$tmpdir/$Z.dec
|
||||
|
||||
# exit on any failure
|
||||
set -e
|
||||
|
||||
# Now try with ssh ed25519 keys
|
||||
keygen=`which ssh-keygen`
|
||||
[ -z "$keygen" ] && die "can't find ssh-keygen"
|
||||
|
||||
ssk1=$tmpdir/ssk1
|
||||
spk1=$ssk1.pub
|
||||
|
||||
ssk2=$tmpdir/ssk2
|
||||
spk2=$ssk2.pub
|
||||
|
||||
# first generate two ssh keys
|
||||
$keygen -q -C 'ssk1@foo' -t ed25519 -f $ssk1 -N ""
|
||||
$keygen -q -C 'ssk2@foo' -t ed25519 -f $ssk2 -N ""
|
||||
|
||||
# extract the pk string
|
||||
spk1_str=$(cat $spk1 | awk '{ print $2 }')
|
||||
|
||||
$bin s --no-password $ssk1 -o $sig $0 || die "can't sign with $ssk1"
|
||||
$bin v -q $spk1 $sig $0 || die "can't verify with $spk2"
|
||||
$bin v -q $spk1_str $sig $0 || die "can't verify with $spk2_str"
|
||||
|
||||
$bin e --no-password -o $encout $spk2 $0 || die "can't encrypt to $spk2 with $ssk1"
|
||||
$bin d --no-password -o $decout $ssk2 $encout || die "can't decrypt with $ssk2"
|
||||
|
||||
# cleanup state
|
||||
rm -f $sig $encout $decout
|
||||
|
||||
# generate keys
|
||||
$bin g -E FOO $bn || die "can't gen keypair $pk, $sk"
|
||||
$bin g -E FOO $bn 2>/dev/null && die "overwrote prev keypair"
|
||||
$bin g -E FOO --overwrite $bn || die "can't force gen keypair $pk, $sk"
|
||||
$bin g -E FOO $bn2 || die "can't force gen keypair $pk2, $sk2"
|
||||
|
||||
# extract pk string
|
||||
pk_str=$(cat $pk | grep 'pk:' | sed -e 's/^pk: //g')
|
||||
pk2_str=$(cat $pk2 | grep 'pk:' | sed -e 's/^pk: //g')
|
||||
|
||||
# sign and verify
|
||||
$bin s -E FOO $sk $0 -o $sig || die "can't sign $0"
|
||||
$bin v -q $pk $sig $0 || die "can't verify signature of $0"
|
||||
$bin v -q $pk_str $sig $0 || die "can't verify signature of $0"
|
||||
$bin v -q $pk2 $sig $0 2>/dev/null && die "bad verification with wrong $pk2"
|
||||
$bin v -q $pk2_str $sig $0 2>/dev/null && die "bad verification with wrong $pk2"
|
||||
|
||||
# encrypt/decrypt
|
||||
$bin e -E FOO -o $encout $pk2 $0 || die "can't encrypt to $pk2"
|
||||
$bin d -E FOO -o $decout $sk2 $encout || die "can't decrypt with $sk2"
|
||||
cmp -s $decout $0 || die "decrypted file mismatch with $0"
|
||||
|
||||
# now with sender verification
|
||||
$bin e -E FOO --overwrite -o $encout -s $sk $pk2 $0 || die "can't sender-encrypt to $pk2"
|
||||
$bin d -E FOO --overwrite -o $decout -v $pk $sk2 $encout || die "can't decrypt with $sk2"
|
||||
cmp -s $decout $0 || die "decrypted file mismatch with $0"
|
||||
|
||||
# Only delete if everything worked
|
||||
echo "$Z: All tests pass!"
|
||||
rm -rf $tmpdir
|
||||
|
||||
# vim: tw=100 sw=4 ts=4 expandtab
|
Loading…
Add table
Reference in a new issue