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

@ -233,8 +233,8 @@ decryption.
### How is the private key protected? ### How is the private key protected?
The Ed25519 private key is encrypted in AES-GCM-256 mode using a key The Ed25519 private key is encrypted in AES-GCM-256 mode using a key
derived from the user's pass-phrase. The user pass phrase is expanded via derived from the user's pass-phrase. The user pass phrase is expanded via
SHA256; this expanded pass phrase is fed to `scrypt()` to SHA256; this expanded pass phrase is fed to `scrypt()` to
generate a key-encryption-key. In pseudo code, this operation looks generate a key-encryption-key. In pseudo code, this operation looks
like below: like below:
@ -263,6 +263,9 @@ etc.
* `src/ssh.go` contains code to parse SSH Ed25519 key files * `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 * `src/stream.go` contains code that provides an `io.Reader` and `io.WriteCloser` interface
for encryption and decryption. 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 The generated keys and signatures are proper YAML files and human
readable. 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 in memory mapped mode (```mmap(2)```) and hashing the file contents
using SHA-512. The Ed25519 signature is calculated on the file-hash. 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 ## Example of Keys, Signature
### Ed25519 Public Key ### Ed25519 Public Key

View file

@ -36,14 +36,15 @@ func encrypt(args []string) {
var outfile string var outfile string
var keyfile string var keyfile string
var envpw string var envpw string
var nopw bool var nopw, force bool
var blksize uint64 var blksize uint64
fs.StringVarP(&outfile, "outfile", "o", "", "Write the output to file `F`") fs.StringVarP(&outfile, "outfile", "o", "", "Write the output to file `F`")
fs.StringVarP(&keyfile, "sign", "s", "", "Sign using private key `S`") 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.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.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) err := fs.Parse(args)
if err != nil { if err != nil {
@ -114,26 +115,32 @@ func encrypt(args []string) {
} }
if len(outfile) > 0 && outfile != "-" { if len(outfile) > 0 && outfile != "-" {
var mode os.FileMode = 0600 // conservative output mode
if inf != nil { if inf != nil {
ost, err := os.Stat(outfile) var err error
if err != nil { var ist, ost os.FileInfo
if ost, err = os.Stat(outfile); err != nil {
die("can't stat %s: %s", outfile, err) die("can't stat %s: %s", outfile, err)
} }
ist, err := inf.Stat() if ist, err = inf.Stat(); err != nil {
if err != nil {
die("can't stat %s: %s", infile, err) die("can't stat %s: %s", infile, err)
} }
if os.SameFile(ist, ost) { if os.SameFile(ist, ost) {
die("won't create output file: same as input file!") 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) sf, err := sign.NewSafeFile(outfile, force, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode)
defer outf.Close() if err != nil {
die("%s", err)
outfd = outf }
defer sf.Abort()
outfd = sf
} }
en, err := sign.NewEncryptor(sk, blksize) en, err := sign.NewEncryptor(sk, blksize)
@ -178,6 +185,7 @@ func encrypt(args []string) {
if err != nil { if err != nil {
die("%s", err) die("%s", err)
} }
outfd.Close()
} }
type nullWriter struct{} type nullWriter struct{}
@ -202,13 +210,14 @@ func decrypt(args []string) {
var envpw string var envpw string
var outfile string var outfile string
var pubkey 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.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.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.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(&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) err := fs.Parse(args)
if err != nil { if err != nil {
@ -221,7 +230,7 @@ func decrypt(args []string) {
} }
var infd io.Reader = os.Stdin var infd io.Reader = os.Stdin
var outfd io.Writer = os.Stdout var outfd io.WriteCloser = os.Stdout
var inf *os.File var inf *os.File
var infile string var infile string
@ -268,24 +277,30 @@ func decrypt(args []string) {
if test { if test {
outfd = &nullWriter{} outfd = &nullWriter{}
} else if len(outfile) > 0 && outfile != "-" { } else if len(outfile) > 0 && outfile != "-" {
var mode os.FileMode = 0600 // conservative mode
if inf != nil { if inf != nil {
ost, err := os.Stat(outfile) var ist, ost os.FileInfo
if err != nil { var err error
if ost, err = os.Stat(outfile); err != nil {
die("can't stat %s: %s", outfile, err) die("can't stat %s: %s", outfile, err)
} }
ist, err := inf.Stat() if ist, err = inf.Stat(); err != nil {
if err != nil {
die("can't stat %s: %s", infile, err) die("can't stat %s: %s", infile, err)
} }
if os.SameFile(ist, ost) { if os.SameFile(ist, ost) {
die("won't create output file: same as input file!") 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) sf, err := sign.NewSafeFile(outfile, force, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode)
defer outf.Close() if err != nil {
die("%s", err)
outfd = outf }
defer sf.Abort()
outfd = sf
} }
d, err := sign.NewDecryptor(infd) d, err := sign.NewDecryptor(infd)
@ -306,14 +321,16 @@ func decrypt(args []string) {
warn("%s: Missing sender Public Key; can't authenticate sender ..", fn) warn("%s: Missing sender Public Key; can't authenticate sender ..", fn)
} }
err = d.Decrypt(outfd) if err = d.Decrypt(outfd); err != nil {
if err != nil {
die("%s", err) die("%s", err)
} }
outfd.Close()
if test { if test {
warn("Enc file OK") warn("Enc file OK")
} }
} }
func encryptUsage(fs *flag.FlagSet) { 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 // 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 // basically, we do a scalarmult: Ephemeral encryption/decryption SK x receiver PK
func (e *Encryptor) wrapKey(pk *PublicKey) (*pb.WrappedKey, error) { func (e *Encryptor) wrapKey(pk *PublicKey) (*pb.WrappedKey, error) {
rxPK := pk.toCurve25519PK() rxPK := pk.ToCurve25519PK()
dkek, err := curve25519.X25519(e.encSK, rxPK) dkek, err := curve25519.X25519(e.encSK, rxPK)
if err != nil { if err != nil {
return nil, fmt.Errorf("wrap: %w", err) 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 // Unwrap a wrapped key using the receivers Ed25519 secret key 'sk' and
// senders ephemeral PublicKey // senders ephemeral PublicKey
func (d *Decryptor) unwrapKey(w *pb.WrappedKey, sk *PrivateKey) ([]byte, error) { func (d *Decryptor) unwrapKey(w *pb.WrappedKey, sk *PrivateKey) ([]byte, error) {
ourSK := sk.toCurve25519SK() ourSK := sk.ToCurve25519SK()
dkek, err := curve25519.X25519(ourSK, d.Pk) dkek, err := curve25519.X25519(ourSK, d.Pk)
if err != nil { if err != nil {
return nil, fmt.Errorf("unwrap: %w", err) return nil, fmt.Errorf("unwrap: %w", err)

View file

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

View file

@ -28,7 +28,6 @@ import (
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"hash" "hash"
"io"
"io/ioutil" "io/ioutil"
"math/big" "math/big"
"os" "os"
@ -64,15 +63,10 @@ type PublicKey struct {
hash []byte hash []byte
} }
// Ed25519 key pair
type Keypair struct {
Sec PrivateKey
Pub PublicKey
}
// Length of Ed25519 Public Key Hash // Length of Ed25519 Public Key Hash
const PKHashLength = 16 const PKHashLength = 16
// constants we use in this module
const ( const (
// Scrypt parameters // Scrypt parameters
_N int = 1 << 19 _N int = 1 << 19
@ -118,55 +112,27 @@ type signature struct {
Signature string `yaml:"signature"` Signature string `yaml:"signature"`
} }
// given a public key, generate a deterministic short-hash of it.
func pkhash(pk []byte) []byte { func pkhash(pk []byte) []byte {
z := sha256.Sum256(pk) z := sha256.Sum256(pk)
return z[:PKHashLength] return z[:PKHashLength]
} }
// Generate a new Ed25519 keypair // NewPrivateKey generates a new Ed25519 private key
func NewKeypair() (*Keypair, error) { func NewPrivateKey() (*PrivateKey, error) {
//kp := &Keypair{Sec: PrivateKey{N: 1 << 17, r: 64, p: 1}} pkb, skb, err := Ed.GenerateKey(rand.Reader)
kp := &Keypair{}
sk := &kp.Sec
pk := &kp.Pub
sk.pk = pk
p, s, err := Ed.GenerateKey(rand.Reader)
if err != nil { if err != nil {
return nil, fmt.Errorf("Can't generate Ed25519 keys: %s", err) return nil, err
} }
pk.Pk = []byte(p) sk := &PrivateKey{
sk.Sk = []byte(s) Sk: []byte(skb),
pk.hash = pkhash(pk.Pk) pk: &PublicKey{
Pk: []byte(pkb),
return kp, nil 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)
} }
return sk, nil
err = sk.serialize(skf, comment, getpw)
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 // 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 return nil, err
} }
if bytes.Index(yml, []byte("OPENSSH PRIVATE KEY-")) > 0 { var sk PrivateKey
return parseSSHPrivateKey(yml, getpw) if err = sk.UnmarshalBinary(yml, getpw); err != nil {
return nil, err
} }
return &sk, nil
if pw, err := getpw(); err == nil {
return MakePrivateKey(yml, pw)
}
return nil, err
} }
// 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. // are assumed to be serialized version of the private key.
func MakePrivateKey(yml []byte, pw []byte) (*PrivateKey, error) { func MakePrivateKey(yml []byte, getpw func() ([]byte, error)) (*PrivateKey, error) {
var ssk serializedPrivKey var sk PrivateKey
err := yaml.Unmarshal(yml, &ssk) err := sk.UnmarshalBinary(yml, getpw)
if err != nil { 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 // make a PrivateKey from a byte array containing ed25519 raw SK
func PrivateKeyFromBytes(buf []byte) (*PrivateKey, error) { func makePrivateKeyFromBytes(sk *PrivateKey, buf []byte) error {
if len(buf) != 64 { 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) skb := make([]byte, 64)
@ -256,21 +179,27 @@ func PrivateKeyFromBytes(buf []byte) (*PrivateKey, error) {
Pk: []byte(edpk), Pk: []byte(edpk),
hash: pkhash([]byte(edpk)), hash: pkhash([]byte(edpk)),
} }
sk := &PrivateKey{ sk.Sk = skb
Sk: skb, sk.pk = pk
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 // Given a secret key, return the corresponding Public Key
func (sk *PrivateKey) PublicKey() *PublicKey { func (sk *PrivateKey) PublicKey() *PublicKey {
return sk.pk return sk.pk
} }
// Convert an Ed25519 Private Key to Curve25519 Private key // Convert an Ed25519 Private Key to Curve25519 Private key
func (sk *PrivateKey) toCurve25519SK() []byte { func (sk *PrivateKey) ToCurve25519SK() []byte {
if sk.ck == nil { if sk.ck == nil {
var ek [64]byte var ek [64]byte
@ -284,12 +213,205 @@ func (sk *PrivateKey) toCurve25519SK() []byte {
return sk.ck 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 // from github.com/FiloSottile/age
var curve25519P, _ = new(big.Int).SetString("57896044618658097711785492504343953926634992332820282019728792003956564819949", 10) var curve25519P, _ = new(big.Int).SetString("57896044618658097711785492504343953926634992332820282019728792003956564819949", 10)
// Convert an Ed25519 Public Key to Curve25519 public key // Convert an Ed25519 Public Key to Curve25519 public key
// from github.com/FiloSottile/age // from github.com/FiloSottile/age
func (pk *PublicKey) toCurve25519PK() []byte { func (pk *PublicKey) ToCurve25519PK() []byte {
if pk.ck != nil { if pk.ck != nil {
return pk.ck return pk.ck
} }
@ -329,131 +451,8 @@ func (pk *PublicKey) Hash() []byte {
return pk.hash return pk.hash
} }
// Serialize the private key to a file // MarshalBinary marshals a PublicKey into a byte array
// AEAD encryption for protecting the private key func (pk *PublicKey) MarshalBinary(comment string) ([]byte, error) {
// 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 {
b64 := base64.StdEncoding.EncodeToString b64 := base64.StdEncoding.EncodeToString
spk := &serializedPubKey{ spk := &serializedPubKey{
Comment: comment, Comment: comment,
@ -461,57 +460,61 @@ func (pk *PublicKey) serialize(fn, comment string) error {
Hash: b64(pk.hash), Hash: b64(pk.hash),
} }
out, err := yaml.Marshal(spk) return yaml.Marshal(spk)
if err != nil { }
return fmt.Errorf("can't marahal to YAML: %s", err)
// 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
} }
return writeFile(fn, out, 0644) // 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 -- // -- 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. // Simple function to reliably write data to a file.
// Does MORE than ioutil.WriteFile() - in that it doesn't trash the // Does MORE than ioutil.WriteFile() - in that it doesn't trash the
// existing file with an incomplete write. // existing file with an incomplete write.
func writeFile(fn string, b []byte, mode uint32) error { func writeFile(fn string, b []byte, ovwrite bool, mode uint32) error {
tmp := fmt.Sprintf("%s.tmp", fn) sf, err := NewSafeFile(fn, ovwrite, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.FileMode(mode))
unlink(tmp)
fd, err := os.OpenFile(tmp, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.FileMode(mode))
if err != nil { 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) sf.Write(b)
if err != nil { return sf.Close()
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 // Generate file checksum out of hash function h
func fileCksum(fn string, h hash.Hash) ([]byte, error) { func fileCksum(fn string, h hash.Hash) ([]byte, error) {
fd, err := os.Open(fn) fd, err := os.Open(fn)
if err != nil { if err != nil {
return nil, fmt.Errorf("can't open %s: %s", fn, err) 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)) binary.BigEndian.PutUint64(b[:], uint64(sz))
h.Write(b[:]) h.Write(b[:])
return h.Sum(nil), nil return h.Sum(nil)[:], nil
} }
func clamp(k []byte) []byte { func clamp(k []byte) []byte {
@ -538,13 +541,5 @@ func clamp(k []byte) []byte {
return k 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 // EOF
// vim: noexpandtab:ts=8:sw=8:tw=92: // 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 nil, err
} }
return MakeSignature(yml) var sig Signature
return makeSignature(&sig, yml)
} }
// Parse serialized signature from bytes 'b' and construct a // Parse serialized signature from bytes 'b' and construct a
// Signature object // Signature object
func MakeSignature(b []byte) (*Signature, error) { func makeSignature(sig *Signature, b []byte) (*Signature, error) {
var ss signature var ss signature
err := yaml.Unmarshal(b, &ss) err := yaml.Unmarshal(b, &ss)
if err != nil { 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 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 // MarshalBinary marshals a signature into a byte stream with
func (sig *Signature) Serialize(comment string) ([]byte, error) { // an optional caller supplied comment.
func (sig *Signature) MarshalBinary(comment string) ([]byte, error) {
sigs := base64.StdEncoding.EncodeToString(sig.Sig) sigs := base64.StdEncoding.EncodeToString(sig.Sig)
pks := base64.StdEncoding.EncodeToString(sig.pkhash) pks := base64.StdEncoding.EncodeToString(sig.pkhash)
ss := &signature{Comment: comment, Pkhash: pks, Signature: sigs} ss := &signature{Comment: comment, Pkhash: pks, Signature: sigs}
out, err := yaml.Marshal(ss) return 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' // UnmarshalBinary constructs a Signature from a previously
func (sig *Signature) SerializeFile(fn, comment string) error { // serialized bytestream
b, err := sig.Serialize(comment) func (sig *Signature) UnmarshalBinary(b []byte) error {
_, err := makeSignature(sig, b)
return err
}
// Serialize a signature suitable for storing in durable media
func (sig *Signature) Serialize(fn, comment string, ovwrite bool) error {
b, err := sig.MarshalBinary(comment)
if err == nil { if err == nil {
err = writeFile(fn, b, 0644) err = writeFile(fn, b, ovwrite, 0644)
} }
return err return err
} }
@ -147,7 +152,6 @@ func (sig *Signature) IsPKMatch(pk *PublicKey) bool {
// Verify a signature 'sig' for file 'fn' against public key 'pk' // Verify a signature 'sig' for file 'fn' against public key 'pk'
// Return True if signature matches, False otherwise // Return True if signature matches, False otherwise
func (pk *PublicKey) VerifyFile(fn string, sig *Signature) (bool, error) { func (pk *PublicKey) VerifyFile(fn string, sig *Signature) (bool, error) {
ck, err := fileCksum(fn, sha512.New()) ck, err := fileCksum(fn, sha512.New())
if err != nil { if err != nil {
return false, err return false, err

View file

@ -38,16 +38,20 @@ func tempdir(t *testing.T) string {
return tmp return tmp
} }
var fixedPw = []byte("abc")
var badPw = []byte("def")
var nilPw []byte
// return a hardcoded password // return a hardcoded password
func hardcodedPw() ([]byte, error) { func hardcodedPw() ([]byte, error) {
return []byte("abc"), nil return fixedPw, nil
} }
func wrongPw() ([]byte, error) { func wrongPw() ([]byte, error) {
return []byte("xyz"), nil return badPw, nil
} }
func emptyPw() ([]byte, error) { func emptyPw() ([]byte, error) {
return nil, nil return nilPw, nil
} }
// Return true if file exists, false otherwise // Return true if file exists, false otherwise
@ -80,68 +84,78 @@ p: 1
func TestSignSimple(t *testing.T) { func TestSignSimple(t *testing.T) {
assert := newAsserter(t) assert := newAsserter(t)
kp, err := NewKeypair() sk, err := NewPrivateKey()
assert(err == nil, "NewKeyPair() fail") assert(err == nil, "NewPrivateKey() fail")
dn := tempdir(t) pk := sk.PublicKey()
dn := t.TempDir()
bn := fmt.Sprintf("%s/t0", dn) bn := fmt.Sprintf("%s/t0", dn)
err = kp.Serialize(bn, "", hardcodedPw)
assert(err == nil, "keyPair.Serialize() fail")
pkf := fmt.Sprintf("%s.pub", bn) pkf := fmt.Sprintf("%s.pub", bn)
skf := fmt.Sprintf("%s.key", bn) skf := fmt.Sprintf("%s.key", bn)
// We must find these two files err = pk.Serialize(pkf, "", true)
assert(fileExists(pkf), "missing pkf") assert(err == nil, "can't serialize pk %s", pkf)
assert(fileExists(skf), "missing skf")
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") assert(err == nil, "ReadPK() fail")
// -ditto- for Sk // send the public key as private key
sk, err := ReadPrivateKey(pkf, emptyPw) nsk, err := ReadPrivateKey(pkf, emptyPw)
assert(err != nil, "bad SK ReadSK fail: %s", err) assert(err != nil, "bad SK ReadSK fail: %s", err)
sk, err = ReadPrivateKey(skf, emptyPw) nsk, err = ReadPrivateKey(skf, emptyPw)
assert(err != nil, "ReadSK() empty pw fail: ks", err) assert(err != nil, "ReadSK() worked with empty pw")
sk, err = ReadPrivateKey(skf, wrongPw) nsk, err = ReadPrivateKey(skf, wrongPw)
assert(err != nil, "ReadSK() wrong pw fail: %s", err) assert(err != nil, "ReadSK() worked with wrong pw")
badf := fmt.Sprintf("%s/badf.key", dn) badf := fmt.Sprintf("%s/badf.key", dn)
err = ioutil.WriteFile(badf, []byte(badsk), 0600) 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) nsk, err = ReadPrivateKey(badf, hardcodedPw)
assert(err != nil, "badsk read fail: %s", err) assert(err != nil, "decoded bad SK")
// Finally, with correct password it should work. // Finally, with correct password it should work.
sk, err = ReadPrivateKey(skf, hardcodedPw) nsk, err = ReadPrivateKey(skf, hardcodedPw)
assert(err == nil, "ReadSK() correct pw fail") assert(err == nil, "ReadSK() correct pw fail: %s", err)
// And, deserialized keys should be identical // And, deserialized keys should be identical
assert(byteEq(pk.Pk, kp.Pub.Pk), "pkbytes unequal") assert(byteEq(pk.Pk, npk.Pk), "pkbytes unequal")
assert(byteEq(sk.Sk, kp.Sec.Sk), "skbytes unequal") assert(byteEq(sk.Sk, nsk.Sk), "skbytes unequal")
os.RemoveAll(dn)
} }
// #2. Create new key pair, sign a rand buffer and verify // #2. Create new key pair, sign a rand buffer and verify
func TestSignRandBuf(t *testing.T) { func TestSignRandBuf(t *testing.T) {
assert := newAsserter(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 var ck [64]byte // simulates sha512 sum
randRead(ck[:]) randRead(ck[:])
pk := &kp.Pub pk := sk.PublicKey()
sk := &kp.Sec
ss, err := sk.SignMessage(ck[:], "") 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") assert(ss != nil, "sig is null")
// verify sig // verify sig
@ -160,27 +174,13 @@ func TestSignRandBuf(t *testing.T) {
assert(ok, "verify fail") assert(ok, "verify fail")
// Now sign a file // Now sign a file
dn := tempdir(t) dn := t.TempDir()
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")
var buf [8192]byte var buf [8192]byte
zf := fmt.Sprintf("%s/file.dat", dn) zf := fmt.Sprintf("%s/file.dat", dn)
fd, err := os.OpenFile(zf, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) 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++ { for i := 0; i < 8; i++ {
randRead(buf[:]) randRead(buf[:])
@ -192,27 +192,31 @@ func TestSignRandBuf(t *testing.T) {
fd.Close() fd.Close()
sig, err := sk.SignFile(zf) 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") assert(sig != nil, "file.dat sign nil")
ok, err = pk.VerifyFile(zf, sig) 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") assert(ok, "file.dat verify false")
// Now, serialize the signature and read it back // Now, serialize the signature and read it back
sf := fmt.Sprintf("%s/file.sig", dn) sf := fmt.Sprintf("%s/file.sig", dn)
err = sig.SerializeFile(sf, "") err = sig.Serialize(sf, "", true)
assert(err == nil, "sig serialize fail") 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) 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(s2 != nil, "file.sig sig nil")
assert(byteEq(s2.Sig, sig.Sig), "sig compare fail") assert(byteEq(s2.Sig, sig.Sig), "sig compare fail")
// If we give a wrong file, verify must fail // If we give a wrong file, verify must fail
st, err := os.Stat(zf) st, err := os.Stat(zf)
assert(err == nil, "file.dat stat fail") assert(err == nil, "file.dat stat fail: %s", err)
n := st.Size() n := st.Size()
assert(n == 8192*8, "file.dat size fail") assert(n == 8192*8, "file.dat size fail")
@ -220,12 +224,12 @@ func TestSignRandBuf(t *testing.T) {
os.Truncate(zf, n-1) os.Truncate(zf, n-1)
st, err = os.Stat(zf) 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") assert(st.Size() == (n-1), "truncate fail")
// Now verify this corrupt file // Now verify this corrupt file
ok, err = pk.VerifyFile(zf, sig) 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") assert(!ok, "file.dat corrupt verify false")
os.RemoveAll(dn) os.RemoveAll(dn)
@ -233,7 +237,7 @@ func TestSignRandBuf(t *testing.T) {
func Benchmark_Keygen(b *testing.B) { func Benchmark_Keygen(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_, _ = NewKeypair() _, _ = NewPrivateKey()
} }
} }
@ -250,7 +254,8 @@ func Benchmark_Sig(b *testing.B) {
} }
b.StopTimer() b.StopTimer()
kp, _ := NewKeypair() sk, _ := NewPrivateKey()
pk := sk.PublicKey()
var sig *Signature var sig *Signature
for _, sz := range sizes { for _, sz := range sizes {
buf := randbuf(sz) buf := randbuf(sz)
@ -260,11 +265,11 @@ func Benchmark_Sig(b *testing.B) {
b.ResetTimer() b.ResetTimer()
b.Run(s0, func(b *testing.B) { 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) { 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 return nil, ErrBadTrailers
} }
pk, err := PublicKeyFromBytes(w.KeyBytes) var pk PublicKey
if err == nil {
if err = makePublicKeyFromBytes(&pk, w.KeyBytes); err == nil {
pk.Comment = strings.TrimSpace(comm) pk.Comment = strings.TrimSpace(comm)
return &pk, nil
} }
return pk, err return nil, err
} }
func parseString(in []byte) (out, rest []byte, ok bool) { 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) var sk PrivateKey
return pk, err if err = makePrivateKeyFromBytes(&sk, key.Priv); err == nil {
return &sk, nil
}
return nil, err
default: default:
return nil, fmt.Errorf("ssh: unhandled key type: %v", pk1.Keytype) 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.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(&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.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) fs.Parse(args)
@ -120,22 +120,19 @@ Options:
bn := args[0] bn := args[0]
if exists(bn) && !force { pkn := fmt.Sprintf("%s.pub", path.Clean(bn))
die("Public/Private key files (%s.key, %s.pub) exist. Won't overwrite!", bn, 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 err error
var pw []byte
kp, err := sign.NewKeypair() if !nopw {
if err != nil {
die("%s", err)
}
err = kp.Serialize(bn, comment, func() ([]byte, error) {
if nopw {
return nil, nil
}
var pws string var pws string
if len(envpw) > 0 { if len(envpw) > 0 {
pws = os.Getenv(envpw) pws = os.Getenv(envpw)
@ -145,16 +142,28 @@ Options:
die("%s", err) die("%s", err)
} }
} }
return []byte(pws), nil
}) pw = []byte(pws)
}
sk, err := sign.NewPrivateKey()
if err != nil { if err != nil {
die("%s", err) 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. // Run the 'sign' command.
func signify(args []string) { func signify(args []string) {
var nopw, help bool var nopw, help, force bool
var output string var output string
var envpw 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.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(&envpw, "env-password", "E", "", "Use passphrase from environment variable `E`")
fs.StringVarP(&output, "output", "o", "", "Write signature to file `F`") 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) fs.Parse(args)
@ -193,6 +203,19 @@ Options:
outf = output 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) { sk, err := sign.ReadPrivateKey(kn, func() ([]byte, error) {
if nopw { if nopw {
return nil, nil return nil, nil
@ -219,20 +242,9 @@ Options:
die("%s", err) die("%s", err)
} }
sigo, err := sig.Serialize(fmt.Sprintf("input=%s", fn)) sigbytes, err := sig.MarshalBinary(fmt.Sprintf("input=%s", fn))
fd.Write(sigbytes)
var fd io.Writer = os.Stdout fd.Close()
if outf != "-" {
fdx, err := os.OpenFile(outf, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
if err != nil {
die("can't create output file %s: %s", outf, err)
}
defer fdx.Close()
fd = fdx
}
fd.Write(sigo)
} }
// Verify signature on a given file // Verify signature on a given file
@ -323,14 +335,8 @@ Commands:
} }
// Return true if $bn.key or $bn.pub exist; false otherwise // Return true if $bn.key or $bn.pub exist; false otherwise
func exists(bn string) bool { func exists(nm string) bool {
pk := bn + ".pub" if _, err := os.Stat(nm); err == nil {
sk := bn + ".key"
if _, err := os.Stat(pk); err == nil {
return true
}
if _, err := os.Stat(sk); err == nil {
return true 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