Refactored the core signing & encryption library, teach sigtool to use safe I/O.

* Added new SafeFile (io.WriteCloser) class + methods to atomically write a file.
* Teach core lib to use SafeFile for all file I/O
* Teach sigtool to use SafeFile for all file I/O
* Cleaned up the public interfaces of sign/ to be more coherent:
   - with uniform APIs for marshaling, unmarshaling, serialization.
   - removed KeyPair class/interface and stick to PrivateKey as the primary
     interface.
* collected common rand utility functions into rand.go
* Teach sigtool to NOT overwrite existing output files (keys, signatures etc.)
* Teach sigtool to use a new --overwrite option for every command that creates
  files (generate, sign, encrypt, decrypt)
* encrypt/decrypt will try to use the input file mode/perm where possible
  (unless input is stdin).
* Added more tests
This commit is contained in:
Sudhi Herle 2022-04-29 21:36:39 +05:30
parent f180079586
commit 42bbe5ddeb
12 changed files with 745 additions and 466 deletions

View file

@ -263,6 +263,9 @@ etc.
* `src/ssh.go` contains code to parse SSH Ed25519 key files
* `src/stream.go` contains code that provides an `io.Reader` and `io.WriteCloser` interface
for encryption and decryption.
* `tests.sh` simple round trip test using the tool; this is in addition to the tests in
`sign/`.
The generated keys and signatures are proper YAML files and human
readable.
@ -275,6 +278,11 @@ Signatures on large files are calculated efficiently by reading them
in memory mapped mode (```mmap(2)```) and hashing the file contents
using SHA-512. The Ed25519 signature is calculated on the file-hash.
### Tests
The core library in `sign/` has extensive tests to verify signing and encryption.
Additionally, a simple shell script `tests.sh` does a full roundtrip of tests
using `sigtool`.
## Example of Keys, Signature
### Ed25519 Public Key

View file

