First working version of encrypt/decrypt

* use protobuf for encryption-header
* use fixed size file-header (42 bytes) before the encryption-header
* add encryption/decryption contexts
* teach MakePrivateKey() to fixup its internal public key bits
This commit is contained in:
Sudhi Herle 2019-10-17 14:29:01 -07:00
parent 9473c10bfd
commit 21445ba1a1
12 changed files with 1893 additions and 144 deletions

View file

@ -4,11 +4,10 @@ pwd = $(shell pwd)
.PHONY: all test clean realclean
all:
mkdir -p bin
go build -o bin/sigtool .
./build -s
test:
go test ./sign
clean realclean:
rm -f bin/sigtool
rm -rf bin

View file

@ -4,15 +4,54 @@
## What is this?
`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.
`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 sign and verify very large files - it prehashes the files
with SHA-512 and then signs the SHA-512 checksum.
with SHA-512 and then signs the SHA-512 checksum. The keys and signatures
are YAML files and so, human readable.
All the artifacts produced by this tool are standard YAML files -
thus, human readable.
It can encrypt & decrypt files by converting the Ed25519 keys to their
corresponding Curve25519 variants. This elliptic co-ordinate transform
follows [FiloSottile's writeup][2]. The file encryption uses
AES-GCM-256 (AEAD); the input is broken into chunks and each chunk is
AEAD encrypted. The default chunk size is 4MB (4 * 1048576 bytes).
A random 32-byte key is used to actually encrypt the file contents in
AES-GCM mode. This file-encryption key is **wrapped** using the recipient's
public key. Thus, a given input file (or stream) can be encrypted to be
read by multiple recipients - each of whom is identified by their Ed25519
public keys. The file-encryptionb-key can optionally be wrapped using the
sender's Private Key - this authenticates the sender. If this private key is
not provided for the encrypt operation, then `sigtool` generates ephemeral
Curve25519 keys and wraps the file-encryption key using the ephemeral
private key and the recipient's public key.
Every encrypted file starts with a header:
7 byte magic ("SigTool")
1 byte version number
4 byte header length
32 byte SHA256 of the encryption-header
The encryption-header is described as a protobuf file (sign/hdr.proto):
```protobuf
message header {
uint32 chunk_size = 1;
bytes salt = 2;
repeated wrapped_key keys = 3;
}
message wrapped_key {
bytes pk_hash = 1; // hash of Ed25519 PK
bytes pk = 2; // curve25519 PK
bytes nonce = 3; // AEAD nonce
bytes key = 4; // AEAD encrypted key
}
```
## How do I build it?
With Go 1.5 and later:
@ -21,7 +60,9 @@ With Go 1.5 and later:
cd sigtool
make
The binary will be in `./sigtool`.
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).
## How do I use it?
Broadly, the tool can:
@ -29,6 +70,8 @@ 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
@ -73,6 +116,22 @@ e.g., to verify the signature of *archive.tar.gz* against
sigtool verify /tmp/testkey.pub archive.sig archive.tar.gz
### Encrypt a file by authenticating the sender
If the sender wishes to prove to the recipient that they encrypted
a file:
sigtool encrypt -s mykey.key theirkey.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 *theikey.key* can decrypt it. Furthermore, if
the recipient has *mykey.pub*, they can verify that the sender is indeed
who they expect.
### Encrypt a file *without* authenticating the sender
### Decrypt a file
## 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
@ -111,6 +170,8 @@ 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
@ -118,6 +179,7 @@ And, a serialized Ed25519 private key looks like so:
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
@ -146,9 +208,12 @@ ensures that the supplied passphrase yields the same value as
### 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.
@ -163,4 +228,5 @@ See the file ``LICENSE.md`` for the full terms of the license.
## Author
Sudhi Herle <sw@herle.net>
.. _signify: https://www.openbsd.org/papers/bsdcan-signify.html
[1]: https://www.openbsd.org/papers/bsdcan-signify.html
[2]: https://blog.filippo.io/using-ed25519-keys-for-encryption/

373
build Executable file
View file

