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:
parent
f180079586
commit
42bbe5ddeb
12 changed files with 745 additions and 466 deletions
|
@ -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
|
||||
|
|
63
crypt.go
63
crypt.go
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
557
sign/keys.go
557
sign/keys.go
|
@ -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
|
||||
}
|
||||
|
||||
// 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)
|
||||
sk := &PrivateKey{
|
||||
Sk: []byte(skb),
|
||||
pk: &PublicKey{
|
||||
Pk: []byte(pkb),
|
||||
hash: pkhash([]byte(pkb)),
|
||||
},
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
return &sk, nil
|
||||
}
|
||||
|
||||
// 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,21 +179,27 @@ func PrivateKeyFromBytes(buf []byte) (*PrivateKey, error) {
|
|||
Pk: []byte(edpk),
|
||||
hash: pkhash([]byte(edpk)),
|
||||
}
|
||||
sk := &PrivateKey{
|
||||
Sk: skb,
|
||||
pk: pk,
|
||||
}
|
||||
|
||||
return sk, nil
|
||||
sk.Sk = skb
|
||||
sk.pk = pk
|
||||
return 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 {
|
||||
return sk.pk
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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 --
|
||||
|
||||
// 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
40
sign/rand.go
Normal 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
124
sign/safefile.go
Normal 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
|
||||
}
|
38
sign/sign.go
38
sign/sign.go
|
@ -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 out, nil
|
||||
return yaml.Marshal(ss)
|
||||
}
|
||||
|
||||
// SerializeFile serializes the signature to an output file 'f'
|
||||
func (sig *Signature) SerializeFile(fn, comment string) error {
|
||||
b, err := sig.Serialize(comment)
|
||||
// UnmarshalBinary constructs a Signature from a previously
|
||||
// serialized bytestream
|
||||
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 {
|
||||
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
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
15
sign/ssh.go
15
sign/ssh.go
|
@ -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)
|
||||
}
|
||||
|
|
82
sigtool.go
82
sigtool.go
|
@ -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
67
tests.sh
Normal 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
|
Loading…
Add table
Reference in a new issue