@ -36,14 +36,15 @@ func encrypt(args []string) {
var outfile string
var keyfile string
var envpw string
var nopw bool
var nopw, force bool
var blksize uint64
fs.StringVarP(&outfile, "outfile", "o", "", "Write the output to file `F`")
fs.StringVarP(&keyfile, "sign", "s", "", "Sign using private key `S`")
fs.BoolVarP(&nopw, "no-password", "", false, "Don't ask for passphrase to decrypt the private key")
fs.StringVarP(&envpw, "env-password", "", "", "Use passphrase from environment variable `E`")
fs.StringVarP(&envpw, "env-password", "E", "", "Use passphrase from environment variable `E`")
fs.SizeVarP(&blksize, "block-size", "B", 128*1024, "Use `S` as the encryption block size")
fs.BoolVarP(&force, "overwrite", "", false, "Overwrite the output file if it exists")
err := fs.Parse(args)
if err != nil {
@ -114,26 +115,32 @@ func encrypt(args []string) {
}
if len(outfile) > 0 && outfile != "-" {
var mode os.FileMode = 0600 // conservative output mode
if inf != nil {
ost, err := os.Stat(outfile)
if err != nil {
var err error
var ist, ost os.FileInfo
if ost, err = os.Stat(outfile); err != nil {
die("can't stat %s: %s", outfile, err)
}
ist, err := inf.Stat()
if err != nil {
if ist, err = inf.Stat(); err != nil {
die("can't stat %s: %s", infile, err)
}
if os.SameFile(ist, ost) {
die("won't create output file: same as input file!")
}
mode = ist.Mode()
}
outf := mustOpen(outfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC)
defer outf.Close()
outfd = outf
sf, err := sign.NewSafeFile(outfile, force, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode)
if err != nil {
die("%s", err)
}
defer sf.Abort()
outfd = sf
}
en, err := sign.NewEncryptor(sk, blksize)
@ -178,6 +185,7 @@ func encrypt(args []string) {
if err != nil {
die("%s", err)
}
outfd.Close()
}
type nullWriter struct{}
@ -202,13 +210,14 @@ func decrypt(args []string) {
var envpw string
var outfile string
var pubkey string
var nopw, test bool
var nopw, test, force bool
fs.StringVarP(&outfile, "outfile", "o", "", "Write the output to file `F`")
fs.BoolVarP(&nopw, "no-password", "", false, "Don't ask for passphrase to decrypt the private key")
fs.StringVarP(&envpw, "env-password", "", "", "Use passphrase from environment variable `E`")
fs.StringVarP(&envpw, "env-password", "E", "", "Use passphrase from environment variable `E`")
fs.StringVarP(&pubkey, "verify-sender", "v", "", "Verify that the sender matches public key in `F`")
fs.BoolVarP(&test, "test", "t", false, "Test the encrypted file against the given key without writing to output")
fs.BoolVarP(&force, "overwrite", "", false, "Overwrite the output file if it exists")
err := fs.Parse(args)
if err != nil {
@ -221,7 +230,7 @@ func decrypt(args []string) {
}
var infd io.Reader = os.Stdin
var outfd io.Writer = os.Stdout
var outfd io.WriteCloser = os.Stdout
var inf *os.File
var infile string
@ -268,24 +277,30 @@ func decrypt(args []string) {
if test {
outfd = &nullWriter{}
} else if len(outfile) > 0 && outfile != "-" {
var mode os.FileMode = 0600 // conservative mode
if inf != nil {
ost, err := os.Stat(outfile)
if err != nil {
var ist, ost os.FileInfo
var err error
if ost, err = os.Stat(outfile); err != nil {
die("can't stat %s: %s", outfile, err)
}
ist, err := inf.Stat()
if err != nil {
if ist, err = inf.Stat(); err != nil {
die("can't stat %s: %s", infile, err)
}
if os.SameFile(ist, ost) {
die("won't create output file: same as input file!")
}
mode = ist.Mode()
}
outf := mustOpen(outfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC)
defer outf.Close()
outfd = outf
sf, err := sign.NewSafeFile(outfile, force, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode)
if err != nil {
die("%s", err)
}
defer sf.Abort()
outfd = sf
}
d, err := sign.NewDecryptor(infd)
@ -306,14 +321,16 @@ func decrypt(args []string) {
warn("%s: Missing sender Public Key; can't authenticate sender ..", fn)
}
err = d.Decrypt(outfd)
if err != nil {
if err = d.Decrypt(outfd); err != nil {
die("%s", err)
}
outfd.Close()
if test {
warn("Enc file OK")
}
}
func encryptUsage(fs *flag.FlagSet) {

View file

@ -605,7 +605,7 @@ func (d *Decryptor) verifySender(key []byte, sk *PrivateKey, senderPK *PublicKey
// Wrap data encryption key 'k' with the sender's PK and our ephemeral curve SK
// basically, we do a scalarmult: Ephemeral encryption/decryption SK x receiver PK
func (e *Encryptor) wrapKey(pk *PublicKey) (*pb.WrappedKey, error) {
rxPK := pk.toCurve25519PK()
rxPK := pk.ToCurve25519PK()
dkek, err := curve25519.X25519(e.encSK, rxPK)
if err != nil {
return nil, fmt.Errorf("wrap: %w", err)
@ -637,7 +637,7 @@ func (e *Encryptor) wrapKey(pk *PublicKey) (*pb.WrappedKey, error) {
// Unwrap a wrapped key using the receivers Ed25519 secret key 'sk' and
// senders ephemeral PublicKey
func (d *Decryptor) unwrapKey(w *pb.WrappedKey, sk *PrivateKey) ([]byte, error) {
ourSK := sk.toCurve25519SK()
ourSK := sk.ToCurve25519SK()
dkek, err := curve25519.X25519(ourSK, d.Pk)
if err != nil {
return nil, fmt.Errorf("unwrap: %w", err)

View file

@ -34,8 +34,10 @@ func (b *Buffer) Close() error {
func TestEncryptSimple(t *testing.T) {
assert := newAsserter(t)
receiver, err := NewKeypair()
assert(err == nil, "receiver keypair gen failed: %s", err)
sk, err := NewPrivateKey()
assert(err == nil, "SK gen failed: %s", err)
pk := sk.PublicKey()
var blkSize int = 1024
var size int = (blkSize * 10)
@ -49,7 +51,7 @@ func TestEncryptSimple(t *testing.T) {
ee, err := NewEncryptor(nil, uint64(blkSize))
assert(err == nil, "encryptor create fail: %s", err)
err = ee.AddRecipient(&receiver.Pub)
err = ee.AddRecipient(pk)
assert(err == nil, "can't add recipient: %s", err)
rd := bytes.NewBuffer(buf)
@ -63,7 +65,7 @@ func TestEncryptSimple(t *testing.T) {
dd, err := NewDecryptor(rd)
assert(err == nil, "decryptor create fail: %s", err)
err = dd.SetPrivateKey(&receiver.Sec, nil)
err = dd.SetPrivateKey(sk, nil)
assert(err == nil, "decryptor can't add SK: %s", err)
wr = Buffer{}
@ -80,8 +82,10 @@ func TestEncryptSimple(t *testing.T) {
func TestEncryptSmallSizes(t *testing.T) {
assert := newAsserter(t)
receiver, err := NewKeypair()
assert(err == nil, "receiver keypair gen failed: %s", err)
sk, err := NewPrivateKey()
assert(err == nil, "SK gen failed: %s", err)
pk := sk.PublicKey()
var blkSize int = 8
var size int = (blkSize * 4)
@ -99,7 +103,7 @@ func TestEncryptSmallSizes(t *testing.T) {
ee, err := NewEncryptor(nil, uint64(blkSize))
assert(err == nil, "encryptor-%d create fail: %s", i, err)
err = ee.AddRecipient(&receiver.Pub)
err = ee.AddRecipient(pk)
assert(err == nil, "encryptor-%d: can't add recipient: %s", i, err)
rd := bytes.NewBuffer(buf)
@ -113,7 +117,7 @@ func TestEncryptSmallSizes(t *testing.T) {
dd, err := NewDecryptor(rd)
assert(err == nil, "decryptor-%d create fail: %s", i, err)
err = dd.SetPrivateKey(&receiver.Sec, nil)
err = dd.SetPrivateKey(sk, nil)
assert(err == nil, "decryptor-%d can't add SK: %s", i, err)
wr = Buffer{}
@ -131,8 +135,10 @@ func TestEncryptSmallSizes(t *testing.T) {
func TestEncryptCorrupted(t *testing.T) {
assert := newAsserter(t)
receiver, err := NewKeypair()
assert(err == nil, "receiver keypair gen failed: %s", err)
sk, err := NewPrivateKey()
assert(err == nil, "SK gen failed: %s", err)
pk := sk.PublicKey()
var blkSize int = 1024
var size int = (blkSize * 23) + randmod(blkSize)
@ -146,7 +152,7 @@ func TestEncryptCorrupted(t *testing.T) {
ee, err := NewEncryptor(nil, uint64(blkSize))
assert(err == nil, "encryptor create fail: %s", err)
err = ee.AddRecipient(&receiver.Pub)
err = ee.AddRecipient(pk)
assert(err == nil, "can't add recipient: %s", err)
rd := bytes.NewReader(buf)
@ -158,6 +164,7 @@ func TestEncryptCorrupted(t *testing.T) {
rb := wr.Bytes()
n := len(rb)
// corrupt the input
for i := 0; i < n; i++ {
j := randint() % n
rb[j] = byte(randint() & 0xff)
@ -173,11 +180,11 @@ func TestEncryptCorrupted(t *testing.T) {
func TestEncryptSenderVerified(t *testing.T) {
assert := newAsserter(t)
sender, err := NewKeypair()
assert(err == nil, "sender keypair gen failed: %s", err)
sender, err := NewPrivateKey()
assert(err == nil, "sender SK gen failed: %s", err)
receiver, err := NewKeypair()
assert(err == nil, "receiver keypair gen failed: %s", err)
receiver, err := NewPrivateKey()
assert(err == nil, "receiver SK gen failed: %s", err)
var blkSize int = 1024
var size int = (blkSize * 23) + randmod(blkSize)
@ -188,10 +195,10 @@ func TestEncryptSenderVerified(t *testing.T) {
buf[i] = byte(i & 0xff)
}
ee, err := NewEncryptor(&sender.Sec, uint64(blkSize))
ee, err := NewEncryptor(sender, uint64(blkSize))
assert(err == nil, "encryptor create fail: %s", err)
err = ee.AddRecipient(&receiver.Pub)
err = ee.AddRecipient(receiver.PublicKey())
assert(err == nil, "can't add recipient: %s", err)
rd := bytes.NewBuffer(buf)
@ -205,14 +212,15 @@ func TestEncryptSenderVerified(t *testing.T) {
dd, err := NewDecryptor(rd)
assert(err == nil, "decryptor create fail: %s", err)
// first send a wrong sender key
randkey, err := NewKeypair()
assert(err == nil, "receiver rand keypair gen failed: %s", err)
randkey, err := NewPrivateKey()
assert(err == nil, "rand SK gen failed: %s", err)
err = dd.SetPrivateKey(&receiver.Sec, &randkey.Pub)
// first send a wrong sender PK
err = dd.SetPrivateKey(receiver, randkey.PublicKey())
assert(err != nil, "decryptor failed to verify sender")
err = dd.SetPrivateKey(&receiver.Sec, &sender.Pub)
// then the correct sender PK
err = dd.SetPrivateKey(receiver, sender.PublicKey())
assert(err == nil, "decryptor can't add SK: %s", err)
wr = Buffer{}
@ -229,8 +237,8 @@ func TestEncryptSenderVerified(t *testing.T) {
func TestEncryptMultiReceiver(t *testing.T) {
assert := newAsserter(t)
sender, err := NewKeypair()
assert(err == nil, "sender keypair gen failed: %s", err)
sender, err := NewPrivateKey()
assert(err == nil, "sender SK gen failed: %s", err)
var blkSize int = 1024
var size int = (blkSize * 23) + randmod(blkSize)
@ -241,17 +249,17 @@ func TestEncryptMultiReceiver(t *testing.T) {
buf[i] = byte(i & 0xff)
}
ee, err := NewEncryptor(&sender.Sec, uint64(blkSize))
ee, err := NewEncryptor(sender, uint64(blkSize))
assert(err == nil, "encryptor create fail: %s", err)
n := 4
rx := make([]*Keypair, n)
rx := make([]*PrivateKey, n)
for i := 0; i < n; i++ {
r, err := NewKeypair()
assert(err == nil, "can't make receiver key %d: %s", i, err)
r, err := NewPrivateKey()
assert(err == nil, "can't make receiver SK %d: %s", i, err)
rx[i] = r
err = ee.AddRecipient(&r.Pub)
err = ee.AddRecipient(r.PublicKey())
assert(err == nil, "can't add recipient %d: %s", i, err)
}
@ -268,7 +276,7 @@ func TestEncryptMultiReceiver(t *testing.T) {
dd, err := NewDecryptor(rd)
assert(err == nil, "decryptor %d create fail: %s", i, err)
err = dd.SetPrivateKey(&rx[i].Sec, &sender.Pub)
err = dd.SetPrivateKey(rx[i], sender.PublicKey())
assert(err == nil, "decryptor can't add SK %d: %s", i, err)
wr = Buffer{}
@ -286,7 +294,7 @@ func TestEncryptMultiReceiver(t *testing.T) {
func TestStreamIO(t *testing.T) {
assert := newAsserter(t)
receiver, err := NewKeypair()
receiver, err := NewPrivateKey()
assert(err == nil, "receiver keypair gen failed: %s", err)
var blkSize int = 1024
@ -301,7 +309,7 @@ func TestStreamIO(t *testing.T) {
ee, err := NewEncryptor(nil, uint64(blkSize))
assert(err == nil, "encryptor create fail: %s", err)
err = ee.AddRecipient(&receiver.Pub)
err = ee.AddRecipient(receiver.PublicKey())
assert(err == nil, "can't add recipient: %s", err)
wr := Buffer{}
@ -334,7 +342,7 @@ func TestStreamIO(t *testing.T) {
dd, err := NewDecryptor(rd)
assert(err == nil, "decryptor create fail: %s", err)
err = dd.SetPrivateKey(&receiver.Sec, nil)
err = dd.SetPrivateKey(receiver, nil)
assert(err == nil, "decryptor can't add SK: %s", err)
rio, err := dd.NewStreamReader()
@ -368,8 +376,8 @@ func TestStreamIO(t *testing.T) {
func TestSmallSizeStreamIO(t *testing.T) {
assert := newAsserter(t)
receiver, err := NewKeypair()
assert(err == nil, "receiver keypair gen failed: %s", err)
receiver, err := NewPrivateKey()
assert(err == nil, "receiver SK gen failed: %s", err)
var blkSize int = 8
var size int = blkSize * 10
@ -387,7 +395,7 @@ func TestSmallSizeStreamIO(t *testing.T) {
ee, err := NewEncryptor(nil, uint64(blkSize))
assert(err == nil, "encryptor create fail: %s", err)
err = ee.AddRecipient(&receiver.Pub)
err = ee.AddRecipient(receiver.PublicKey())
assert(err == nil, "can't add recipient: %s", err)
wr := Buffer{}
@ -420,7 +428,7 @@ func TestSmallSizeStreamIO(t *testing.T) {
dd, err := NewDecryptor(rd)
assert(err == nil, "decryptor create fail: %s", err)
err = dd.SetPrivateKey(&receiver.Sec, nil)
err = dd.SetPrivateKey(receiver, nil)
assert(err == nil, "decryptor can't add SK: %s", err)
rio, err := dd.NewStreamReader()

View file

@ -28,7 +28,6 @@ import (
"encoding/binary"
"fmt"
"hash"
"io"
"io/ioutil"
"math/big"
"os"
@ -64,15 +63,10 @@ type PublicKey struct {
hash []byte
}
// Ed25519 key pair
type Keypair struct {
Sec PrivateKey
Pub PublicKey
}
// Length of Ed25519 Public Key Hash
const PKHashLength = 16
// constants we use in this module
const (
// Scrypt parameters
_N int = 1 << 19
@ -118,55 +112,27 @@ type signature struct {
Signature string `yaml:"signature"`
}
// given a public key, generate a deterministic short-hash of it.
func pkhash(pk []byte) []byte {
z := sha256.Sum256(pk)
return z[:PKHashLength]
}
// Generate a new Ed25519 keypair
func NewKeypair() (*Keypair, error) {
//kp := &Keypair{Sec: PrivateKey{N: 1 << 17, r: 64, p: 1}}
kp := &Keypair{}
sk := &kp.Sec
pk := &kp.Pub
sk.pk = pk
p, s, err := Ed.GenerateKey(rand.Reader)
// NewPrivateKey generates a new Ed25519 private key
func NewPrivateKey() (*PrivateKey, error) {
pkb, skb, err := Ed.GenerateKey(rand.Reader)
if err != nil {
return nil, fmt.Errorf("Can't generate Ed25519 keys: %s", err)
return nil, err
}
pk.Pk = []byte(p)
sk.Sk = []byte(s)
pk.hash = pkhash(pk.Pk)
return kp, nil
sk := &PrivateKey{
Sk: []byte(skb),
pk: &PublicKey{
Pk: []byte(pkb),
hash: pkhash([]byte(pkb)),
},
}
// Serialize the keypair to two separate files. The basename of the
// file is 'bn'; the public key goes in $bn.pub and the private key
// goes in $bn.key.
// If password is non-empty, then the private key is encrypted
// before writing to disk.
func (kp *Keypair) Serialize(bn, comment string, getpw func() ([]byte, error)) error {
sk := &kp.Sec
pk := &kp.Pub
skf := fmt.Sprintf("%s.key", bn)
pkf := fmt.Sprintf("%s.pub", bn)
err := pk.serialize(pkf, comment)
if err != nil {
return fmt.Errorf("Can't serialize to %s: %s", pkf, err)
}
err = sk.serialize(skf, comment, getpw)
if err != nil {
return fmt.Errorf("Can't serialize to %s: %s", pkf, err)
}
return nil
return sk, nil
}
// Read the private key in 'fn', optionally decrypting it using
@ -177,73 +143,30 @@ func ReadPrivateKey(fn string, getpw func() ([]byte, error)) (*PrivateKey, error
return nil, err
}
if bytes.Index(yml, []byte("OPENSSH PRIVATE KEY-")) > 0 {
return parseSSHPrivateKey(yml, getpw)
}
if pw, err := getpw(); err == nil {
return MakePrivateKey(yml, pw)
}
var sk PrivateKey
if err = sk.UnmarshalBinary(yml, getpw); err != nil {
return nil, err
}
return &sk, nil
}
// Make a private key from bytes 'yml' and password 'pw'. The bytes
// Make a private key from bytes 'yml' using optional caller provided
// getpw() function to read the password if needed.
// are assumed to be serialized version of the private key.
func MakePrivateKey(yml []byte, pw []byte) (*PrivateKey, error) {
var ssk serializedPrivKey
func MakePrivateKey(yml []byte, getpw func() ([]byte, error)) (*PrivateKey, error) {
var sk PrivateKey
err := yaml.Unmarshal(yml, &ssk)
err := sk.UnmarshalBinary(yml, getpw)
if err != nil {
return nil, fmt.Errorf("make priv key: can't parse YAML: %s", err)
return nil, err
}
return &sk, nil
}
if len(ssk.Salt) == 0 || len(ssk.Esk) == 0 {
return nil, fmt.Errorf("sign: not YAML private key")
}
b64 := base64.StdEncoding.DecodeString
salt, err := b64(ssk.Salt)
if err != nil {
return nil, fmt.Errorf("make priv key: can't decode salt: %s", err)
}
esk, err := b64(ssk.Esk)
if err != nil {
return nil, fmt.Errorf("make priv key: can't decode key: %s", err)
}
// We take short passwords and extend them
pwb := sha512.Sum512(pw)
// "32" == Length of AES-256 key
key, err := scrypt.Key(pwb[:], salt, ssk.N, ssk.R, ssk.P, 32)
if err != nil {
return nil, fmt.Errorf("make priv key: can't derive key: %s", err)
}
aes, err := aes.NewCipher(key)
if err != nil {
return nil, fmt.Errorf("make priv key: aes failure: %s", err)
}
ae, err := cipher.NewGCM(aes)
if err != nil {
return nil, fmt.Errorf("make priv key: aes failure: %s", err)
}
skb, err := ae.Open(nil, salt[:ae.NonceSize()], esk, nil)
if err != nil {
return nil, fmt.Errorf("make priv key: wrong password")
}
return PrivateKeyFromBytes(skb)
}
// Make a private key from 64-bytes of extended Ed25519 key
func PrivateKeyFromBytes(buf []byte) (*PrivateKey, error) {
// make a PrivateKey from a byte array containing ed25519 raw SK
func makePrivateKeyFromBytes(sk *PrivateKey, buf []byte) error {
if len(buf) != 64 {
return nil, fmt.Errorf("private key is malformed (len %d!)", len(buf))
return fmt.Errorf("private key is malformed (len %d!)", len(buf))
}
skb := make([]byte, 64)
@ -256,13 +179,19 @@ func PrivateKeyFromBytes(buf []byte) (*PrivateKey, error) {
Pk: []byte(edpk),
hash: pkhash([]byte(edpk)),
}
sk := &PrivateKey{
Sk: skb,
pk: pk,
sk.Sk = skb
sk.pk = pk
return nil
}
return sk, nil
/*
// Make a private key from 64-bytes of extended Ed25519 key
func PrivateKeyFromBytes(buf []byte) (*PrivateKey, error) {
var sk PrivateKey
return makePrivateKeyFromBytes(&sk, buf)
}
*/
// Given a secret key, return the corresponding Public Key
func (sk *PrivateKey) PublicKey() *PublicKey {
@ -270,7 +199,7 @@ func (sk *PrivateKey) PublicKey() *PublicKey {
}
// Convert an Ed25519 Private Key to Curve25519 Private key
func (sk *PrivateKey) toCurve25519SK() []byte {
func (sk *PrivateKey) ToCurve25519SK() []byte {
if sk.ck == nil {
var ek [64]byte
@ -284,12 +213,205 @@ func (sk *PrivateKey) toCurve25519SK() []byte {
return sk.ck
}
// Serialize the private key to file 'fn' using human readable
// 'comment' and encrypt the key with supplied passphrase 'pw'.
func (sk *PrivateKey) Serialize(fn, comment string, ovwrite bool, pw []byte) error {
b, err := sk.MarshalBinary(comment, pw)
if err == nil {
return writeFile(fn, b, ovwrite, 0600)
}
return err
}
// MarshalBinary marshals the private key with a caller provided
// passphrase 'pw' and human readable 'comment'
func (sk *PrivateKey) MarshalBinary(comment string, pw []byte) ([]byte, error) {
// expand the password into 64 bytes
pass := sha512.Sum512(pw)
salt := make([]byte, 32)
randRead(salt)
// "32" == Length of AES-256 key
key, err := scrypt.Key(pass[:], salt, _N, _r, _p, 32)
if err != nil {
return nil, fmt.Errorf("marshal: can't derive scrypt key: %s", err)
}
aes, err := aes.NewCipher(key)
if err != nil {
return nil, fmt.Errorf("marshal: %s", err)
}
ae, err := cipher.NewGCM(aes)
if err != nil {
return nil, fmt.Errorf("marshal: %s", err)
}
tl := ae.Overhead()
buf := make([]byte, tl+len(sk.Sk))
esk := ae.Seal(buf[:0], salt[:ae.NonceSize()], sk.Sk, nil)
enc := base64.StdEncoding.EncodeToString
ssk := serializedPrivKey{
Comment: comment,
Esk: enc(esk),
Salt: enc(salt),
Algo: sk_algo,
N: _N,
R: _r,
P: _p,
}
// We won't protect the Scrypt parameters with the hash above
// because it is not needed. If the parameters are wrong, the
// derived key will be wrong and thus, the hash will not match.
return yaml.Marshal(&ssk)
}
// UnmarshalBinary unmarshals the private key and optionally invokes the
// caller provided getpw() function to read the password if needed. If the
// input byte stream 'b' is an OpenSSH ed25519 key, this function transparently
// decodes it.
func (sk *PrivateKey) UnmarshalBinary(b []byte, getpw func() ([]byte, error)) error {
if bytes.Index(b, []byte("OPENSSH PRIVATE KEY-")) > 0 {
xk, err := parseSSHPrivateKey(b, getpw)
if err != nil {
return err
}
*sk = *xk
return nil
}
var pw []byte
if getpw != nil {
var err error
pw, err = getpw()
if err != nil {
return err
}
}
// We take short passwords and extend them
pwb := sha512.Sum512(pw)
var ssk serializedPrivKey
err := yaml.Unmarshal(b, &ssk)
if err != nil {
return fmt.Errorf("unmarshal priv key: can't parse YAML: %s", err)
}
if len(ssk.Salt) == 0 || len(ssk.Esk) == 0 {
return fmt.Errorf("unmarshal priv key: not YAML format")
}
b64 := base64.StdEncoding.DecodeString
salt, err := b64(ssk.Salt)
if err != nil {
return fmt.Errorf("unmarshal priv key: can't decode salt: %s", err)
}
esk, err := b64(ssk.Esk)
if err != nil {
return fmt.Errorf("unmarshal priv key: can't decode key: %s", err)
}
// "32" == Length of AES-256 key
key, err := scrypt.Key(pwb[:], salt, ssk.N, ssk.R, ssk.P, 32)
if err != nil {
return fmt.Errorf("unmarshal priv key: can't derive key: %s", err)
}
aes, err := aes.NewCipher(key)
if err != nil {
return fmt.Errorf("unmarshal priv key: aes failure: %s", err)
}
ae, err := cipher.NewGCM(aes)
if err != nil {
return fmt.Errorf("unmarshal priv key: aes failure: %s", err)
}
skb := make([]byte, 64)
skb, err = ae.Open(skb[:0], salt[:ae.NonceSize()], esk, nil)
if err != nil {
return fmt.Errorf("unmarshal priv key: wrong password")
}
return makePrivateKeyFromBytes(sk, skb)
}
// --- Public Key Methods ---
// Read the public key from 'fn' and create new instance of
// PublicKey
func ReadPublicKey(fn string) (*PublicKey, error) {
var err error
var yml []byte
if yml, err = ioutil.ReadFile(fn); err != nil {
return nil, err
}
var pk PublicKey
if err = pk.UnmarshalBinary(yml); err != nil {
return nil, err
}
return &pk, nil
}
// Parse a serialized public in 'yml' and return the resulting
// public key instance
func MakePublicKey(yml []byte) (*PublicKey, error) {
var pk PublicKey
if err := pk.UnmarshalBinary(yml); err != nil {
return nil, err
}
return &pk, nil
}
func makePublicKeyFromBytes(pk *PublicKey, b []byte) error {
if len(b) != 32 {
return fmt.Errorf("public key is malformed (len %d!)", len(b))
}
pk.Pk = make([]byte, 32)
pk.hash = pkhash(b)
copy(pk.Pk, b)
return nil
}
/*
// Make a public key from a byte string
func PublicKeyFromBytes(b []byte) (*PublicKey, error) {
var pk PublicKey
makePublicKeyFromBytes(&pk, b)
}
*/
// Serialize a PublicKey into file 'fn' with a human readable 'comment'.
// If 'ovwrite' is true, overwrite the file if it exists.
func (pk *PublicKey) Serialize(fn, comment string, ovwrite bool) error {
out, err := pk.MarshalBinary(comment)
if err == nil {
return writeFile(fn, out, ovwrite, 0644)
}
return err
}
// from github.com/FiloSottile/age
var curve25519P, _ = new(big.Int).SetString("57896044618658097711785492504343953926634992332820282019728792003956564819949", 10)
// Convert an Ed25519 Public Key to Curve25519 public key
// from github.com/FiloSottile/age
func (pk *PublicKey) toCurve25519PK() []byte {
func (pk *PublicKey) ToCurve25519PK() []byte {
if pk.ck != nil {
return pk.ck
}
@ -329,131 +451,8 @@ func (pk *PublicKey) Hash() []byte {
return pk.hash
}
// Serialize the private key to a file
// AEAD encryption for protecting the private key
// Format: YAML
// All []byte are in base64 (RawEncoding)
func (sk *PrivateKey) serialize(fn, comment string, getpw func() ([]byte, error)) error {
pw, err := getpw()
if err != nil {
return err
}
// expand the password into 64 bytes
pass := sha512.Sum512(pw)
salt := make([]byte, 32)
randRead(salt)
// "32" == Length of AES-256 key
key, err := scrypt.Key(pass[:], salt, _N, _r, _p, 32)
if err != nil {
return fmt.Errorf("marshal: can't derive scrypt key: %s", err)
}
aes, err := aes.NewCipher(key)
if err != nil {
return fmt.Errorf("marshal: %s", err)
}
ae, err := cipher.NewGCM(aes)
if err != nil {
return fmt.Errorf("marshal: %s", err)
}
tl := ae.Overhead()
buf := make([]byte, tl+len(sk.Sk))
esk := ae.Seal(buf[:0], salt[:ae.NonceSize()], sk.Sk, nil)
enc := base64.StdEncoding.EncodeToString
ssk := serializedPrivKey{
Comment: comment,
Esk: enc(esk),
Salt: enc(salt),
Algo: sk_algo,
N: _N,
R: _r,
P: _p,
}
// We won't protect the Scrypt parameters with the hash above
// because it is not needed. If the parameters are wrong, the
// derived key will be wrong and thus, the hash will not match.
out, err := yaml.Marshal(&ssk)
if err != nil {
return fmt.Errorf("can't marahal to YAML: %s", err)
}
return writeFile(fn, out, 0600)
}
// --- Public Key Methods ---
// Read the public key from 'fn' and create new instance of
// PublicKey
func ReadPublicKey(fn string) (*PublicKey, error) {
var err error
var yml []byte
if yml, err = ioutil.ReadFile(fn); err != nil {
return nil, err
}
// first try to parse as a ssh key
pk, err := parseSSHPublicKey(yml)
if err != nil {
pk, err = MakePublicKey(yml)
}
return pk, err
}
// Parse a serialized public in 'yml' and return the resulting
// public key instance
func MakePublicKey(yml []byte) (*PublicKey, error) {
var spk serializedPubKey
var err error
if err = yaml.Unmarshal(yml, &spk); err != nil {
return nil, fmt.Errorf("can't parse YAML: %s", err)
}
if len(spk.Pk) == 0 {
return nil, fmt.Errorf("sign: not a YAML public key")
}
b64 := base64.StdEncoding.DecodeString
var pkb []byte
if pkb, err = b64(spk.Pk); err != nil {
return nil, fmt.Errorf("can't decode YAML:Pk: %s", err)
}
if pk, err := PublicKeyFromBytes(pkb); err == nil {
pk.Comment = spk.Comment
return pk, nil
}
return nil, err
}
// Make a public key from a byte string
func PublicKeyFromBytes(b []byte) (*PublicKey, error) {
if len(b) != 32 {
return nil, fmt.Errorf("public key is malformed (len %d!)", len(b))
}
pk := &PublicKey{
Pk: make([]byte, 32),
hash: pkhash(b),
}
copy(pk.Pk, b)
return pk, nil
}
// Serialize Public Keys
func (pk *PublicKey) serialize(fn, comment string) error {
// MarshalBinary marshals a PublicKey into a byte array
func (pk *PublicKey) MarshalBinary(comment string) ([]byte, error) {
b64 := base64.StdEncoding.EncodeToString
spk := &serializedPubKey{
Comment: comment,
@ -461,57 +460,61 @@ func (pk *PublicKey) serialize(fn, comment string) error {
Hash: b64(pk.hash),
}
out, err := yaml.Marshal(spk)
if err != nil {
return fmt.Errorf("can't marahal to YAML: %s", err)
return yaml.Marshal(spk)
}
return writeFile(fn, out, 0644)
// UnmarshalBinary constructs a PublicKey from a previously
// marshaled byte stream instance. In addition, it is also
// capable of parsing an OpenSSH ed25519 public key.
func (pk *PublicKey) UnmarshalBinary(yml []byte) error {
// first try to parse as a ssh key
if xk, err := parseSSHPublicKey(yml); err == nil {
*pk = *xk
return nil
}
// OK Yaml it is.
var spk serializedPubKey
var err error
if err = yaml.Unmarshal(yml, &spk); err != nil {
return fmt.Errorf("can't parse YAML: %s", err)
}
if len(spk.Pk) == 0 {
return fmt.Errorf("sign: not a YAML public key")
}
b64 := base64.StdEncoding.DecodeString
var pkb []byte
if pkb, err = b64(spk.Pk); err != nil {
return fmt.Errorf("can't decode YAML:Pk: %s", err)
}
return makePublicKeyFromBytes(pk, pkb)
}
// -- Internal Utility Functions --
// 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))
func writeFile(fn string, b []byte, ovwrite bool, mode uint32) error {
sf, err := NewSafeFile(fn, ovwrite, 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)
return err
}
defer sf.Abort() // always cleanup on error
_, 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
sf.Write(b)
return sf.Close()
}
// Generate file checksum out of hash function h
func fileCksum(fn string, h hash.Hash) ([]byte, error) {
fd, err := os.Open(fn)
if err != nil {
return nil, fmt.Errorf("can't open %s: %s", fn, err)
@ -528,7 +531,7 @@ func fileCksum(fn string, h hash.Hash) ([]byte, error) {
binary.BigEndian.PutUint64(b[:], uint64(sz))
h.Write(b[:])
return h.Sum(nil), nil
return h.Sum(nil)[:], nil
}
func clamp(k []byte) []byte {
@ -538,13 +541,5 @@ func clamp(k []byte) []byte {
return k
}
func randRead(b []byte) []byte {
_, err := io.ReadFull(rand.Reader, b)
if err != nil {
panic(fmt.Sprintf("can't read %d bytes of random data: %s", len(b), err))
}
return b
}
// EOF
// vim: noexpandtab:ts=8:sw=8:tw=92:

40
sign/rand.go Normal file
View file

@ -0,0 +1,40 @@
// rand.go - utility functions to generate random quantities
//
// (c) 2018 Sudhi Herle <sudhi@herle.net>
//
// Licensing Terms: GPLv2
//
// If you need a commercial license for this work, please contact
// the author.
//
// This software does not come with any express or implied
// warranty; it is provided "as is". No claim is made to its
// suitability for any purpose.
package sign
import (
"crypto/rand"
"encoding/binary"
"fmt"
"io"
)
func randu32() uint32 {
var b [4]byte
_, err := io.ReadFull(rand.Reader, b[:])
if err != nil {
panic(fmt.Sprintf("can't read 4 rand bytes: %s", err))
}
return binary.LittleEndian.Uint32(b[:])
}
func randRead(b []byte) []byte {
_, err := io.ReadFull(rand.Reader, b)
if err != nil {
panic(fmt.Sprintf("can't read %d bytes of random data: %s", len(b), err))
}
return b
}

124
sign/safefile.go Normal file
View file

@ -0,0 +1,124 @@
// safefile.go - safe file creation and unwinding on error
//
// (c) 2021 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 (
"fmt"
"io"
"os"
)
// SafeFile is an io.WriteCloser which uses a temporary file that
// will be atomically renamed when there are no errors and
// caller invokes Close(). Callers are advised to call
// Abort() in the appropriate error handling (defer) context
// so that the temporary file is properly deleted.
type SafeFile struct {
*os.File
// error for writes recorded once
err error
name string // actual filename
closed bool // set if the file is closed properly
}
var _ io.WriteCloser = &SafeFile{}
// NewSafeFile creates a new temporary file that would either be
// aborted or safely renamed to the correct name.
// 'nm' is the name of the final file; if 'ovwrite' is true,
// then the file is overwritten if it exists.
func NewSafeFile(nm string, ovwrite bool, flag int, perm os.FileMode) (*SafeFile, error) {
if _, err := os.Stat(nm); err == nil && !ovwrite {
return nil, fmt.Errorf("safefile: won't overwrite existing %s", nm)
}
// forcibly unlink the old file - so previous artifacts don't exist
os.Remove(nm)
tmp := fmt.Sprintf("%s.tmp.%d.%x", nm, os.Getpid(), randu32())
fd, err := os.OpenFile(tmp, flag, perm)
if err != nil {
return nil, err
}
sf := &SafeFile{
File: fd,
name: nm,
}
return sf, nil
}
// Attempt to write everything in 'b' and don't proceed if there was
// a previous error or the file was already closed.
func (sf *SafeFile) Write(b []byte) (int, error) {
if sf.err != nil {
return 0, sf.err
}
if sf.closed {
return 0, fmt.Errorf("safefile: %s is closed", sf.Name())
}
var z, nw int
n := len(b)
for n > 0 {
if nw, sf.err = sf.File.Write(b); sf.err != nil {
return z, sf.err
}
z += nw
n -= nw
b = b[nw:]
}
return z, nil
}
// Abort the file write and remove any temporary artifacts
func (sf *SafeFile) Abort() {
// if we've successfully closed, nothing to do!
if sf.closed {
return
}
sf.closed = true
sf.File.Close()
os.Remove(sf.Name())
}
// Close flushes all file data & metadata to disk, closes the file and atomically renames
// the temp file to the actual file - ONLY if there were no intervening errors.
func (sf *SafeFile) Close() error {
if sf.err != nil {
sf.Abort()
return sf.err
}
// mark this file as closed!
sf.closed = true
if sf.err = sf.Sync(); sf.err != nil {
return sf.err
}
if sf.err = sf.File.Close(); sf.err != nil {
return sf.err
}
if sf.err = os.Rename(sf.Name(), sf.name); sf.err != nil {
return sf.err
}
return nil
}

View file

@ -86,12 +86,13 @@ func ReadSignature(fn string) (*Signature, error) {
return nil, err
}
return MakeSignature(yml)
var sig Signature
return makeSignature(&sig, yml)
}
// Parse serialized signature from bytes 'b' and construct a
// Signature object
func MakeSignature(b []byte) (*Signature, error) {
func makeSignature(sig *Signature, b []byte) (*Signature, error) {
var ss signature
err := yaml.Unmarshal(b, &ss)
if err != nil {
@ -110,29 +111,33 @@ func MakeSignature(b []byte) (*Signature, error) {
return nil, fmt.Errorf("can't decode Base64:Pkhash <%s>: %s", ss.Pkhash, err)
}
return &Signature{Sig: s, pkhash: p}, nil
sig.Sig = s
sig.pkhash = p
return sig, nil
}
// Serialize a signature suitable for storing in durable media
func (sig *Signature) Serialize(comment string) ([]byte, error) {
// MarshalBinary marshals a signature into a byte stream with
// an optional caller supplied comment.
func (sig *Signature) MarshalBinary(comment string) ([]byte, error) {
sigs := base64.StdEncoding.EncodeToString(sig.Sig)
pks := base64.StdEncoding.EncodeToString(sig.pkhash)
ss := &signature{Comment: comment, Pkhash: pks, Signature: sigs}
out, err := yaml.Marshal(ss)
if err != nil {
return nil, fmt.Errorf("can't marshal signature of %x to YAML: %s", sig.Sig, err)
return yaml.Marshal(ss)
}
return out, nil
// UnmarshalBinary constructs a Signature from a previously
// serialized bytestream
func (sig *Signature) UnmarshalBinary(b []byte) error {
_, err := makeSignature(sig, b)
return err
}
// SerializeFile serializes the signature to an output file 'f'
func (sig *Signature) SerializeFile(fn, comment string) error {
b, err := sig.Serialize(comment)
// Serialize a signature suitable for storing in durable media
func (sig *Signature) Serialize(fn, comment string, ovwrite bool) error {
b, err := sig.MarshalBinary(comment)
if err == nil {
err = writeFile(fn, b, 0644)
err = writeFile(fn, b, ovwrite, 0644)
}
return err
}
@ -147,7 +152,6 @@ func (sig *Signature) IsPKMatch(pk *PublicKey) bool {
// Verify a signature 'sig' for file 'fn' against public key 'pk'
// Return True if signature matches, False otherwise
func (pk *PublicKey) VerifyFile(fn string, sig *Signature) (bool, error) {
ck, err := fileCksum(fn, sha512.New())
if err != nil {
return false, err

View file

@ -38,16 +38,20 @@ func tempdir(t *testing.T) string {
return tmp
}
var fixedPw = []byte("abc")
var badPw = []byte("def")
var nilPw []byte
// return a hardcoded password
func hardcodedPw() ([]byte, error) {
return []byte("abc"), nil
return fixedPw, nil
}
func wrongPw() ([]byte, error) {
return []byte("xyz"), nil
return badPw, nil
}
func emptyPw() ([]byte, error) {
return nil, nil
return nilPw, nil
}
// Return true if file exists, false otherwise
@ -80,68 +84,78 @@ p: 1
func TestSignSimple(t *testing.T) {
assert := newAsserter(t)
kp, err := NewKeypair()
assert(err == nil, "NewKeyPair() fail")
sk, err := NewPrivateKey()
assert(err == nil, "NewPrivateKey() fail")
dn := tempdir(t)
pk := sk.PublicKey()
dn := t.TempDir()
bn := fmt.Sprintf("%s/t0", dn)
err = kp.Serialize(bn, "", hardcodedPw)
assert(err == nil, "keyPair.Serialize() fail")
pkf := fmt.Sprintf("%s.pub", bn)
skf := fmt.Sprintf("%s.key", bn)
// We must find these two files
assert(fileExists(pkf), "missing pkf")
assert(fileExists(skf), "missing skf")
err = pk.Serialize(pkf, "", true)
assert(err == nil, "can't serialize pk %s", pkf)
pk, err := ReadPublicKey(pkf)
// try to overwrite
err = pk.Serialize(pkf, "", false)
assert(err != nil, "pk %s overwritten!", pkf)
err = sk.Serialize(skf, "", true, fixedPw)
assert(err == nil, "can't serialize sk %s", skf)
err = sk.Serialize(skf, "", false, nilPw)
assert(err != nil, "sk %s overwritten!", skf)
// We must find these two files
assert(fileExists(pkf), "missing pkf %s", pkf)
assert(fileExists(skf), "missing skf %s", skf)
npk, err := ReadPublicKey(pkf)
assert(err == nil, "ReadPK() fail")
// -ditto- for Sk
sk, err := ReadPrivateKey(pkf, emptyPw)
// send the public key as private key
nsk, err := ReadPrivateKey(pkf, emptyPw)
assert(err != nil, "bad SK ReadSK fail: %s", err)
sk, err = ReadPrivateKey(skf, emptyPw)
assert(err != nil, "ReadSK() empty pw fail: ks", err)
nsk, err = ReadPrivateKey(skf, emptyPw)
assert(err != nil, "ReadSK() worked with empty pw")
sk, err = ReadPrivateKey(skf, wrongPw)
assert(err != nil, "ReadSK() wrong pw fail: %s", err)
nsk, err = ReadPrivateKey(skf, wrongPw)
assert(err != nil, "ReadSK() worked with wrong pw")
badf := fmt.Sprintf("%s/badf.key", dn)
err = ioutil.WriteFile(badf, []byte(badsk), 0600)
assert(err == nil, "write badsk")
assert(err == nil, "can't write badsk: %s", err)
sk, err = ReadPrivateKey(badf, hardcodedPw)
assert(err != nil, "badsk read fail: %s", err)
nsk, err = ReadPrivateKey(badf, hardcodedPw)
assert(err != nil, "decoded bad SK")
// Finally, with correct password it should work.
sk, err = ReadPrivateKey(skf, hardcodedPw)
assert(err == nil, "ReadSK() correct pw fail")
nsk, err = ReadPrivateKey(skf, hardcodedPw)
assert(err == nil, "ReadSK() correct pw fail: %s", err)
// And, deserialized keys should be identical
assert(byteEq(pk.Pk, kp.Pub.Pk), "pkbytes unequal")
assert(byteEq(sk.Sk, kp.Sec.Sk), "skbytes unequal")
os.RemoveAll(dn)
assert(byteEq(pk.Pk, npk.Pk), "pkbytes unequal")
assert(byteEq(sk.Sk, nsk.Sk), "skbytes unequal")
}
// #2. Create new key pair, sign a rand buffer and verify
func TestSignRandBuf(t *testing.T) {
assert := newAsserter(t)
kp, err := NewKeypair()
assert(err == nil, "NewKeyPair() fail")
sk, err := NewPrivateKey()
assert(err == nil, "NewPrivateKey() fail: %s", err)
var ck [64]byte // simulates sha512 sum
randRead(ck[:])
pk := &kp.Pub
sk := &kp.Sec
pk := sk.PublicKey()
ss, err := sk.SignMessage(ck[:], "")
assert(err == nil, "sk.sign fail")
assert(err == nil, "sk.sign fail: %s", err)
assert(ss != nil, "sig is null")
// verify sig
@ -160,27 +174,13 @@ func TestSignRandBuf(t *testing.T) {
assert(ok, "verify fail")
// Now sign a file
dn := tempdir(t)
bn := fmt.Sprintf("%s/k", dn)
pkf := fmt.Sprintf("%s.pub", bn)
skf := fmt.Sprintf("%s.key", bn)
err = kp.Serialize(bn, "", emptyPw)
assert(err == nil, "keyPair.Serialize() fail")
// Now read the private key and sign
sk, err = ReadPrivateKey(skf, emptyPw)
assert(err == nil, "readSK fail")
pk, err = ReadPublicKey(pkf)
assert(err == nil, "ReadPK fail")
dn := t.TempDir()
var buf [8192]byte
zf := fmt.Sprintf("%s/file.dat", dn)
fd, err := os.OpenFile(zf, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
assert(err == nil, "file.dat creat file")
assert(err == nil, "file.dat creat file: %s", err)
for i := 0; i < 8; i++ {
randRead(buf[:])
@ -192,27 +192,31 @@ func TestSignRandBuf(t *testing.T) {
fd.Close()
sig, err := sk.SignFile(zf)
assert(err == nil, "file.dat sign fail")
assert(err == nil, "file.dat sign fail: %s", err)
assert(sig != nil, "file.dat sign nil")
ok, err = pk.VerifyFile(zf, sig)
assert(err == nil, "file.dat verify fail")
assert(err == nil, "file.dat verify fail: %s", err)
assert(ok, "file.dat verify false")
// Now, serialize the signature and read it back
sf := fmt.Sprintf("%s/file.sig", dn)
err = sig.SerializeFile(sf, "")
assert(err == nil, "sig serialize fail")
err = sig.Serialize(sf, "", true)
assert(err == nil, "sig serialize fail: %s", err)
// now try to overwrite it
err = sig.Serialize(sf, "", false)
assert(err != nil, "sig serialize overwrote?!")
s2, err := ReadSignature(sf)
assert(err == nil, "file.sig read fail")
assert(err == nil, "file.sig read fail: %s", err)
assert(s2 != nil, "file.sig sig nil")
assert(byteEq(s2.Sig, sig.Sig), "sig compare fail")
// If we give a wrong file, verify must fail
st, err := os.Stat(zf)
assert(err == nil, "file.dat stat fail")
assert(err == nil, "file.dat stat fail: %s", err)
n := st.Size()
assert(n == 8192*8, "file.dat size fail")
@ -220,12 +224,12 @@ func TestSignRandBuf(t *testing.T) {
os.Truncate(zf, n-1)
st, err = os.Stat(zf)
assert(err == nil, "file.dat stat2 fail")
assert(err == nil, "file.dat stat2 fail: %s", err)
assert(st.Size() == (n-1), "truncate fail")
// Now verify this corrupt file
ok, err = pk.VerifyFile(zf, sig)
assert(err == nil, "file.dat corrupt i/o fail")
assert(err == nil, "file.dat corrupt i/o fail: %s", err)
assert(!ok, "file.dat corrupt verify false")
os.RemoveAll(dn)
@ -233,7 +237,7 @@ func TestSignRandBuf(t *testing.T) {
func Benchmark_Keygen(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = NewKeypair()
_, _ = NewPrivateKey()
}
}
@ -250,7 +254,8 @@ func Benchmark_Sig(b *testing.B) {
}
b.StopTimer()
kp, _ := NewKeypair()
sk, _ := NewPrivateKey()
pk := sk.PublicKey()
var sig *Signature
for _, sz := range sizes {
buf := randbuf(sz)
@ -260,11 +265,11 @@ func Benchmark_Sig(b *testing.B) {
b.ResetTimer()
b.Run(s0, func(b *testing.B) {
sig = benchSign(b, buf, &kp.Sec)
sig = benchSign(b, buf, sk)
})
b.Run(s1, func(b *testing.B) {
benchVerify(b, buf, sig, &kp.Pub)
benchVerify(b, buf, sig, pk)
})
}
}

View file

@ -95,11 +95,13 @@ func parseEncPubKey(in []byte, comm string) (*PublicKey, error) {
return nil, ErrBadTrailers
}
pk, err := PublicKeyFromBytes(w.KeyBytes)
if err == nil {
var pk PublicKey
if err = makePublicKeyFromBytes(&pk, w.KeyBytes); err == nil {
pk.Comment = strings.TrimSpace(comm)
return &pk, nil
}
return pk, err
return nil, err
}
func parseString(in []byte) (out, rest []byte, ok bool) {
@ -343,8 +345,11 @@ func parseOpenSSHPrivateKey(data []byte, getpw func() ([]byte, error)) (*Private
}
}
pk, err := PrivateKeyFromBytes(key.Priv)
return pk, err
var sk PrivateKey
if err = makePrivateKeyFromBytes(&sk, key.Priv); err == nil {
return &sk, nil
}
return nil, err
default:
return nil, fmt.Errorf("ssh: unhandled key type: %v", pk1.Keytype)
}

View file

@ -96,7 +96,7 @@ func gen(args []string) {
fs.BoolVarP(&nopw, "no-password", "", false, "Don't ask for a password for the private key")
fs.StringVarP(&comment, "comment", "c", "", "Use `C` as the text comment for the keys")
fs.StringVarP(&envpw, "env-password", "E", "", "Use passphrase from environment variable `E`")
fs.BoolVarP(&force, "force", "F", false, "Overwrite the output file if it exists")
fs.BoolVarP(&force, "overwrite", "", false, "Overwrite the output file if it exists")
fs.Parse(args)
@ -120,22 +120,19 @@ Options:
bn := args[0]
if exists(bn) && !force {
die("Public/Private key files (%s.key, %s.pub) exist. Won't overwrite!", bn, bn)
pkn := fmt.Sprintf("%s.pub", path.Clean(bn))
skn := fmt.Sprintf("%s.key", path.Clean(bn))
if !force {
if exists(pkn) || exists(skn) {
die("Public/Private key files (%s, %s) exist. won't overwrite!", skn, pkn)
}
}
var err error
var pw []byte
kp, err := sign.NewKeypair()
if err != nil {
die("%s", err)
}
err = kp.Serialize(bn, comment, func() ([]byte, error) {
if nopw {
return nil, nil
}
if !nopw {
var pws string
if len(envpw) > 0 {
pws = os.Getenv(envpw)
@ -145,16 +142,28 @@ Options:
die("%s", err)
}
}
return []byte(pws), nil
})
pw = []byte(pws)
}
sk, err := sign.NewPrivateKey()
if err != nil {
die("%s", err)
}
if err = sk.Serialize(skn, comment, force, pw); err != nil {
die("%s", err)
}
pk := sk.PublicKey()
if err = pk.Serialize(pkn, comment, force); err != nil {
die("%s", err)
}
}
// Run the 'sign' command.
func signify(args []string) {
var nopw, help bool
var nopw, help, force bool
var output string
var envpw string
@ -163,6 +172,7 @@ func signify(args []string) {
fs.BoolVarP(&nopw, "no-password", "", false, "Don't ask for a password for the private key")
fs.StringVarP(&envpw, "env-password", "E", "", "Use passphrase from environment variable `E`")
fs.StringVarP(&output, "output", "o", "", "Write signature to file `F`")
fs.BoolVarP(&force, "overwrite", "", false, "Overwrite previous signature file if it exists")
fs.Parse(args)
@ -193,6 +203,19 @@ Options:
outf = output
}
var fd io.WriteCloser = os.Stdout
if outf != "-" {
sf, err := sign.NewSafeFile(outf, force, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
if err != nil {
die("can't create sig file: %s", err)
}
// we unlink and remove temp on any error
defer sf.Abort()
fd = sf
}
sk, err := sign.ReadPrivateKey(kn, func() ([]byte, error) {
if nopw {
return nil, nil
@ -219,20 +242,9 @@ Options:
die("%s", err)
}
sigo, err := sig.Serialize(fmt.Sprintf("input=%s", fn))
var fd io.Writer = os.Stdout
if outf != "-" {
fdx, err := os.OpenFile(outf, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
if err != nil {
die("can't create output file %s: %s", outf, err)
}
defer fdx.Close()
fd = fdx
}
fd.Write(sigo)
sigbytes, err := sig.MarshalBinary(fmt.Sprintf("input=%s", fn))
fd.Write(sigbytes)
fd.Close()
}
// Verify signature on a given file
@ -323,14 +335,8 @@ Commands:
}
// Return true if $bn.key or $bn.pub exist; false otherwise
func exists(bn string) bool {
pk := bn + ".pub"
sk := bn + ".key"
if _, err := os.Stat(pk); err == nil {
return true
}
if _, err := os.Stat(sk); err == nil {
func exists(nm string) bool {
if _, err := os.Stat(nm); err == nil {
return true
}

67
tests.sh Normal file
View file

@ -0,0 +1,67 @@
#! /usr/bin/env bash
# simple round-trip tests to verify the tool
arch=`./build --print-arch`
bin=./bin/$arch/sigtool
Z=`basename $0`
die() {
echo "$Z: $@" 1>&2
exit 1
}
[ -x $bin ] || ./build || die "Can't build sigtool for $arch"
# env name for reading the password
passenv=FOO
# this is the password for SKs
FOO=bar
# basename of keyfile
tmpdir=/tmp/sigtool$$
mkdir -p $tmpdir || die "can't mkdir $tmpdir"
#trap "rm -rf $tmpdir" EXIT
bn=$tmpdir/foo
pk=$bn.pub
sk=$bn.key
sig=$tmpdir/$Z.sig
bn2=$tmpdir/bar
pk2=$bn2.pub
sk2=$bn2.key
encout=$tmpdir/$Z.enc
decout=$tmpdir/$Z.dec
# exit on any failure
set -e
# generate keys
$bin g -E FOO $bn || die "can't gen keypair $pk, $sk"
$bin g -E FOO $bn && die "overwrote prev keypair"
$bin g -E FOO --overwrite $bn || die "can't force gen keypair $pk, $sk"
$bin g -E FOO $bn2 || die "can't force gen keypair $pk2, $sk2"
# sign and verify
$bin s -E FOO $sk $0 -o $sig || die "can't sign $0"
$bin v -q $pk $sig $0 || die "can't verify signature of $0"
$bin v -q $pk2 $sig $0 && die "bad verification with wrong $pk2"
# encrypt/decrypt
$bin e -E FOO -o $encout $pk2 $0 || die "can't encrypt to $pk2"
$bin d -E FOO -o $decout $sk2 $encout || die "can't decrypt with $sk2"
cmp -s $decout $0 || die "decrypted file mismatch with $0"
# now with sender verification
$bin e -E FOO --overwrite -o $encout -s $sk $pk2 $0 || die "can't sender-encrypt to $pk2"
$bin d -E FOO --overwrite -o $decout -v $pk $sk2 $encout || die "can't decrypt with $sk2"
cmp -s $decout $0 || die "decrypted file mismatch with $0"
# vim: tw=100 sw=4 ts=4 expandtab