Moved go-sign from external repo to this repo; updated README; added Makefile
This commit is contained in:
parent
191b7a457d
commit
15477d6197
8 changed files with 1083 additions and 139 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -27,7 +27,6 @@ vendor/*
|
||||||
# vendor management
|
# vendor management
|
||||||
vendor/src/*
|
vendor/src/*
|
||||||
vendor/pkg/*
|
vendor/pkg/*
|
||||||
src/*
|
|
||||||
bin/*
|
bin/*
|
||||||
|
|
||||||
.??*.sw?
|
.??*.sw?
|
||||||
|
|
17
Makefile
Normal file
17
Makefile
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
|
||||||
|
pwd = $(shell pwd)
|
||||||
|
GOPATH := $(pwd)/vendor:$(pwd)
|
||||||
|
export GOPATH
|
||||||
|
|
||||||
|
all:
|
||||||
|
mkdir -p bin
|
||||||
|
go get -d .
|
||||||
|
go build -o bin/sigtool .
|
||||||
|
|
||||||
|
test:
|
||||||
|
go test sign
|
||||||
|
clean:
|
||||||
|
rm -f bin/sigtool
|
||||||
|
|
||||||
|
realclean: clean
|
||||||
|
rm -rf vendor
|
166
README.md
Normal file
166
README.md
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
[](https://godoc.org/github.com/opencoff/go-sign)
|
||||||
|
|
||||||
|
# README for sigtool
|
||||||
|
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
It can sign and verify very large files - it prehashes the files
|
||||||
|
with SHA-512 and then signs the SHA-512 checksum.
|
||||||
|
|
||||||
|
All the artifacts produced by this tool are standard YAML files -
|
||||||
|
thus, human readable.
|
||||||
|
|
||||||
|
## How do I build it?
|
||||||
|
With Go 1.5 and later:
|
||||||
|
|
||||||
|
git clone https://github.com/opencoff/sigtool
|
||||||
|
cd sigtool
|
||||||
|
make
|
||||||
|
|
||||||
|
The binary will be in `./sigtool`.
|
||||||
|
|
||||||
|
## How do I use it?
|
||||||
|
Broadly, the tool can:
|
||||||
|
|
||||||
|
- generate new key pairs (public key and private key)
|
||||||
|
- sign a file
|
||||||
|
- verify a file against its signature
|
||||||
|
|
||||||
|
### Generate Key pair
|
||||||
|
To start with, you generate a new key pair (a public key used for
|
||||||
|
verification and a private key used for signing). e.g.,
|
||||||
|
|
||||||
|
sigtool gen /tmp/testkey
|
||||||
|
|
||||||
|
The tool then generates */tmp/testkey.pub* and */tmp/testkey.key*. The secret
|
||||||
|
key (".key") can optionally be encrypted with a user supplied pass
|
||||||
|
phrase - which the user has to enter via interactive prompt:
|
||||||
|
|
||||||
|
sigtool gen -p /tmp/testkey
|
||||||
|
|
||||||
|
### Sign a file
|
||||||
|
Signing a file requires the user to provide a previously generated
|
||||||
|
Ed25519 private key. The signature (YAML) is written to STDOUT.
|
||||||
|
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 with a user pass phrase:
|
||||||
|
|
||||||
|
sigtool sign -p /tmp/testkey.key archive.tar.gz
|
||||||
|
|
||||||
|
|
||||||
|
The signature can also be written directly to a user supplied output
|
||||||
|
file.
|
||||||
|
|
||||||
|
sigtool sign -p -o archive.sig /tmp/testkey.key archive.tar.gz
|
||||||
|
|
||||||
|
|
||||||
|
### Verify a signature against a file
|
||||||
|
Verifying a signature of a file requires the user to supply three
|
||||||
|
pieces of information:
|
||||||
|
|
||||||
|
- the Ed25519 public key to be used for verification
|
||||||
|
- the Ed25519 signature
|
||||||
|
- the file whose signature must be verified
|
||||||
|
|
||||||
|
e.g., to verify the signature of *archive.tar.gz* against
|
||||||
|
*testkey.pub* using the signature *archive.sig*
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
As an additional security measure, the user supplied pass phrase is
|
||||||
|
hashed with SHA512.
|
||||||
|
|
||||||
|
## Understanding the Code
|
||||||
|
`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.
|
||||||
|
|
||||||
|
The signature file contains a hash of the public key - so that at
|
||||||
|
verification time, the right private key may be used (in situations
|
||||||
|
where there are lots of keys).
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Example of Keys, Signature
|
||||||
|
|
||||||
|
### Ed25519 Public Key
|
||||||
|
A serialized Ed25519 public key looks like so:
|
||||||
|
|
||||||
|
pk: uxpDh+gqXojAmxA/6vxZHzA+Uk+8wogUwvEhPBlWgvo=
|
||||||
|
|
||||||
|
### Ed25519 Private Key
|
||||||
|
And, a serialized Ed25519 private key looks like so:
|
||||||
|
|
||||||
|
esk: t3vfqHbgUiA733KKPymFjWT8DdnBEkiMfsDHolPUdQWpvVn/F1Z4J6KYV3M5rGO9xgKxh5RAmqt+6LKgOiJAMQ==
|
||||||
|
salt: pPHKG55UJYtJ5wU0G9hBvNQJ0DvT0a7T4Fmj4aPB84s=
|
||||||
|
algo: scrypt-sha256
|
||||||
|
verify: JvjRjJMKhJhBmZngC3Pvq7x3KCLKt7gar1AAz7HB4qM=
|
||||||
|
Z: 131072
|
||||||
|
r: 16
|
||||||
|
p: 1
|
||||||
|
|
||||||
|
The Ed25519 private key is encrypted using Scrypt password hashing
|
||||||
|
mechanism. A user supplied passphrase to protect the private key
|
||||||
|
is first pre-hashed using SHA-512 before being used in
|
||||||
|
```scrypt()```. In pseudo code, this operation looks like below:
|
||||||
|
|
||||||
|
passphrase = get_user_passphrase()
|
||||||
|
hpass = SHA512(passphrase)
|
||||||
|
salt = randombytes(32)
|
||||||
|
xorkey = Scrypt(hpass, salt, N, r, p)
|
||||||
|
verify = SHA256(salt, xorkey)
|
||||||
|
esk = ed25519_private_key ^ xorkey
|
||||||
|
|
||||||
|
Where, ```N```, ```r```, ```p``` are Scrypt parameters. In our
|
||||||
|
implementation:
|
||||||
|
|
||||||
|
N = 131072
|
||||||
|
r = 16
|
||||||
|
p = 1
|
||||||
|
|
||||||
|
```verify``` is used during the decryption of the Ed25519 private
|
||||||
|
key - *before* actually doing the "xor" operation. This check
|
||||||
|
ensures that the supplied passphrase yields the same value as
|
||||||
|
```verify```.
|
||||||
|
|
||||||
|
### Ed25519 Signature
|
||||||
|
A generated signature looks like below after serialization:
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Licensing Terms
|
||||||
|
The tool and code is licensed under the terms of the
|
||||||
|
GNU Public License v2.0 (strictly v2.0). If you need a commercial
|
||||||
|
license or a different license, please get in touch with me.
|
||||||
|
|
||||||
|
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
|
137
README.rst
137
README.rst
|
@ -1,137 +0,0 @@
|
||||||
==================
|
|
||||||
README for sigtool
|
|
||||||
==================
|
|
||||||
|
|
||||||
|
|
||||||
What is this?
|
|
||||||
=============
|
|
||||||
This is a tool to generate, sign and verify Ed25519 signatures. In
|
|
||||||
many ways, it is like like OpenBSD's signify_ -- except written in Golang
|
|
||||||
and designed to be 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.
|
|
||||||
|
|
||||||
All the artifacts produced by this tool are standard YAML files -
|
|
||||||
thus, human readable.
|
|
||||||
|
|
||||||
How do I build it?
|
|
||||||
==================
|
|
||||||
With Go 1.5 and later::
|
|
||||||
|
|
||||||
mkdir sigtool
|
|
||||||
cd sigtool
|
|
||||||
env GOPATH=`pwd` go get -u github.com/opencoff/sigtool
|
|
||||||
|
|
||||||
The binary will be in ``bin/sigtool``.
|
|
||||||
|
|
||||||
How do I use it?
|
|
||||||
================
|
|
||||||
Broadly, the tool can:
|
|
||||||
|
|
||||||
- generate new key pairs (public key and private key)
|
|
||||||
- sign a file
|
|
||||||
- verify a file against its signature
|
|
||||||
|
|
||||||
Generate Key pair
|
|
||||||
-----------------
|
|
||||||
To start with, you generate a new key pair (a public key used for
|
|
||||||
verification and a private key used for signing). e.g., ::
|
|
||||||
|
|
||||||
sigtool gen /tmp/testkey
|
|
||||||
|
|
||||||
The tool then generates */tmp/testkey.pub* and */tmp/testkey.key*. The secret
|
|
||||||
key (".key") can optionally be encrypted with a user supplied pass
|
|
||||||
phrase - which the user has to enter via interactive prompt::
|
|
||||||
|
|
||||||
sigtool gen -p /tmp/testkey
|
|
||||||
|
|
||||||
Sign a file
|
|
||||||
-----------
|
|
||||||
Signing a file requires the user to provide a previously generated
|
|
||||||
Ed25519 private key. The signature (YAML) is written to STDOUT.
|
|
||||||
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 with a user pass phrase::
|
|
||||||
|
|
||||||
sigtool sign -p /tmp/testkey.key archive.tar.gz
|
|
||||||
|
|
||||||
|
|
||||||
The signature can also be written directly to a user supplied output
|
|
||||||
file.::
|
|
||||||
|
|
||||||
sigtool sign -p -o archive.sig /tmp/testkey.key archive.tar.gz
|
|
||||||
|
|
||||||
|
|
||||||
Verify a signature against a file
|
|
||||||
---------------------------------
|
|
||||||
Verifying a signature of a file requires the user to supply three
|
|
||||||
pieces of information:
|
|
||||||
|
|
||||||
- the Ed25519 public key to be used for verification
|
|
||||||
- the Ed25519 signature
|
|
||||||
- the file whose signature must be verified
|
|
||||||
|
|
||||||
e.g., to verify the signature of *archive.tar.gz* against
|
|
||||||
*testkey.pub* using the signature *archive.sig*::
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
As an additional security measure, the user supplied pass phrase is
|
|
||||||
hashed with SHA512.
|
|
||||||
|
|
||||||
In Pseudo-code::
|
|
||||||
|
|
||||||
passwd = get_user_password()
|
|
||||||
hpass = SHA512(passwd)
|
|
||||||
salt = randombytes(32)
|
|
||||||
xorkey = Scrypt(hpass, salt, N, r, p)
|
|
||||||
|
|
||||||
cksum = SHA256(salt, xorkey)
|
|
||||||
enckey = ed25519_private_key ^ xorkey
|
|
||||||
|
|
||||||
And, ``cksum``, ``enckey`` are the entities stored as on-disk
|
|
||||||
private key.
|
|
||||||
|
|
||||||
The Scrypt parameters used by the ``sign`` library are:
|
|
||||||
|
|
||||||
- N: 131072
|
|
||||||
- r: 16
|
|
||||||
- p: 1
|
|
||||||
|
|
||||||
Understanding the Code
|
|
||||||
======================
|
|
||||||
The tool uses a companion library that manages the keys and
|
|
||||||
signatures. It is part of a growing set of Golang libraries that are
|
|
||||||
useful in multiple projects. You can find them on github_.
|
|
||||||
|
|
||||||
The core code is in the ``sign`` library. This library is
|
|
||||||
can be reused in any of your projects.
|
|
||||||
|
|
||||||
.. _github: https://github.com/opencoff/go-sign/
|
|
||||||
|
|
||||||
Licensing Terms
|
|
||||||
===============
|
|
||||||
The tool is licensed under the terms of the GNU Public License v2.0
|
|
||||||
(strictly v2.0). If you need a commercial license or a different
|
|
||||||
license, please get in touch with me.
|
|
||||||
|
|
||||||
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
|
|
|
@ -21,8 +21,10 @@ import (
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
flag "github.com/ogier/pflag"
|
flag "github.com/ogier/pflag"
|
||||||
"github.com/opencoff/go-sign"
|
|
||||||
"github.com/opencoff/go-utils"
|
"github.com/opencoff/go-utils"
|
||||||
|
|
||||||
|
// My signing library
|
||||||
|
"sign"
|
||||||
)
|
)
|
||||||
|
|
||||||
// This will be filled in by "build"
|
// This will be filled in by "build"
|
||||||
|
|
46
src/sign/.gitignore
vendored
Normal file
46
src/sign/.gitignore
vendored
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
||||||
|
*.test
|
||||||
|
*.prof
|
||||||
|
|
||||||
|
bin/*
|
||||||
|
.*.sw?
|
||||||
|
.idea
|
||||||
|
logs/*
|
||||||
|
|
||||||
|
# gg ignores
|
||||||
|
vendor/src/*
|
||||||
|
vendor/pkg/*
|
||||||
|
servers.iml
|
||||||
|
*.DS_Store
|
||||||
|
|
||||||
|
# vagrant ignores
|
||||||
|
tools/vagrant/.vagrant
|
||||||
|
tools/vagrant/adsrv-conf/.frontend
|
||||||
|
tools/vagrant/adsrv-conf/.bidder
|
||||||
|
tools/vagrant/adsrv-conf/.transcoder
|
||||||
|
tools/vagrant/redis-cluster-conf/7777/nodes.conf
|
||||||
|
tools/vagrant/redis-cluster-conf/7778/nodes.conf
|
||||||
|
tools/vagrant/redis-cluster-conf/7779/nodes.conf
|
||||||
|
*.aof
|
||||||
|
*.rdb
|
541
src/sign/sign.go
Normal file
541
src/sign/sign.go
Normal file
|
@ -0,0 +1,541 @@
|
||||||
|
// sign.go -- Ed25519 keys and signature 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 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 "golang.org/x/crypto/ed25519"
|
||||||
|
"golang.org/x/crypto/scrypt"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
|
"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 // 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) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
return &Signature{Sig: sig, pkhash: pkh[:16]}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read and sign a file
|
||||||
|
//
|
||||||
|
// We calculate the signature differently here: We first calculate
|
||||||
|
// the SHA-512 checksum of the file and its size. We sign the
|
||||||
|
// checksum.
|
||||||
|
func (sk *PrivateKey) SignFile(fn string) (*Signature, error) {
|
||||||
|
|
||||||
|
ck, err := fileCksum(fn, sha512.New())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return sk.SignMessage(ck, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Signature Methods --
|
||||||
|
|
||||||
|
// Read serialized signature from file 'fn' and construct a
|
||||||
|
// Signature object
|
||||||
|
func ReadSignature(fn string) (*Signature, error) {
|
||||||
|
yml, err := ioutil.ReadFile(fn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return MakeSignature(yml)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse serialized signature from bytes 'b' and construct a
|
||||||
|
// Signature object
|
||||||
|
func MakeSignature(b []byte) (*Signature, error) {
|
||||||
|
var ss signature
|
||||||
|
err := yaml.Unmarshal(b, &ss)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't parse YAML signature: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b64 := base64.StdEncoding.DecodeString
|
||||||
|
|
||||||
|
s, err := b64(ss.Signature)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't decode Base64:Signature <%s>: %s", ss.Signature, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := b64(ss.Pkhash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't decode Base64:Pkhash <%s>: %s", ss.Pkhash, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Signature{Sig: s, pkhash: p}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, 0644)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsPKMatch returns true if public key 'pk' can potentially validate
|
||||||
|
// the signature. It does this by comparing the hash of 'pk' against
|
||||||
|
// 'Pkhash' of 'sig'.
|
||||||
|
func (sig *Signature) IsPKMatch(pk *PublicKey) bool {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify a signature 'sig' for a pre-calculated checksum 'ck' against public key 'pk'
|
||||||
|
// Return True if signature matches, False otherwise
|
||||||
|
func (pk *PublicKey) VerifyMessage(ck []byte, sig *Signature) (bool, error) {
|
||||||
|
|
||||||
|
x := Ed.PublicKey(pk.Pk)
|
||||||
|
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:
|
310
src/sign/sign_test.go
Normal file
310
src/sign/sign_test.go
Normal file
|
@ -0,0 +1,310 @@
|
||||||
|
// sign_test.go -- Test harness 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/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)
|
||||||
|
|
||||||
|
var b [10]byte
|
||||||
|
|
||||||
|
dn := os.TempDir()
|
||||||
|
rand.Read(b[:])
|
||||||
|
|
||||||
|
tmp := path.Join(dn, fmt.Sprintf("%x", b[:]))
|
||||||
|
err := os.MkdirAll(tmp, 0755)
|
||||||
|
assert(err == nil, fmt.Sprintf("mkdir -p %s: %s", tmp, err))
|
||||||
|
|
||||||
|
//t.Logf("Tempdir is %s", tmp)
|
||||||
|
return tmp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return true if file exists, false otherwise
|
||||||
|
func fileExists(fn string) bool {
|
||||||
|
st, err := os.Stat(fn)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if st.Mode().IsRegular() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const badsk string = `
|
||||||
|
esk: q8AP3/6C5F0zB8CLiuJsidx2gJYmrnyOmuoazEbKL5Uh+Jn/Zgw85fTbYfhjcbt48CJejBzsgPYRYR7wWECFRA==
|
||||||
|
salt: uIdTQZotfnkaLkth9jsHvoQKMWdNZuE7dgVNADrRoeY=
|
||||||
|
algo: scrypt-sha256
|
||||||
|
verify: AOFLLC6h29+mvstWtMU1/zZFwHLBMMiI4mlW9DHpYdM=
|
||||||
|
Z: 65536
|
||||||
|
r: 8
|
||||||
|
p: 1
|
||||||
|
`
|
||||||
|
|
||||||
|
// #1. Create new key pair, and read them back.
|
||||||
|
func Test0(t *testing.T) {
|
||||||
|
assert := newAsserter(t)
|
||||||
|
|
||||||
|
kp, err := NewKeypair()
|
||||||
|
assert(err == nil, "NewKeyPair() fail")
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
// We must find these two files
|
||||||
|
assert(fileExists(pkf), "missing pkf")
|
||||||
|
assert(fileExists(skf), "missing skf")
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
|
||||||
|
// -ditto- for Sk
|
||||||
|
sk, err := ReadPrivateKey(pkf, "")
|
||||||
|
assert(err != nil, "bad SK ReadSK fail")
|
||||||
|
|
||||||
|
sk, err = ReadPrivateKey(skf, "")
|
||||||
|
assert(err != nil, "ReadSK() empty pw fail")
|
||||||
|
|
||||||
|
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, "write badsk")
|
||||||
|
|
||||||
|
sk, err = ReadPrivateKey(badf, "abc")
|
||||||
|
assert(err != nil, "badsk read fail")
|
||||||
|
|
||||||
|
// Finally, with correct password it should work.
|
||||||
|
sk, err = ReadPrivateKey(skf, "abc")
|
||||||
|
assert(err == nil, "ReadSK() correct pw fail")
|
||||||
|
|
||||||
|
// And, deserialized keys should be identical
|
||||||
|
assert(byteEq(pk.Pk, kp.Pub.Pk), "pkbytes unequal")
|
||||||
|
assert(byteEq(sk.Sk, kp.Sec.Sk), "skbytes unequal")
|
||||||
|
|
||||||
|
os.RemoveAll(dn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// #2. Create new key pair, sign a rand buffer and verify
|
||||||
|
func Test1(t *testing.T) {
|
||||||
|
assert := newAsserter(t)
|
||||||
|
kp, err := NewKeypair()
|
||||||
|
assert(err == nil, "NewKeyPair() fail")
|
||||||
|
|
||||||
|
var ck [64]byte // simulates sha512 sum
|
||||||
|
|
||||||
|
rand.Read(ck[:])
|
||||||
|
|
||||||
|
pk := &kp.Pub
|
||||||
|
sk := &kp.Sec
|
||||||
|
|
||||||
|
ss, err := sk.SignMessage(ck[:], "")
|
||||||
|
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
|
||||||
|
rand.Read(ss.pkhash[:])
|
||||||
|
assert(!ss.IsPKMatch(pk), "corrupt pk match fail")
|
||||||
|
|
||||||
|
// Incorrect checksum == should fail verification
|
||||||
|
ok, err := pk.VerifyMessage(ck[:16], ss)
|
||||||
|
assert(err == nil, "bad ck verify err fail")
|
||||||
|
assert(!ok, "bad ck verify fail")
|
||||||
|
|
||||||
|
// proper checksum == should work
|
||||||
|
ok, err = pk.VerifyMessage(ck[:], ss)
|
||||||
|
assert(err == nil, "verify err")
|
||||||
|
assert(ok, "verify fail")
|
||||||
|
|
||||||
|
// Now sign a file
|
||||||
|
dn := tempdir(t)
|
||||||
|
bn := fmt.Sprintf("%s/k", dn)
|
||||||
|
|
||||||
|
pkf := fmt.Sprintf("%s.pub", bn)
|
||||||
|
skf := fmt.Sprintf("%s.key", bn)
|
||||||
|
|
||||||
|
err = kp.Serialize(bn, "", "")
|
||||||
|
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")
|
||||||
|
|
||||||
|
for i := 0; i < 8; i++ {
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
fd.Sync()
|
||||||
|
fd.Close()
|
||||||
|
|
||||||
|
sig, err := sk.SignFile(zf)
|
||||||
|
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")
|
||||||
|
assert(ok, "file.dat verify false")
|
||||||
|
|
||||||
|
// Now, serialize the signature and read it back
|
||||||
|
sf := fmt.Sprintf("%s/file.sig", dn)
|
||||||
|
err = sig.SerializeFile(sf, "")
|
||||||
|
assert(err == nil, "sig serialize fail")
|
||||||
|
|
||||||
|
s2, err := ReadSignature(sf)
|
||||||
|
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")
|
||||||
|
|
||||||
|
n := st.Size()
|
||||||
|
assert(n == 8192*8, "file.dat size fail")
|
||||||
|
|
||||||
|
os.Truncate(zf, n-1)
|
||||||
|
|
||||||
|
st, err = os.Stat(zf)
|
||||||
|
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")
|
||||||
|
assert(!ok, "file.dat corrupt verify false")
|
||||||
|
|
||||||
|
os.RemoveAll(dn)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func Benchmark_Keygen(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, _ = NewKeypair()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func Benchmark_Sig(b *testing.B) {
|
||||||
|
var sizes = [...]uint{
|
||||||
|
16,
|
||||||
|
32,
|
||||||
|
64,
|
||||||
|
}
|
||||||
|
|
||||||
|
b.StopTimer()
|
||||||
|
kp, _ := NewKeypair()
|
||||||
|
var sig *Signature
|
||||||
|
for _, sz := range sizes {
|
||||||
|
buf := randbuf(sz)
|
||||||
|
s0 := fmt.Sprintf("%d byte sign", sz)
|
||||||
|
s1 := fmt.Sprintf("%d byte verify", sz)
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
b.Run(s0, func (b *testing.B) {
|
||||||
|
sig = benchSign(b, buf, &kp.Sec)
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Run(s1, func (b *testing.B) {
|
||||||
|
benchVerify(b, buf, sig, &kp.Pub)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchSign(b *testing.B, buf []byte, sk *PrivateKey) (sig *Signature) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
sig, _ = sk.SignMessage(buf, "")
|
||||||
|
}
|
||||||
|
return sig
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchVerify(b *testing.B, buf []byte, sig *Signature, pk *PublicKey) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
pk.VerifyMessage(buf, sig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func randbuf(sz uint) []byte {
|
||||||
|
b := make([]byte, sz)
|
||||||
|
rand.Read(b)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// vim: noexpandtab:ts=8:sw=8:tw=92:
|
Loading…
Add table
Reference in a new issue