Moved go-sign from external repo to this repo; updated README; added Makefile

This commit is contained in:
Sudhi Herle 2018-10-18 17:10:29 +09:00
parent 191b7a457d
commit 15477d6197
8 changed files with 1083 additions and 139 deletions

1
.gitignore vendored
View file

@ -27,7 +27,6 @@ vendor/*
# vendor management
vendor/src/*
vendor/pkg/*
src/*
bin/*
.??*.sw?

17
Makefile Normal file
View 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
View file

@ -0,0 +1,166 @@
[![GoDoc](https://godoc.org/github.com/opencoff/go-sign?status.svg)](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

View file

@ -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

View file

@ -21,8 +21,10 @@ import (
"path"
flag "github.com/ogier/pflag"
"github.com/opencoff/go-sign"
"github.com/opencoff/go-utils"
// My signing library
"sign"
)
// This will be filled in by "build"

46
src/sign/.gitignore vendored Normal file
View 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
View 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
View 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: