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
12
README.md
12
README.md
|
@ -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
|
||||||
|
|
63
crypt.go
63
crypt.go
|
@ -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) {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
557
sign/keys.go
557
sign/keys.go
|
@ -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
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 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
|
||||||
|
|
|
@ -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)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
15
sign/ssh.go
15
sign/ssh.go
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
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.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
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