Merge branch 'encdec'

This commit is contained in:
Sudhi Herle 2019-10-22 20:12:51 -07:00
commit b14f9d1e53
7 changed files with 247 additions and 11 deletions

View file

@ -89,7 +89,7 @@ e.g., to verify the signature of *archive.tar.gz* against
If the sender wishes to prove to the recipient that they encrypted
a file:
sigtool encrypt -s sender.key to.pub -o archive.tar.gz.enc archive.tar.gz
sigtool encrypt -s sender.key to.pub -o archive.tar.gz.enc archive.tar.gz
This will create an encrypted file *archive.tar.gz.enc* such that the
@ -101,7 +101,7 @@ who they expect.
If the receiver has the public key of the sender, they can verify that
they indeed sent the file by cryptographically checking the output:
sigtool decrypt -o archive.tar.gz -v sender.pub to.key archive.tar.gz.enc
sigtool decrypt -o archive.tar.gz -v sender.pub to.key archive.tar.gz.enc
Note that the verification is optional and if the `-v` option is not
used, then decryption will proceed without verifying the sender.
@ -110,7 +110,7 @@ used, then decryption will proceed without verifying the sender.
`sigtool` can generate ephemeral keys for encrypting a file such that
the receiver doesn't need to authenticate the sender:
sigtool encrypt to.pub -o archive.tar.gz.enc archive.tar.gz
sigtool encrypt to.pub -o archive.tar.gz.enc archive.tar.gz
This will create an encrypted file *archive.tar.gz.enc* such that the
recipient in possession of *to.key* can decrypt it.

View file