@ -0,0 +1,373 @@
#! /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=".:sigtool"
# Relative path to protobuf sources
# e.g. src/foo/a.proto
Protobufs="sign/hdr.proto"
# -- DO NOT CHANGE ANYTHING AFTER THIS --
Z=`basename $0`
PWD=`pwd`
Static=0
Dryrun=0
Prodver=0.1
Verbose=0
hostos=$(go env GOHOSTOS) || exit 1
hostcpu=$(go env GOHOSTARCH) || exit 1
[ -f ./version ] && Prodver=$(cat ./version)
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
# build a tool that runs on the host - if needed.
hosttool() {
local tool=$1
local bindir=$2
local src=$3
local p=$(type -P $tool)
if [ -n "$p" ]; then
echo $p
return 0
fi
# from here - we want this dir to find all build artifacts
PATH=$PATH:$bindir
export PATH
p=$bindir/$tool
if [ -x $p ]; then
echo $p
return 0
fi
# build it and stash it in the hostdir
echo "Building tool $tool from $src .."
$e go get -d $src || exit 1
$e go build -o $p $src || exit 1
echo $p
return 0
}
usage() {
cat <<EOF
$0 - A Go production build tool that adds git-repository information,
product version, build-timestamp etc. It supports cross-compilation,
static linking and generating protobuf output.
If needed, it uses the gogo-slick protobuf compiler [github.com/gogo/protobuf].
Build output is in bin/\$OS-\$CPU for a given OS, CPU combination.
Usage: $0
$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: $Progs (source in ./src/)
If ./version is present, its content are used as version number for the binary.
Options:
-h, --help Show this help message and quit
-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]
-x Run in debug/trace mode [False]
EOF
exit 0
}
host=`uname|tr '[A-Z]' '[a-z]'`
export GO15VENDOREXPERIMENT=1
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=
#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
;;
--version=*)
Prodver=$ac_optarg
;;
--test|-t)
Tool=test
;;
--vet)
Tool=vet
;;
-V|--version)
ac_prev=Prodver
;;
-v|--verbose)
Verbose=1
;;
-s|--static)
Static=1
;;
--dry-run|-n)
Dryrun=1
;;
--debug|-x)
set -x
;;
*) # 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
# 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
# This is where build outputs go
Bindir=$PWD/bin/$cross
Hostbindir=$PWD/bin/$hostos-$hostcpu
[ -d $Bindir ] || mkdir -p $Bindir
[ -d $Hostbindir ] || mkdir -p $Hostbindir
# Get git/hg version info for the build
if [ -d "./.hg" ]; then
xrev=$(hg id --id) || exit 1
brev=${xrev%+}
if [ "$brev" != "$xrev" ]; then
rev="hg:${brev}-dirty"
else
rev="hg:${brev}"
fi
elif [ -d "./.git" ]; then
xrev=$(git describe --always --dirty --long --abbrev=12) || exit 1
rev="git:$xrev"
else
rev="UNKNOWN-VER"
echo "$0: Can't find version info" 1>&2
fi
# Do Protobufs if needed
if [ -n "$Protobufs" ]; then
slick=$Hostbindir/protoc-gen-gogoslick
slicksrc=github.com/gogo/protobuf/protoc-gen-gogoslick
pc=$(type -p protoc)
[ -z "$pc" ] && die "Need 'protoc' for building .."
slick=$(hosttool protoc-gen-gogoslick $Hostbindir $slicksrc) || exit 1
#if [ ! -f $slick ]; then
# echo "Building $slick .."
# $e go build -o $slick github.com/gogo/protobuf/protoc-gen-gogoslick || exit 1
#i
PATH=$Hostbindir:$PATH
export PATH
for f in $Protobufs; do
dn=$(dirname $f)
bn=$(basename $f .proto)
of=$dn/${bn}.pb.go
if [ $f -nt $of ]; then
echo "gogoslick: $f -> $of ..."
$e $pc --gogoslick_out=. $f || exit 1
$e gofmt -w $of
fi
done
fi
repover="main.RepoVersion=$rev"
prodver="main.ProductVersion=$Prodver"
date="main.Buildtime=`date -u '+%Y-%m-%dT%H:%M.%SZ'`"
ldflags="-ldflags \"-X $repover -X $prodver -X $date $ldflags\""
vflag=""
[ $Verbose -gt 0 ] && vflag="-v"
case $Tool in
test)
set -- $args
$e go test $vflag "$@"
;;
vet)
set -- $args
$e go vet $vflag "$@"
;;
*) # Default is to build programs
set -- $args
if [ -z "$1" ]; then
all=$Progs
else
all="$@"
fi
echo "Building $rev, $cross $msg .."
for p in $all; do
if echo $p | grep -q ':' ; then
out=${p##*:}
dir=${p%%:*}
else
out=$p
dir=$p
fi
echo " $dir: $out .. "
$e eval go build $vflag -o $Bindir/$out $isuffix "$ldflags" ./$dir || exit 1
done
;;
esac
# vim: ft=sh:expandtab:ts=4:sw=4:tw=84:

View file

@ -18,8 +18,8 @@ import (
"io"
"os"
flag "github.com/opencoff/pflag"
"github.com/opencoff/go-utils"
flag "github.com/opencoff/pflag"
"github.com/opencoff/sigtool/sign"
)
@ -108,7 +108,10 @@ func encrypt(args []string) {
outfd = outf
}
var recip []*sign.PublicKey
en, err := sign.NewEncryptor(sk)
if err != nil {
die("%s", err)
}
for i := 0; i < len(args)-1; i++ {
fn := args[i]
@ -116,11 +119,18 @@ func encrypt(args []string) {
if err != nil {
die("%s", err)
}
recip = append(recip, pk)
err = en.AddRecipient(pk)
if err != nil {
die("%s", err)
}
}
encryptFile(sk, recip, infd, outfd)
err = en.Encrypt(infd, outfd)
if err != nil {
die("%s", err)
}
}
// sigtool decrypt a.key [file] [-o output]
@ -132,11 +142,13 @@ func decrypt(args []string) {
var envpw string
var outfile string
var pubkey string
var pw bool
fs.StringVarP(&outfile, "outfile", "o", "", "Write the output to file `F`")
fs.BoolVarP(&pw, "password", "p", false, "Ask for passphrase to decrypt the private key")
fs.StringVarP(&envpw, "env-password", "", "", "Use passphrase from environment variable `E`")
fs.StringVarP(&pubkey, "verify-sender", "v", "", "Verify that the sender matches public key in `F`")
err := fs.Parse(args)
if err != nil {
@ -153,7 +165,6 @@ func decrypt(args []string) {
var inf *os.File
var pws, infile string
if len(envpw) > 0 {
pws = os.Getenv(envpw)
} else if pw {
@ -169,6 +180,15 @@ func decrypt(args []string) {
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 != "-" {
@ -200,14 +220,21 @@ func decrypt(args []string) {
outfd = outf
}
decryptFile(sk, infd, outfd)
}
d, err := sign.NewDecryptor(infd, pk)
if err != nil {
die("%s", err)
}
func encryptFile(sk *sign.PrivateKey, pks []*sign.PublicKey, infd io.Reader, outfd io.Writer) {
}
err = d.SetPrivateKey(sk)
if err != nil {
die("%s", err)
}
func decryptFile(sk *sign.PrivateKey, infd io.Reader, outfd io.Writer) {
err = d.Decrypt(outfd)
if err != nil {
die("%s", err)
}
}

1
go.mod
View file

@ -3,6 +3,7 @@ module github.com/opencoff/sigtool
go 1.13
require (
github.com/gogo/protobuf v1.3.1
github.com/opencoff/go-utils v0.3.0
github.com/opencoff/pflag v0.3.3
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc

5
go.sum
View file

@ -1,3 +1,7 @@
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/opencoff/go-utils v0.3.0 h1:/TQXjf50o3GSB9MItog5L8Gf4GWJ4B5+rmqjB4g2RZQ=
github.com/opencoff/go-utils v0.3.0/go.mod h1:c+7QUAiCCHcNH6OGvsZ0fviG7cgse8Y3ucg+xy7sGXM=
github.com/opencoff/pflag v0.3.3 h1:yohZkwYGPkB34WXvUQzU5GyLhImnjfePDARUaE8me3U=
@ -11,6 +15,7 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View file

@ -17,98 +17,427 @@ import (
"crypto/aes"
"crypto/cipher"
"crypto/ed25519"
"crypto/sha256"
"crypto/sha512"
"encoding/hex"
"crypto/subtle"
"fmt"
"golang.org/x/crypto/curve25519"
"golang.org/x/crypto/hkdf"
"io"
"math/big"
"strings"
"bytes"
"encoding/binary"
)
// A File-encryption-key wrapped by the Ed25519 public key of the recipient
type WrappedKey struct {
Key []byte // KEK - wrapped with the Curve25519 PK of recipient
Pk []byte // Curve25519 PK used to wrap
PkHash []byte // hash of the corresponding Ed25519 PK
// Encryption chunk size = 4MB
const chunkSize int = 4 * 1048576
const _Magic = "SigTool"
const _MagicLen = len(_Magic)
const _AEADNonceLen = 32
// Encryptor holds the encryption context
type Encryptor struct {
Header
key [32]byte // file encryption key
ae cipher.AEAD
sender *PrivateKey
started bool
buf []byte
}
func hx(b []byte) string {
return hex.EncodeToString(b)
}
// Create a new Encryption context and use the optional private key 'sk' for
// signing any recipient keys. If 'sk' is nil, then ephmeral Curve25519 keys
// are generated and used with recipient's public key.
func NewEncryptor(sk *PrivateKey) (*Encryptor, error) {
func unhx(s string) ([]byte, error) {
return hex.DecodeString(s)
}
e := &Encryptor{
Header: Header{
ChunkSize: uint32(chunkSize),
Salt: make([]byte, _AEADNonceLen),
},
func (w *WrappedKey) ToString() string {
return fmt.Sprintf("(ed25519 to=%x, pk=%x, kek=%x)",
hx(w.PkHash), hx(w.Pk), hx(w.Key))
}
func parseErr(s string, v ...interface{}) error {
return fmt.Errorf(s, v...)
}
// Given an marshalled stream of bytes, return the PubKey, encrypted key
func ParseWrappedKey(s string) (*WrappedKey, error) {
s = strings.TrimSpace(s)
if s[0] != '(' {
return nil, parseErr("missing '(' in wrapped key")
sender: sk,
}
if s[len(s)-1] != ')' {
return nil, parseErr("missing ')' in wrapped key")
}
s = s[1 : len(s)-1]
v := strings.Fields(s)
if len(v) != 3 {
return nil, parseErr("Incorrect number of elements (exp 3, saw %d) in wrapped key", len(v))
}
var w WrappedKey
for _, z := range v {
kw := strings.Split(z, "=")
if len(kw) != 2 {
return nil, parseErr("malformed key=value pair (%s) in wrapped key", z)
}
var err error
switch strings.ToLower(kw[0]) {
case "to":
w.PkHash, err = unhx(kw[1])
case "pk":
w.Pk, err = unhx(kw[1])
case "kek":
w.Key, err = unhx(kw[1])
default:
return nil, parseErr("unknown keyword %s in wrapped key", kw[0])
}
randread(e.key[:])
randread(e.Salt)
aes, err := aes.NewCipher(e.key[:])
if err != nil {
return nil, parseErr("can't parse value for %s in wrapped key", kw[0])
return nil, fmt.Errorf("encrypt: %s", err)
}
e.ae, err = cipher.NewGCMWithNonceSize(aes, _AEADNonceLen)
if err != nil {
return nil, fmt.Errorf("encrypt: %s", err)
}
e.buf = make([]byte, chunkSize + 4 + e.ae.Overhead())
return e, nil
}
// Add a new recipient to this encryption context.
func (e *Encryptor) AddRecipient(pk *PublicKey) error {
if e.started {
return fmt.Errorf("encrypt: can't add new recipient after encryption has started")
}
var w *WrappedKey
var err error
if e.sender != nil {
w, err = e.sender.WrapKey(pk, e.key[:])
} else {
w, err = pk.WrapKeyEphemeral(e.key[:])
}
if err != nil {
return err
}
e.Keys = append(e.Keys, w)
return nil
}
// Begin the encryption process by writing the header
func (e *Encryptor) start(wr io.Writer) error {
msize := e.Size()
// marshal the header and recipients
hdrlen := _MagicLen + 1 + 4 + sha256.Size
buf := make([]byte, hdrlen + msize)
hdrbuf := buf[hdrlen:]
copy(buf[:], []byte(_Magic))
buf[_MagicLen] = 1 // file version#
// The fixed header is the magic _and _ the length of the variable segment.
// So, we capture the length of the variable portion first.
binary.BigEndian.PutUint32(buf[_MagicLen + 1:], uint32(sha256.Size + msize))
// Now marshal the variable portion
_, err := e.MarshalToSizedBuffer(hdrbuf)
if err != nil {
return fmt.Errorf("encrypt: can't marshal header: %s", err)
}
// and calculate the header checksum
cksum := buf[_MagicLen + 1 + 4:]
h := sha256.New()
h.Write(hdrbuf)
h.Sum(cksum[:0])
// Finally write it out
err = fullwrite(buf, wr)
if err != nil {
return fmt.Errorf("encrypt: %s", err)
}
e.started = true
return 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 fmt.Errorf("I/O error: %s", err)
}
n -= m
buf = buf[m:]
}
return nil
}
// Encrypt the input stream 'rd' and write encrypted stream to 'wr'
func (e *Encryptor) Encrypt(rd io.Reader, wr io.Writer) error {
if !e.started {
err := e.start(wr)
if err != nil {
return err
}
}
if len(w.PkHash) != 16 {
return nil, parseErr("invalid PkHash length (exp 16, saw %d) in wrapped key", len(w.PkHash))
buf := make([]byte, e.ChunkSize)
i := 0
for {
n, err := io.ReadAtLeast(rd, buf, int(e.ChunkSize))
if n == 0 {
return nil
}
if n > 0 {
err = e.encrypt(buf[:n], wr, i)
if err != nil {
return err
}
i++
continue
}
if err != nil && err != io.EOF {
return fmt.Errorf("encrypt: I/O read error: %s", err)
}
}
}
// encrypt exactly _one_ block of data
// The nonce for the block is: sha256(salt || chunkLen || block#)
// 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(buf []byte, wr io.Writer, i int) error {
var b [8]byte
var noncebuf [32]byte
binary.BigEndian.PutUint32(b[:4], uint32(e.ae.Overhead() + len(buf)))
binary.BigEndian.PutUint32(b[4:], uint32(i))
h := sha256.New()
h.Write(e.Salt)
h.Write(b[:])
nonce := h.Sum(noncebuf[:0])
copy(e.buf[:4], b[:4])
cbuf := e.buf[4:]
c := e.ae.Seal(cbuf[:0], nonce, buf, b[:])
n := len(c) + 4
err := fullwrite(e.buf[:n], wr)
if err != nil {
return fmt.Errorf("encrypt: %s", err)
}
return nil
}
// Decryptor holds the decryption context
type Decryptor struct {
Header
ae cipher.AEAD
rd io.Reader
buf []byte
// Decrypted key
key []byte
}
// Create a new decryption context and if 'pk' is given, check that it matches
// the sender
func NewDecryptor(rd io.Reader, pk *PublicKey) (*Decryptor, error) {
var b [12]byte
_, err := io.ReadFull(rd, b[:])
if err != nil {
return nil, err
}
if bytes.Compare(b[:_MagicLen], []byte(_Magic)) != 0 {
return nil, fmt.Errorf("decrypt: Not a sigtool encrypted file?")
}
if b[_MagicLen] != 1 {
return nil, fmt.Errorf("decrypt: Unsupported version %d", b[_MagicLen])
}
hdrlen := binary.BigEndian.Uint32(b[_MagicLen+1:])
if hdrlen > 65536 {
return nil, fmt.Errorf("decrypt: header too large (max 65536)")
}
if hdrlen < 32 {
return nil, fmt.Errorf("decrypt: header too small (min 32)")
}
hdr := make([]byte, hdrlen)
_, err = io.ReadFull(rd, hdr)
if err != nil {
return nil, err
}
verify := hdr[:32]
hdr = hdr[32:]
cksum := sha256.Sum256(hdr)
if subtle.ConstantTimeCompare(verify, cksum[:]) == 0 {
return nil, fmt.Errorf("decrypt: header corrupted")
}
d := &Decryptor{
rd: rd,
}
err = d.Header.Unmarshal(hdr)
if err != nil {
return nil, fmt.Errorf("decrypt: decode error: %s", err)
}
if d.ChunkSize == 0 || d.ChunkSize > (16 * 1048576) {
return nil, fmt.Errorf("decrypt: invalid chunkSize %d", d.ChunkSize)
}
if len(d.Salt) != 32 {
return nil, fmt.Errorf("decrypt: invalid nonce length %d", len(d.Salt))
}
if len(d.Keys) == 0 {
return nil, fmt.Errorf("decrypt: no wrapped keys")
}
// sanity check on the wrapped keys
for i, w := range d.Keys {
if len(w.PkHash) != PKHashLength {
return nil, fmt.Errorf("decrypt: wrapped key %d: invalid PkHash", i)
}
if len(w.Pk) != 32 {
return nil, parseErr("invalid Public Key length (exp 32, saw %d) in wrapped key", len(w.Pk))
return nil, fmt.Errorf("decrypt: wrapped key %d: invalid Curve25519 PK", i)
}
if len(w.Key) != 32 {
return nil, parseErr("invalid Key length (exp 32, saw %d) in wrapped key", len(w.Key))
// XXX Default AES-256-GCM Nonce size is 12
if len(w.Nonce) != 12 {
return nil, fmt.Errorf("decrypt: wrapped key %d: invalid Nonce", i)
}
return &w, nil
if len(w.Key) == 0 {
return nil, fmt.Errorf("decrypt: wrapped key %d: missing encrypted key", i)
}
}
d.buf = make([]byte, d.ChunkSize)
if pk != nil {
validSender := false
pkh := pk.Hash()
for _, w := range d.Keys {
if subtle.ConstantTimeCompare(pkh, w.PkHash) == 1 {
validSender = true
}
}
if !validSender {
return nil, fmt.Errorf("decrypt: Can't find sender's public key in the header")
}
}
return d, nil
}
// Use Private Key 'sk' to decrypt the encrypted keys in the header
func (d *Decryptor) SetPrivateKey(sk *PrivateKey) error {
var err error
pkh := sk.PublicKey().Hash()
for i, w := range d.Keys {
if subtle.ConstantTimeCompare(pkh, w.PkHash) == 1 {
d.key, err = w.UnwrapKey(sk)
if err != nil {
return fmt.Errorf("decrypt: can't unwrap key %d: %s", i, err)
}
goto havekey
}
}
return fmt.Errorf("decrypt: Can't find any public key to match the given private key")
havekey:
aes, err := aes.NewCipher(d.key)
if err != nil {
return fmt.Errorf("decrypt: %s", err)
}
d.ae, err = cipher.NewGCMWithNonceSize(aes, _AEADNonceLen)
if err != nil {
return fmt.Errorf("decrypt: %s", err)
}
return nil
}
// Return a list of Wrapped keys in the encrypted file header
func (d *Decryptor) WrappedKeys() []*WrappedKey {
return d.Keys
}
// Decrypt the file and write to 'wr'
func (d *Decryptor) Decrypt(wr io.Writer) error {
if d.key == nil {
return fmt.Errorf("decrypt: wrapped-key not decrypted (missing SetPrivateKey()?")
}
for i := 0; ; i++ {
c, err := d.decrypt(i)
if err != nil {
return err
}
if len(c) == 0 {
return nil
}
if len(c) > 0 {
err = fullwrite(c, wr)
if err != nil {
return fmt.Errorf("decrypt: %s", err)
}
}
}
return nil
}
// Decrypt exactly one chunk of data
func (d *Decryptor) decrypt(i int) ([]byte, error) {
var b [8]byte
var nonceb [32]byte
n, err := io.ReadFull(d.rd, b[:4])
if n == 0 || err == io.EOF {
return nil, nil
}
if err != nil {
return nil, fmt.Errorf("decrypt: can't read chunk %d length: %s", i, err)
}
chunklen := int(binary.BigEndian.Uint32(b[:4]))
binary.BigEndian.PutUint32(b[4:], uint32(i))
h := sha256.New()
h.Write(d.Salt)
h.Write(b[:])
nonce := h.Sum(nonceb[:0])
n, err = io.ReadFull(d.rd, d.buf[:chunklen])
if n == 0 || err == io.EOF {
return nil, nil
}
if err != nil {
return nil, fmt.Errorf("decrypt: can't read chunk %d: %s", i, err)
}
p, err := d.ae.Open(d.buf[:0], nonce, d.buf[:chunklen], b[:])
if err != nil {
return nil, fmt.Errorf("decrypt: can't decrypt chunk %d: %s", i, err)
}
return p, nil
}
// given a file-encryption-key, wrap it in the identity of the recipient 'pk' using our
@ -125,6 +454,23 @@ func (sk *PrivateKey) WrapKey(pk *PublicKey, key []byte) (*WrappedKey, error) {
}
// Unwrap a wrapped key using the private key 'sk'
func (w *WrappedKey) UnwrapKey(sk *PrivateKey) ([]byte, error) {
var shared, theirPK, ourSK [32]byte
pk := sk.PublicKey()
copy(ourSK[:], sk.toCurve25519SK())
copy(theirPK[:], w.Pk)
curve25519.ScalarMult(&shared, &ourSK, &theirPK)
key, err := aeadOpen(w.Key, w.Nonce, shared[:], pk.Pk)
if err != nil {
return nil, err
}
return key, nil
}
// Wrap a shared key with the recipient's public key 'pk' by generating an ephemeral
// Curve25519 keypair. This function does not identify the sender (non-repudiation).
func (pk *PublicKey) WrapKeyEphemeral(key []byte) (*WrappedKey, error) {
@ -144,22 +490,16 @@ func (pk *PublicKey) WrapKeyEphemeral(key []byte) (*WrappedKey, error) {
}
func wrapKey(pk *PublicKey, k, theirPK, shared []byte) (*WrappedKey, error) {
// hkdf or HMAC-sha-256
kek, err := expand(shared[:], pk.Pk)
if err != nil {
return nil, fmt.Errorf("wrap: %s", err)
}
ek, err := aeadSeal(k, kek)
ek, nonce, err := aeadSeal(k, shared[:], pk.Pk)
if err != nil {
return nil, fmt.Errorf("wrap: %s", err)
}
return &WrappedKey{
Key: ek,
Pk: theirPK,
PkHash: pk.hash,
Pk: theirPK,
Nonce: nonce,
Key: ek,
}, nil
}
@ -226,67 +566,65 @@ func expand(shared, pk []byte) ([]byte, error) {
return kek, err
}
func aeadSeal(data, key []byte) ([]byte, error) {
var salt [32]byte
var nonceb [64]byte
randread(salt[:])
h := sha512.New()
h.Write(salt[:])
h.Write(key)
nonce := h.Sum(nonceb[:0])[:32]
aes, err := aes.NewCipher(key)
// seal the data via AEAD after suitably expanding 'shared'
func aeadSeal(data, shared, pk []byte) ([]byte, []byte, error) {
kek, err := expand(shared[:], pk)
if err != nil {
return nil, err
return nil, nil, fmt.Errorf("wrap: %s", err)
}
ae, err := cipher.NewGCMWithNonceSize(aes, len(nonce))
aes, err := aes.NewCipher(kek)
if err != nil {
return nil, err
return nil, nil, fmt.Errorf("wrap: %s", err)
}
ae, err := cipher.NewGCM(aes)
if err != nil {
return nil, nil, fmt.Errorf("wrap: %s", err)
}
noncesize := ae.NonceSize()
tagsize := ae.Overhead()
buf := make([]byte, tagsize + len(kek))
nonce := make([]byte, noncesize)
randread(nonce)
out := ae.Seal(buf[:0], nonce, data, nil)
return out, nonce, nil
}
func aeadOpen(data, nonce, shared, pk []byte) ([]byte, error) {
// hkdf or HMAC-sha-256
kek, err := expand(shared, pk)
if err != nil {
return nil, fmt.Errorf("unwrap: %s", err)
}
aes, err := aes.NewCipher(kek)
if err != nil {
return nil, fmt.Errorf("unwrap: %s", err)
}
ae, err := cipher.NewGCM(aes)
if err != nil {
return nil, fmt.Errorf("unwrap: %s", err)
}
want := 32 + ae.Overhead()
if len(data) != want {
return nil, fmt.Errorf("unwrap: incorrect decrypt bytes (need %d, saw %d)", want, len(data))
}
c, err := ae.Open(data[:0], nonce, data, nil)
if err != nil {
return nil, fmt.Errorf("unwrap: %s", err)
}
c := ae.Seal(nil, nonce, data, nil)
c = append(c, salt[:]...)
return c, nil
}
func aeadOpen(data, key []byte) ([]byte, error) {
var nonceb [64]byte
// GCM tag: 16 bytes
// salt: 32 bytes
// last 32 bytes: salt
n := len(data)
if n < (32 + 16) {
return nil, fmt.Errorf("aead: too few decrypt bytes (min 48, saw %d)", n)
}
salt := data[n-32:]
data = data[:n-32]
h := sha512.New()
h.Write(salt)
h.Write(key)
nonce := h.Sum(nonceb[:0])[:32]
aes, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
ae, err := cipher.NewGCMWithNonceSize(aes, len(nonce))
if err != nil {
return nil, err
}
c, err := ae.Open(nil, nonce, data, nil)
if err != nil {
return nil, err
}
return c, nil
}
func clamp(k []byte) []byte {
k[0] &= 248

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

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

23
sign/hdr.proto Normal file
View file

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

View file

@ -77,6 +77,9 @@ type Signature struct {
const sk_algo = "scrypt-sha256"
const sig_algo = "sha512-ed25519"
// Length of Ed25519 Public Key Hash
const PKHashLength = 16
// Scrypt parameters
const _N = 1 << 17
const _r = 16
@ -132,7 +135,7 @@ type signature struct {
func pkhash(pk []byte) []byte {
z := sha256.Sum256(pk)
return z[:16]
return z[:PKHashLength]
}
// Generate a new Ed25519 keypair
@ -220,7 +223,6 @@ func MakePrivateKey(yml []byte, pw string) (*PrivateKey, error) {
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))
@ -240,11 +242,24 @@ func MakePrivateKey(yml []byte, pw string) (*PrivateKey, error) {
}
// Everything works. Now, decode the key
sk.Sk = make([]byte, len(esk.Esk))
skb := make([]byte, len(esk.Esk))
for i := 0; i < len(esk.Esk); i++ {
sk.Sk[i] = esk.Esk[i] ^ xork[i]
skb[i] = esk.Esk[i] ^ xork[i]
}
edsk := Ed.PrivateKey(skb)
edpk := edsk.Public().(Ed.PublicKey)
pk := &PublicKey{
Pk: []byte(edpk),
hash: pkhash([]byte(edpk)),
}
sk := &PrivateKey{
Sk: skb,
pk: pk,
}
return sk, nil
}

View file

@ -306,6 +306,8 @@ 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))

1
version Normal file
View file

@ -0,0 +1 @@
0.2.0