@ -35,17 +35,24 @@ func encrypt(args []string) {
var keyfile string
var envpw string
var pw bool
var sblksize string
fs.StringVarP(&outfile, "outfile", "o", "", "Write the output to file `F`")
fs.StringVarP(&keyfile, "sign", "s", "", "Sign using private key `S`")
fs.BoolVarP(&pw, "password", "p", false, "Ask for passphrase to decrypt the private key")
fs.StringVarP(&envpw, "env-password", "", "", "Use passphrase from environment variable `E`")
fs.StringVarP(&sblksize, "block-size", "B", "4M", "Use `S` as the encryption block size")
err := fs.Parse(args)
if err != nil {
die("%s", err)
}
blksize, err := utils.ParseSize(sblksize)
if err != nil {
die("%s", err)
}
var pws, infile string
if len(envpw) > 0 {
@ -108,7 +115,7 @@ func encrypt(args []string) {
outfd = outf
}
en, err := sign.NewEncryptor(sk)
en, err := sign.NewEncryptor(sk, blksize)
if err != nil {
die("%s", err)
}

2
go.mod
View file

@ -4,7 +4,7 @@ go 1.13
require (
github.com/gogo/protobuf v1.3.1
github.com/opencoff/go-utils v0.3.0
github.com/opencoff/go-utils v0.4.0
github.com/opencoff/pflag v0.3.3
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc
gopkg.in/yaml.v2 v2.2.4

2
go.sum
View file

@ -4,6 +4,8 @@ github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQL
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/opencoff/go-utils v0.3.0 h1:/TQXjf50o3GSB9MItog5L8Gf4GWJ4B5+rmqjB4g2RZQ=
github.com/opencoff/go-utils v0.3.0/go.mod h1:c+7QUAiCCHcNH6OGvsZ0fviG7cgse8Y3ucg+xy7sGXM=
github.com/opencoff/go-utils v0.4.0 h1:pu08Om//u2+YGvLkHa2CyL6eI+/1J0bXih1Z6nuITp8=
github.com/opencoff/go-utils v0.4.0/go.mod h1:c+7QUAiCCHcNH6OGvsZ0fviG7cgse8Y3ucg+xy7sGXM=
github.com/opencoff/pflag v0.3.3 h1:yohZkwYGPkB34WXvUQzU5GyLhImnjfePDARUaE8me3U=
github.com/opencoff/pflag v0.3.3/go.mod h1:mTLzGGUGda1Av3d34iAJlh0JIlRxmFZtmc6qoWPspK0=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=

218
sign/crypt_test.go Normal file
View file

@ -0,0 +1,218 @@
// crypt_test.go -- Test harness for encrypt/decrypt bits
//
// (c) 2016 Sudhi Herle <sudhi@herle.net>
//
// Licensing Terms: GPLv2
//
// If you need a commercial license for this work, please contact
// the author.
//
// This software does not come with any express or implied
// warranty; it is provided "as is". No claim is made to its
// suitability for any purpose.
package sign
import (
"bytes"
"crypto/rand"
"encoding/binary"
"fmt"
"io"
"testing"
)
// one sender, one receiver no verification of sender
func TestSimple(t *testing.T) {
assert := newAsserter(t)
receiver, err := NewKeypair()
assert(err == nil, "receiver keypair gen failed: %s", err)
// cleartext
buf := make([]byte, 64*1024)
for i := 0; i < len(buf); i++ {
buf[i] = byte(i & 0xff)
}
ee, err := NewEncryptor(nil, 4096)
assert(err == nil, "encryptor create fail: %s", err)
err = ee.AddRecipient(&receiver.Pub)
assert(err == nil, "can't add recipient: %s", err)
rd := bytes.NewBuffer(buf)
wr := bytes.Buffer{}
err = ee.Encrypt(rd, &wr)
assert(err == nil, "encrypt fail: %s", err)
rd = bytes.NewBuffer(wr.Bytes())
dd, err := NewDecryptor(rd)
assert(err == nil, "decryptor create fail: %s", err)
err = dd.SetPrivateKey(&receiver.Sec, nil)
assert(err == nil, "decryptor can't add SK: %s", err)
wr = bytes.Buffer{}
err = dd.Decrypt(&wr)
assert(err == nil, "decrypt fail: %s", err)
b := wr.Bytes()
assert(len(b) == len(buf), "decrypt length mismatch: exp %d, saw %d", len(buf), len(b))
assert(byteEq(b, buf), "decrypt content mismatch")
}
// test corrupted header or corrupted input
func TestCorrupted(t *testing.T) {
assert := newAsserter(t)
receiver, err := NewKeypair()
assert(err == nil, "receiver keypair gen failed: %s", err)
// cleartext
buf := make([]byte, 64*1024)
for i := 0; i < len(buf); i++ {
buf[i] = byte(i & 0xff)
}
ee, err := NewEncryptor(nil, 4096)
assert(err == nil, "encryptor create fail: %s", err)
err = ee.AddRecipient(&receiver.Pub)
assert(err == nil, "can't add recipient: %s", err)
rd := bytes.NewBuffer(buf)
wr := bytes.Buffer{}
err = ee.Encrypt(rd, &wr)
assert(err == nil, "encrypt fail: %s", err)
rb := wr.Bytes()
n := len(rb)
for i := 0; i < n; i++ {
j := randint() % n
rb[j] = byte(randint() & 0xff)
}
rd = bytes.NewBuffer(rb)
dd, err := NewDecryptor(rd)
assert(err != nil, "decryptor works on bad input")
assert(dd == nil, "decryptor not nil for bad input")
}
// one sender, one receiver with verification of sender
func TestSenderVerified(t *testing.T) {
assert := newAsserter(t)
sender, err := NewKeypair()
assert(err == nil, "sender keypair gen failed: %s", err)
receiver, err := NewKeypair()
assert(err == nil, "receiver keypair gen failed: %s", err)
// cleartext
buf := make([]byte, 64*1024)
for i := 0; i < len(buf); i++ {
buf[i] = byte(i & 0xff)
}
ee, err := NewEncryptor(&sender.Sec, 4096)
assert(err == nil, "encryptor create fail: %s", err)
err = ee.AddRecipient(&receiver.Pub)
assert(err == nil, "can't add recipient: %s", err)
rd := bytes.NewBuffer(buf)
wr := bytes.Buffer{}
err = ee.Encrypt(rd, &wr)
assert(err == nil, "encrypt fail: %s", err)
rd = bytes.NewBuffer(wr.Bytes())
dd, err := NewDecryptor(rd)
assert(err == nil, "decryptor create fail: %s", err)
err = dd.SetPrivateKey(&receiver.Sec, &sender.Pub)
assert(err == nil, "decryptor can't add SK: %s", err)
wr = bytes.Buffer{}
err = dd.Decrypt(&wr)
assert(err == nil, "decrypt fail: %s", err)
b := wr.Bytes()
assert(len(b) == len(buf), "decrypt length mismatch: exp %d, saw %d", len(buf), len(b))
assert(byteEq(b, buf), "decrypt content mismatch")
}
// one sender, multiple receivers, each decrypting the blob
func TestMultiReceiver(t *testing.T) {
assert := newAsserter(t)
sender, err := NewKeypair()
assert(err == nil, "sender keypair gen failed: %s", err)
// cleartext
buf := make([]byte, 64*1024)
for i := 0; i < len(buf); i++ {
buf[i] = byte(i & 0xff)
}
ee, err := NewEncryptor(&sender.Sec, 4096)
assert(err == nil, "encryptor create fail: %s", err)
n := 4
rx := make([]*Keypair, n)
for i := 0; i < n; i++ {
r, err := NewKeypair()
assert(err == nil, "can't make receiver key %d: %s", i, err)
rx[i] = r
err = ee.AddRecipient(&r.Pub)
assert(err == nil, "can't add recipient %d: %s", i, err)
}
rd := bytes.NewBuffer(buf)
wr := bytes.Buffer{}
err = ee.Encrypt(rd, &wr)
assert(err == nil, "encrypt fail: %s", err)
encBytes := wr.Bytes()
for i := 0; i < n; i++ {
rd = bytes.NewBuffer(encBytes)
dd, err := NewDecryptor(rd)
assert(err == nil, "decryptor %d create fail: %s", i, err)
err = dd.SetPrivateKey(&rx[i].Sec, &sender.Pub)
assert(err == nil, "decryptor can't add SK %d: %s", i, err)
wr = bytes.Buffer{}
err = dd.Decrypt(&wr)
assert(err == nil, "decrypt %d fail: %s", i, err)
b := wr.Bytes()
assert(len(b) == len(buf), "decrypt %d length mismatch: exp %d, saw %d", i, len(buf), len(b))
assert(byteEq(b, buf), "decrypt %d content mismatch", i)
}
}
func randint() int {
var b [4]byte
_, err := io.ReadFull(rand.Reader, b[:])
if err != nil {
panic(fmt.Sprintf("can't read 4 rand bytes: %s", err))
}
u := binary.BigEndian.Uint32(b[:])
return int(u & 0x7fffffff)
}

View file

@ -1,4 +1,4 @@
// cipher.go -- Ed25519 based encrypt/decrypt
// encrypt.go -- Ed25519 based encrypt/decrypt
//
// (c) 2016 Sudhi Herle <sudhi@herle.net>
//
@ -31,6 +31,7 @@ import (
// Encryption chunk size = 4MB
const chunkSize int = 4 * 1048576
const maxChunkSize int = 16 * 1048576
const _Magic = "SigTool"
const _MagicLen = len(_Magic)
@ -51,11 +52,19 @@ type Encryptor struct {
// Create a new Encryption context and use the optional private key 'sk' for
// signing any recipient keys. If 'sk' is nil, then ephmeral Curve25519 keys
// are generated and used with recipient's public key.
func NewEncryptor(sk *PrivateKey) (*Encryptor, error) {
func NewEncryptor(sk *PrivateKey, blksize uint64) (*Encryptor, error) {
if blksize >= uint64(maxChunkSize) {
return nil, fmt.Errorf("encrypt: Blocksize is too large (max 16M)")
}
if blksize == 0 {
blksize = uint64(chunkSize)
}
e := &Encryptor{
Header: Header{
ChunkSize: uint32(chunkSize),
ChunkSize: uint32(blksize),
Salt: make([]byte, _AEADNonceLen),
},
@ -282,7 +291,7 @@ func NewDecryptor(rd io.Reader) (*Decryptor, error) {
return nil, fmt.Errorf("decrypt: decode error: %s", err)
}
if d.ChunkSize == 0 || d.ChunkSize > (16*1048576) {
if d.ChunkSize == 0 || d.ChunkSize >= uint32(maxChunkSize) {
return nil, fmt.Errorf("decrypt: invalid chunkSize %d", d.ChunkSize)
}
@ -397,7 +406,7 @@ func (d *Decryptor) decrypt(i int) ([]byte, error) {
chunklen := int(binary.BigEndian.Uint32(b[:4]))
// Sanity check - in case of corrupt header
if chunklen > (d.ae.Overhead()+chunkSize) {
if chunklen > (d.ae.Overhead() + chunkSize) {
return nil, fmt.Errorf("decrypt: chunksize is too large (%d)", chunklen)
}

View file

@ -1 +1 @@
0.2.1
0.3.1