2019-10-21 13:28:27 -07:00
|
|
|
// encrypt.go -- Ed25519 based encrypt/decrypt
|
2019-10-09 14:52:34 -07:00
|
|
|
//
|
|
|
|
// (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.
|
2019-12-31 11:39:25 -08:00
|
|
|
//
|
2020-01-29 16:47:14 +05:30
|
|
|
|
|
|
|
// Implementation Notes for Encryption/Decryption:
|
|
|
|
//
|
2020-01-08 09:17:54 -08:00
|
|
|
// Header: has 3 parts:
|
|
|
|
// - Fixed sized header
|
|
|
|
// - Variable sized protobuf encoded header
|
|
|
|
// - SHA256 sum of both above.
|
|
|
|
//
|
2019-12-31 11:39:25 -08:00
|
|
|
// Fixed size header:
|
|
|
|
// - Magic: 7 bytes
|
|
|
|
// - Version: 1 byte
|
|
|
|
// - VLen: 4 byte
|
|
|
|
//
|
|
|
|
// Variable Length Segment:
|
2020-01-08 09:17:54 -08:00
|
|
|
// - Protobuf encoded, per-recipient wrapped keys
|
2019-12-31 11:39:25 -08:00
|
|
|
// - Shasum: 32 bytes (SHA256 of full header)
|
|
|
|
//
|
|
|
|
// The variable length segment consists of one or more
|
2020-03-20 17:40:52 -07:00
|
|
|
// recipients, each with their wrapped keys. This is encoded as
|
2019-12-31 11:39:25 -08:00
|
|
|
// a protobuf message. This protobuf encoded message immediately
|
|
|
|
// follows the fixed length header.
|
|
|
|
//
|
2020-03-20 17:40:52 -07:00
|
|
|
// The input data is encrypted with an expanded random 32-byte key:
|
|
|
|
// - Prefix_string = "Encrypt Nonce"
|
|
|
|
// - datakey = SHA256(Prefix_string || header_checksum || random_key)
|
|
|
|
// - The header checksum is mixed in the above process to ensure we
|
|
|
|
// catch any malicious modification of the header.
|
|
|
|
//
|
2020-01-29 16:47:14 +05:30
|
|
|
// The input data is broken up into "chunks"; each no larger than
|
|
|
|
// maxChunkSize. The default block size is "chunkSize". Each block
|
|
|
|
// is AEAD encrypted:
|
|
|
|
// AEAD nonce = SHA256(header.salt || block# || block-size)
|
|
|
|
//
|
|
|
|
// The encrypted block (includes the AEAD tag) length is written
|
|
|
|
// as a big-endian 4-byte prefix. The high-order bit of this length
|
|
|
|
// field is set for the last-block (denoting EOF).
|
|
|
|
//
|
|
|
|
// The encrypted blocks use an opinionated nonce length of 32 (_AEADNonceLen).
|
2019-10-09 14:52:34 -07:00
|
|
|
|
|
|
|
package sign
|
|
|
|
|
|
|
|
import (
|
2019-10-19 21:12:57 -05:00
|
|
|
"bytes"
|
2019-10-09 14:52:34 -07:00
|
|
|
"crypto/aes"
|
|
|
|
"crypto/cipher"
|
2020-03-23 10:44:40 -07:00
|
|
|
"crypto/ed25519"
|
2019-10-17 14:29:01 -07:00
|
|
|
"crypto/sha256"
|
2019-10-09 14:52:34 -07:00
|
|
|
"crypto/sha512"
|
2019-10-17 14:29:01 -07:00
|
|
|
"crypto/subtle"
|
2019-10-19 21:12:57 -05:00
|
|
|
"encoding/binary"
|
2019-10-09 14:52:34 -07:00
|
|
|
"fmt"
|
|
|
|
"golang.org/x/crypto/curve25519"
|
|
|
|
"golang.org/x/crypto/hkdf"
|
|
|
|
"io"
|
2020-03-20 17:40:52 -07:00
|
|
|
|
|
|
|
"github.com/opencoff/sigtool/internal/pb"
|
2019-10-09 14:52:34 -07:00
|
|
|
)
|
|
|
|
|
2019-10-17 14:29:01 -07:00
|
|
|
// Encryption chunk size = 4MB
|
2020-01-08 09:17:54 -08:00
|
|
|
const (
|
|
|
|
chunkSize uint32 = 4 * 1048576
|
|
|
|
maxChunkSize uint32 = 16 * 1048576
|
|
|
|
_EOF uint32 = 1 << 31
|
|
|
|
|
|
|
|
_Magic = "SigTool"
|
|
|
|
_MagicLen = len(_Magic)
|
2020-03-23 10:44:40 -07:00
|
|
|
_AEADNonceLen = 16
|
2020-01-08 09:17:54 -08:00
|
|
|
_FixedHdrLen = _MagicLen + 1 + 4
|
2020-03-23 10:44:40 -07:00
|
|
|
|
|
|
|
_WrapReceiverNonce = "Receiver Key Nonce"
|
|
|
|
_WrapSenderNonce = "Sender Sig Nonce"
|
|
|
|
_EncryptNonce = "Encrypt Nonce"
|
2020-01-08 09:17:54 -08:00
|
|
|
)
|
2019-10-17 14:29:01 -07:00
|
|
|
|
|
|
|
// Encryptor holds the encryption context
|
|
|
|
type Encryptor struct {
|
2020-03-20 17:40:52 -07:00
|
|
|
pb.Header
|
|
|
|
key []byte // file encryption key
|
|
|
|
|
|
|
|
ae cipher.AEAD
|
|
|
|
|
2020-03-23 10:44:40 -07:00
|
|
|
// ephemeral key
|
|
|
|
encSK []byte
|
2019-10-17 14:29:01 -07:00
|
|
|
|
|
|
|
started bool
|
|
|
|
|
2020-03-20 17:40:52 -07:00
|
|
|
hdrsum []byte
|
2020-02-14 18:47:25 -08:00
|
|
|
buf []byte
|
|
|
|
stream bool
|
2019-10-17 14:29:01 -07:00
|
|
|
}
|
|
|
|
|
2020-03-23 10:44:40 -07:00
|
|
|
// Create a new Encryption context for encrypting blocks of size 'blksize'.
|
|
|
|
// If 'sk' is not nil, authenticate the sender to each receiver.
|
2019-10-21 13:28:27 -07:00
|
|
|
func NewEncryptor(sk *PrivateKey, blksize uint64) (*Encryptor, error) {
|
2020-01-23 12:24:27 +05:30
|
|
|
var blksz uint32
|
2019-10-21 13:28:27 -07:00
|
|
|
|
2020-01-23 12:24:27 +05:30
|
|
|
switch {
|
|
|
|
case blksize == 0:
|
2019-11-07 11:57:01 +01:00
|
|
|
blksz = chunkSize
|
2020-01-23 12:24:27 +05:30
|
|
|
case blksize > uint64(maxChunkSize):
|
|
|
|
blksz = maxChunkSize
|
|
|
|
default:
|
|
|
|
blksz = uint32(blksize)
|
2019-10-21 13:28:27 -07:00
|
|
|
}
|
2019-10-17 14:29:01 -07:00
|
|
|
|
2020-03-23 10:44:40 -07:00
|
|
|
// generate ephemeral Curve25519 keys
|
|
|
|
esk, epk, err := newSender()
|
2020-03-20 17:40:52 -07:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("encrypt: %s", err)
|
2019-10-17 14:29:01 -07:00
|
|
|
}
|
|
|
|
|
2020-03-20 17:40:52 -07:00
|
|
|
key := make([]byte, 32)
|
|
|
|
salt := make([]byte, _AEADNonceLen)
|
2019-10-17 14:29:01 -07:00
|
|
|
|
2020-03-23 10:44:40 -07:00
|
|
|
randRead(key)
|
|
|
|
randRead(salt)
|
2020-03-20 17:40:52 -07:00
|
|
|
|
2020-03-23 10:44:40 -07:00
|
|
|
// if sender has provided their identity to authenticate, we sign the data-enc key
|
|
|
|
// and encrypt the signature. At no point will we send the sender's identity.
|
|
|
|
var senderSig []byte
|
2020-03-20 17:40:52 -07:00
|
|
|
if sk != nil {
|
2020-03-23 10:44:40 -07:00
|
|
|
sig, err := sk.SignMessage(key, "")
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("encrypt: can't sign: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
senderSig = sig.Sig
|
|
|
|
} else {
|
|
|
|
var zero [ed25519.SignatureSize]byte
|
|
|
|
senderSig = zero[:]
|
2019-10-17 14:29:01 -07:00
|
|
|
}
|
|
|
|
|
2020-03-23 10:44:40 -07:00
|
|
|
wSig, err := wrapSenderSig(senderSig, key, salt)
|
2019-10-17 14:29:01 -07:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("encrypt: %s", err)
|
|
|
|
}
|
|
|
|
|
2020-03-20 17:40:52 -07:00
|
|
|
e := &Encryptor{
|
|
|
|
Header: pb.Header{
|
2020-03-23 10:44:40 -07:00
|
|
|
ChunkSize: blksz,
|
|
|
|
Salt: salt,
|
|
|
|
Pk: epk,
|
|
|
|
SenderSign: wSig,
|
2020-03-20 17:40:52 -07:00
|
|
|
},
|
|
|
|
|
2020-03-23 10:44:40 -07:00
|
|
|
key: key,
|
|
|
|
encSK: esk,
|
2020-03-20 17:40:52 -07:00
|
|
|
}
|
|
|
|
|
2019-10-17 14:29:01 -07:00
|
|
|
return e, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add a new recipient to this encryption context.
|
|
|
|
func (e *Encryptor) AddRecipient(pk *PublicKey) error {
|
|
|
|
if e.started {
|
|
|
|
return fmt.Errorf("encrypt: can't add new recipient after encryption has started")
|
|
|
|
}
|
|
|
|
|
2020-03-23 10:44:40 -07:00
|
|
|
w, err := e.wrapKey(pk)
|
2020-03-20 17:40:52 -07:00
|
|
|
if err == nil {
|
|
|
|
e.Keys = append(e.Keys, w)
|
2019-10-17 14:29:01 -07:00
|
|
|
}
|
|
|
|
|
2020-03-20 17:40:52 -07:00
|
|
|
return err
|
2019-10-17 14:29:01 -07:00
|
|
|
}
|
|
|
|
|
2020-01-08 09:17:54 -08:00
|
|
|
// Encrypt the input stream 'rd' and write encrypted stream to 'wr'
|
2020-02-14 18:47:25 -08:00
|
|
|
func (e *Encryptor) Encrypt(rd io.Reader, wr io.WriteCloser) error {
|
|
|
|
if e.stream {
|
|
|
|
return fmt.Errorf("encrypt: can't use Encrypt() after using streaming I/O")
|
|
|
|
}
|
|
|
|
|
2020-01-08 09:17:54 -08:00
|
|
|
if !e.started {
|
|
|
|
err := e.start(wr)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
buf := make([]byte, e.ChunkSize)
|
|
|
|
|
|
|
|
var i uint32
|
|
|
|
var eof bool
|
|
|
|
for !eof {
|
|
|
|
n, err := io.ReadAtLeast(rd, buf, int(e.ChunkSize))
|
2020-02-02 21:32:16 +05:30
|
|
|
if err != nil {
|
|
|
|
switch err {
|
|
|
|
case io.EOF, io.ErrClosedPipe, io.ErrUnexpectedEOF:
|
|
|
|
eof = true
|
|
|
|
default:
|
|
|
|
return fmt.Errorf("encrypt: I/O read error: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-08 09:17:54 -08:00
|
|
|
if n >= 0 {
|
|
|
|
err = e.encrypt(buf[:n], wr, i, eof)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-10-17 14:29:01 -07:00
|
|
|
|
2020-01-08 09:17:54 -08:00
|
|
|
i++
|
|
|
|
}
|
|
|
|
}
|
2020-02-14 18:47:25 -08:00
|
|
|
|
|
|
|
return wr.Close()
|
2020-01-08 09:17:54 -08:00
|
|
|
}
|
2019-10-17 14:29:01 -07:00
|
|
|
|
2020-01-08 09:17:54 -08:00
|
|
|
// Begin the encryption process by writing the header
|
|
|
|
func (e *Encryptor) start(wr io.Writer) error {
|
|
|
|
varSize := e.Size()
|
2019-10-17 14:29:01 -07:00
|
|
|
|
2020-01-08 09:17:54 -08:00
|
|
|
buffer := make([]byte, _FixedHdrLen+varSize+sha256.Size)
|
|
|
|
fixHdr := buffer[:_FixedHdrLen]
|
|
|
|
varHdr := buffer[_FixedHdrLen:]
|
|
|
|
sumHdr := varHdr[varSize:]
|
2019-10-17 14:29:01 -07:00
|
|
|
|
2020-01-08 09:17:54 -08:00
|
|
|
// Now assemble the fixed header
|
|
|
|
copy(fixHdr[:], []byte(_Magic))
|
|
|
|
fixHdr[_MagicLen] = 1 // version #
|
|
|
|
binary.BigEndian.PutUint32(fixHdr[_MagicLen+1:], uint32(varSize))
|
2019-10-17 14:29:01 -07:00
|
|
|
|
|
|
|
// Now marshal the variable portion
|
2020-03-20 17:40:52 -07:00
|
|
|
_, err := e.MarshalTo(varHdr[:varSize])
|
2019-10-17 14:29:01 -07:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("encrypt: can't marshal header: %s", err)
|
|
|
|
}
|
|
|
|
|
2019-12-31 11:39:25 -08:00
|
|
|
// Now calculate checksum of everything
|
2019-10-17 14:29:01 -07:00
|
|
|
h := sha256.New()
|
2020-01-08 09:17:54 -08:00
|
|
|
h.Write(buffer[:_FixedHdrLen+varSize])
|
|
|
|
h.Sum(sumHdr[:0])
|
2019-10-17 14:29:01 -07:00
|
|
|
|
|
|
|
// Finally write it out
|
2020-01-08 09:17:54 -08:00
|
|
|
err = fullwrite(buffer, wr)
|
2019-10-17 14:29:01 -07:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("encrypt: %s", err)
|
|
|
|
}
|
|
|
|
|
2020-03-20 17:40:52 -07:00
|
|
|
// we mix the header checksum to create the encryption key
|
|
|
|
h = sha256.New()
|
2020-03-23 10:44:40 -07:00
|
|
|
h.Write([]byte(_EncryptNonce))
|
2020-03-20 17:40:52 -07:00
|
|
|
h.Write(e.key)
|
|
|
|
h.Write(sumHdr)
|
|
|
|
key := h.Sum(nil)
|
|
|
|
|
|
|
|
aes, err := aes.NewCipher(key)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("encrypt: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
ae, err := cipher.NewGCMWithNonceSize(aes, _AEADNonceLen)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("encrypt: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
e.buf = make([]byte, e.ChunkSize+4+uint32(ae.Overhead()))
|
|
|
|
e.ae = ae
|
|
|
|
|
2019-10-17 14:29:01 -07:00
|
|
|
e.started = true
|
|
|
|
return nil
|
2019-10-09 14:52:34 -07:00
|
|
|
}
|
|
|
|
|
2019-10-17 14:29:01 -07:00
|
|
|
// Write _all_ bytes of buffer 'buf'
|
|
|
|
func fullwrite(buf []byte, wr io.Writer) error {
|
|
|
|
n := len(buf)
|
|
|
|
|
|
|
|
for n > 0 {
|
|
|
|
m, err := wr.Write(buf)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("I/O error: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
n -= m
|
|
|
|
buf = buf[m:]
|
|
|
|
}
|
|
|
|
return nil
|
2019-10-09 14:52:34 -07:00
|
|
|
}
|
|
|
|
|
2019-10-17 14:29:01 -07:00
|
|
|
// encrypt exactly _one_ block of data
|
|
|
|
// The nonce for the block is: sha256(salt || chunkLen || block#)
|
|
|
|
// This protects the output stream from re-ordering attacks and length
|
|
|
|
// modification attacks. The encoded length & block number is used as
|
|
|
|
// additional data in the AEAD construction.
|
2019-11-07 11:57:01 +01:00
|
|
|
func (e *Encryptor) encrypt(buf []byte, wr io.Writer, i uint32, eof bool) error {
|
2019-10-17 14:29:01 -07:00
|
|
|
var b [8]byte
|
2020-03-23 10:44:40 -07:00
|
|
|
var nonceb [32]byte
|
2020-01-29 16:47:14 +05:30
|
|
|
var z uint32 = uint32(len(buf))
|
2019-10-17 14:29:01 -07:00
|
|
|
|
2019-11-07 11:57:01 +01:00
|
|
|
// mark last block
|
|
|
|
if eof {
|
2020-01-08 09:17:54 -08:00
|
|
|
z |= _EOF
|
2019-11-07 11:57:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
binary.BigEndian.PutUint32(b[:4], z)
|
|
|
|
binary.BigEndian.PutUint32(b[4:], i)
|
2019-10-17 14:29:01 -07:00
|
|
|
|
|
|
|
h := sha256.New()
|
|
|
|
h.Write(e.Salt)
|
|
|
|
h.Write(b[:])
|
2020-03-23 10:44:40 -07:00
|
|
|
nonce := h.Sum(nonceb[:0])[:e.ae.NonceSize()]
|
2019-10-17 14:29:01 -07:00
|
|
|
|
|
|
|
copy(e.buf[:4], b[:4])
|
|
|
|
cbuf := e.buf[4:]
|
|
|
|
c := e.ae.Seal(cbuf[:0], nonce, buf, b[:])
|
|
|
|
|
2020-01-29 16:47:14 +05:30
|
|
|
// total number of bytes written
|
2019-10-17 14:29:01 -07:00
|
|
|
n := len(c) + 4
|
|
|
|
err := fullwrite(e.buf[:n], wr)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("encrypt: %s", err)
|
|
|
|
}
|
|
|
|
return nil
|
2019-10-09 14:52:34 -07:00
|
|
|
}
|
|
|
|
|
2019-10-17 14:29:01 -07:00
|
|
|
// Decryptor holds the decryption context
|
|
|
|
type Decryptor struct {
|
2020-03-20 17:40:52 -07:00
|
|
|
pb.Header
|
2019-10-17 14:29:01 -07:00
|
|
|
|
2020-03-20 17:40:52 -07:00
|
|
|
ae cipher.AEAD
|
|
|
|
rd io.Reader
|
|
|
|
buf []byte
|
|
|
|
hdrsum []byte
|
2019-10-17 14:29:01 -07:00
|
|
|
|
2020-03-23 10:44:40 -07:00
|
|
|
// flag set to true if sender signed the key
|
|
|
|
auth bool
|
|
|
|
|
2019-10-17 14:29:01 -07:00
|
|
|
// Decrypted key
|
2020-02-14 18:47:25 -08:00
|
|
|
key []byte
|
|
|
|
eof bool
|
|
|
|
stream bool
|
2019-10-09 14:52:34 -07:00
|
|
|
}
|
|
|
|
|
2019-10-17 14:29:01 -07:00
|
|
|
// Create a new decryption context and if 'pk' is given, check that it matches
|
|
|
|
// the sender
|
2019-10-18 15:42:08 -07:00
|
|
|
func NewDecryptor(rd io.Reader) (*Decryptor, error) {
|
2019-12-31 11:39:25 -08:00
|
|
|
var b [_FixedHdrLen]byte
|
2019-10-17 14:29:01 -07:00
|
|
|
|
2019-10-19 21:12:57 -05:00
|
|
|
_, err := io.ReadFull(rd, b[:])
|
2019-10-17 14:29:01 -07:00
|
|
|
if err != nil {
|
2019-11-07 11:57:01 +01:00
|
|
|
return nil, fmt.Errorf("decrypt: err while reading header: %s", err)
|
2019-10-17 14:29:01 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if bytes.Compare(b[:_MagicLen], []byte(_Magic)) != 0 {
|
|
|
|
return nil, fmt.Errorf("decrypt: Not a sigtool encrypted file?")
|
|
|
|
}
|
|
|
|
|
|
|
|
if b[_MagicLen] != 1 {
|
|
|
|
return nil, fmt.Errorf("decrypt: Unsupported version %d", b[_MagicLen])
|
|
|
|
}
|
|
|
|
|
2020-01-08 09:17:54 -08:00
|
|
|
varSize := binary.BigEndian.Uint32(b[_MagicLen+1:])
|
2019-12-31 11:39:25 -08:00
|
|
|
|
|
|
|
// sanity check on variable segment length
|
2020-01-08 09:17:54 -08:00
|
|
|
if varSize > 1048576 {
|
2019-12-31 11:39:25 -08:00
|
|
|
return nil, fmt.Errorf("decrypt: header too large (max 1048576)")
|
2019-10-17 14:29:01 -07:00
|
|
|
}
|
2020-01-08 09:17:54 -08:00
|
|
|
if varSize < 32 {
|
2019-10-17 14:29:01 -07:00
|
|
|
return nil, fmt.Errorf("decrypt: header too small (min 32)")
|
|
|
|
}
|
|
|
|
|
2020-01-08 09:17:54 -08:00
|
|
|
// SHA256 is the trailer part of the file-header
|
|
|
|
varBuf := make([]byte, varSize+sha256.Size)
|
2019-10-17 14:29:01 -07:00
|
|
|
|
2020-01-08 09:17:54 -08:00
|
|
|
_, err = io.ReadFull(rd, varBuf)
|
2019-10-17 14:29:01 -07:00
|
|
|
if err != nil {
|
2019-11-07 11:57:01 +01:00
|
|
|
return nil, fmt.Errorf("decrypt: err while reading header: %s", err)
|
2019-10-17 14:29:01 -07:00
|
|
|
}
|
|
|
|
|
2020-01-08 09:17:54 -08:00
|
|
|
verify := varBuf[varSize:]
|
2019-12-31 11:39:25 -08:00
|
|
|
|
|
|
|
h := sha256.New()
|
|
|
|
h.Write(b[:])
|
2020-01-08 09:17:54 -08:00
|
|
|
h.Write(varBuf[:varSize])
|
2019-12-31 11:39:25 -08:00
|
|
|
cksum := h.Sum(nil)
|
2019-10-17 14:29:01 -07:00
|
|
|
|
|
|
|
if subtle.ConstantTimeCompare(verify, cksum[:]) == 0 {
|
|
|
|
return nil, fmt.Errorf("decrypt: header corrupted")
|
|
|
|
}
|
|
|
|
|
|
|
|
d := &Decryptor{
|
2020-03-20 17:40:52 -07:00
|
|
|
rd: rd,
|
|
|
|
hdrsum: cksum,
|
2019-10-09 14:52:34 -07:00
|
|
|
}
|
|
|
|
|
2020-03-20 17:40:52 -07:00
|
|
|
err = d.Unmarshal(varBuf[:varSize])
|
2019-10-17 14:29:01 -07:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("decrypt: decode error: %s", err)
|
2019-10-09 14:52:34 -07:00
|
|
|
}
|
|
|
|
|
2019-11-07 11:57:01 +01:00
|
|
|
if d.ChunkSize == 0 || d.ChunkSize >= maxChunkSize {
|
2019-10-17 14:29:01 -07:00
|
|
|
return nil, fmt.Errorf("decrypt: invalid chunkSize %d", d.ChunkSize)
|
2019-10-09 14:52:34 -07:00
|
|
|
}
|
|
|
|
|
2020-01-29 16:47:14 +05:30
|
|
|
if len(d.Salt) != _AEADNonceLen {
|
2019-10-17 14:29:01 -07:00
|
|
|
return nil, fmt.Errorf("decrypt: invalid nonce length %d", len(d.Salt))
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(d.Keys) == 0 {
|
|
|
|
return nil, fmt.Errorf("decrypt: no wrapped keys")
|
|
|
|
}
|
|
|
|
|
|
|
|
// sanity check on the wrapped keys
|
|
|
|
for i, w := range d.Keys {
|
2020-03-23 10:44:40 -07:00
|
|
|
if len(w.DKey) <= 32 {
|
2020-03-20 17:40:52 -07:00
|
|
|
return nil, fmt.Errorf("decrypt: wrapped key %d: wrong-size encrypted key", i)
|
2019-10-17 14:29:01 -07:00
|
|
|
}
|
|
|
|
}
|
2019-10-09 14:52:34 -07:00
|
|
|
|
2019-10-17 14:29:01 -07:00
|
|
|
return d, nil
|
|
|
|
}
|
|
|
|
|
2019-10-18 15:42:08 -07:00
|
|
|
// Use Private Key 'sk' to decrypt the encrypted keys in the header and optionally validate
|
|
|
|
// the sender
|
|
|
|
func (d *Decryptor) SetPrivateKey(sk *PrivateKey, senderPk *PublicKey) error {
|
2019-10-17 14:29:01 -07:00
|
|
|
var err error
|
2020-03-20 17:40:52 -07:00
|
|
|
var key []byte
|
2019-10-17 14:29:01 -07:00
|
|
|
|
|
|
|
for i, w := range d.Keys {
|
2020-03-23 10:44:40 -07:00
|
|
|
key, err = d.unwrapKey(w, sk)
|
2020-03-20 17:40:52 -07:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("decrypt: can't unwrap key %d: %s", i, err)
|
|
|
|
}
|
|
|
|
if key != nil {
|
2019-10-17 14:29:01 -07:00
|
|
|
goto havekey
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-20 17:40:52 -07:00
|
|
|
return fmt.Errorf("decrypt: wrong key")
|
2019-10-09 14:52:34 -07:00
|
|
|
|
2019-10-17 14:29:01 -07:00
|
|
|
havekey:
|
2020-03-23 10:44:40 -07:00
|
|
|
if err := d.verifySender(key, sk, senderPk); err != nil {
|
|
|
|
return fmt.Errorf("decrypt: %s", err)
|
2020-03-20 17:40:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
d.key = key
|
|
|
|
|
|
|
|
// we mix the header checksum into the key
|
|
|
|
h := sha256.New()
|
2020-03-23 10:44:40 -07:00
|
|
|
h.Write([]byte(_EncryptNonce))
|
2020-03-20 17:40:52 -07:00
|
|
|
h.Write(d.key)
|
|
|
|
h.Write(d.hdrsum)
|
|
|
|
key = h.Sum(nil)
|
|
|
|
|
|
|
|
aes, err := aes.NewCipher(key)
|
2019-10-17 14:29:01 -07:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("decrypt: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
d.ae, err = cipher.NewGCMWithNonceSize(aes, _AEADNonceLen)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("decrypt: %s", err)
|
|
|
|
}
|
2019-10-19 21:12:57 -05:00
|
|
|
d.buf = make([]byte, int(d.ChunkSize)+d.ae.Overhead())
|
2019-10-17 14:29:01 -07:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-03-23 10:44:40 -07:00
|
|
|
// AuthenticatedSender returns true if the sender authenticated themselves
|
|
|
|
// (the data-encryption key is signed).
|
|
|
|
func (d *Decryptor) AuthenticatedSender() bool {
|
|
|
|
return d.auth
|
|
|
|
}
|
|
|
|
|
|
|
|
// Decrypt the file and write to 'wr'
|
|
|
|
func (d *Decryptor) Decrypt(wr io.Writer) error {
|
|
|
|
if d.key == nil {
|
|
|
|
return fmt.Errorf("decrypt: wrapped-key not decrypted (missing SetPrivateKey()?")
|
|
|
|
}
|
|
|
|
|
|
|
|
if d.stream {
|
|
|
|
return fmt.Errorf("decrypt: can't use Decrypt() after using streaming I/O")
|
|
|
|
}
|
|
|
|
|
|
|
|
if d.eof {
|
|
|
|
return io.EOF
|
|
|
|
}
|
|
|
|
|
|
|
|
var i uint32
|
|
|
|
for i = 0; ; i++ {
|
|
|
|
c, eof, err := d.decrypt(i)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if len(c) > 0 {
|
|
|
|
err = fullwrite(c, wr)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("decrypt: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if eof {
|
|
|
|
d.eof = true
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wrap sender's signature of the encryption key
|
|
|
|
func wrapSenderSig(sig []byte, key, salt []byte) ([]byte, error) {
|
|
|
|
aes, err := aes.NewCipher(key)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("wrap: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
ae, err := cipher.NewGCM(aes)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("wrap: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
tagsize := ae.Overhead()
|
|
|
|
nonceSize := ae.NonceSize()
|
|
|
|
|
|
|
|
nonce := makeNonce([]byte(_WrapSenderNonce), salt)[:nonceSize]
|
|
|
|
esig := make([]byte, tagsize+len(sig))
|
|
|
|
|
|
|
|
return ae.Seal(esig[:0], nonce, sig, nil), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// unwrap sender's signature using 'key' and extract the signature
|
|
|
|
// Optionally, verify the signature using the sender's PK (if provided).
|
|
|
|
func (d *Decryptor) verifySender(key []byte, sk *PrivateKey, senderPK *PublicKey) error {
|
|
|
|
aes, err := aes.NewCipher(key)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("unwrap: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
ae, err := cipher.NewGCM(aes)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("unwrap: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
nonceSize := ae.NonceSize()
|
|
|
|
nonce := makeNonce([]byte(_WrapSenderNonce), d.Salt)[:nonceSize]
|
|
|
|
sig := make([]byte, ed25519.SignatureSize)
|
|
|
|
sig, err = ae.Open(sig[:0], nonce, d.SenderSign, nil)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("unwrap: can't open sender info: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var zero [ed25519.SignatureSize]byte
|
|
|
|
|
|
|
|
// Did the sender actually sign anything?
|
|
|
|
if subtle.ConstantTimeCompare(zero[:], sig) == 0 {
|
|
|
|
d.auth = true
|
|
|
|
|
|
|
|
if senderPK != nil {
|
|
|
|
ss := &Signature{
|
|
|
|
Sig: sig,
|
|
|
|
}
|
|
|
|
|
|
|
|
ok := senderPK.VerifyMessage(key, ss)
|
|
|
|
if !ok {
|
|
|
|
return fmt.Errorf("unwrap: sender verification failed")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-03-20 17:40:52 -07:00
|
|
|
// Wrap data encryption key 'k' with the sender's PK and our ephemeral curve SK
|
2020-03-23 10:44:40 -07:00
|
|
|
// basically, we do two scalarmults:
|
|
|
|
// a) Ephemeral encryption/decryption SK x receiver PK
|
|
|
|
// b) Sender's SK x receiver PK
|
|
|
|
func (e *Encryptor) wrapKey(pk *PublicKey) (*pb.WrappedKey, error) {
|
|
|
|
rxPK := pk.toCurve25519PK()
|
|
|
|
dkek, err := curve25519.X25519(e.encSK, rxPK)
|
2020-03-20 17:40:52 -07:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("wrap: %s", err)
|
|
|
|
}
|
|
|
|
|
2020-03-23 10:44:40 -07:00
|
|
|
aes, err := aes.NewCipher(dkek)
|
2020-03-20 17:40:52 -07:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("wrap: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
ae, err := cipher.NewGCM(aes)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("wrap: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
tagsize := ae.Overhead()
|
2020-03-23 10:44:40 -07:00
|
|
|
nonceSize := ae.NonceSize()
|
|
|
|
|
|
|
|
nonceR := makeNonce([]byte(_WrapReceiverNonce), e.Salt)[:nonceSize]
|
|
|
|
ekey := make([]byte, tagsize+len(e.key))
|
|
|
|
|
|
|
|
w := &pb.WrappedKey{
|
|
|
|
DKey: ae.Seal(ekey[:0], nonceR, e.key, pk.Pk),
|
|
|
|
}
|
2020-03-20 17:40:52 -07:00
|
|
|
|
2020-03-23 10:44:40 -07:00
|
|
|
return w, nil
|
2020-03-20 17:40:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Unwrap a wrapped key using the receivers Ed25519 secret key 'sk' and
|
|
|
|
// senders ephemeral PublicKey
|
2020-03-23 10:44:40 -07:00
|
|
|
func (d *Decryptor) unwrapKey(w *pb.WrappedKey, sk *PrivateKey) ([]byte, error) {
|
2020-03-20 17:40:52 -07:00
|
|
|
ourSK := sk.toCurve25519SK()
|
2020-03-23 10:44:40 -07:00
|
|
|
dkek, err := curve25519.X25519(ourSK, d.Pk)
|
2020-03-20 17:40:52 -07:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("unwrap: %s", err)
|
|
|
|
}
|
|
|
|
|
2020-03-23 10:44:40 -07:00
|
|
|
aes, err := aes.NewCipher(dkek)
|
2020-03-20 17:40:52 -07:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("unwrap: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
ae, err := cipher.NewGCM(aes)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("unwrap: %s", err)
|
|
|
|
}
|
|
|
|
|
2020-03-23 10:44:40 -07:00
|
|
|
// 32 == AES-256 key size
|
2020-03-20 17:40:52 -07:00
|
|
|
want := 32 + ae.Overhead()
|
2020-03-23 10:44:40 -07:00
|
|
|
if len(w.DKey) != want {
|
|
|
|
return nil, fmt.Errorf("unwrap: incorrect decrypt bytes (need %d, saw %d)", want, len(w.DKey))
|
2020-03-20 17:40:52 -07:00
|
|
|
}
|
|
|
|
|
2020-03-23 10:44:40 -07:00
|
|
|
nonceSize := ae.NonceSize()
|
|
|
|
|
|
|
|
nonceR := makeNonce([]byte(_WrapReceiverNonce), d.Salt)[:nonceSize]
|
2020-03-20 17:40:52 -07:00
|
|
|
pk := sk.PublicKey()
|
2020-03-23 10:44:40 -07:00
|
|
|
|
|
|
|
dkey := make([]byte, 32) // decrypted data decryption key
|
2020-03-20 17:40:52 -07:00
|
|
|
|
|
|
|
// we indicate incorrect receiver SK by returning a nil key
|
2020-03-23 10:44:40 -07:00
|
|
|
dkey, err = ae.Open(dkey[:0], nonceR, w.DKey, pk.Pk)
|
2020-03-20 17:40:52 -07:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2020-03-23 10:44:40 -07:00
|
|
|
return dkey, nil
|
2019-10-17 14:29:01 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Decrypt exactly one chunk of data
|
2019-11-07 11:57:01 +01:00
|
|
|
func (d *Decryptor) decrypt(i uint32) ([]byte, bool, error) {
|
2019-10-17 14:29:01 -07:00
|
|
|
var b [8]byte
|
|
|
|
var nonceb [32]byte
|
2020-01-29 16:47:14 +05:30
|
|
|
var ovh uint32 = uint32(d.ae.Overhead())
|
|
|
|
var p []byte
|
2019-10-09 14:52:34 -07:00
|
|
|
|
2019-10-17 14:29:01 -07:00
|
|
|
n, err := io.ReadFull(d.rd, b[:4])
|
2020-01-29 16:47:14 +05:30
|
|
|
if err != nil || n == 0 {
|
2020-01-08 09:17:54 -08:00
|
|
|
return nil, false, fmt.Errorf("decrypt: premature EOF while reading header block %d", i)
|
2019-10-09 14:52:34 -07:00
|
|
|
}
|
|
|
|
|
2019-11-07 11:57:01 +01:00
|
|
|
m := binary.BigEndian.Uint32(b[:4])
|
2020-01-08 09:17:54 -08:00
|
|
|
eof := (m & _EOF) > 0
|
2019-11-07 11:57:01 +01:00
|
|
|
|
2020-01-08 09:17:54 -08:00
|
|
|
m &= (_EOF - 1)
|
2019-10-19 21:12:57 -05:00
|
|
|
|
|
|
|
// Sanity check - in case of corrupt header
|
2020-01-29 16:47:14 +05:30
|
|
|
switch {
|
|
|
|
case m > uint32(d.ChunkSize):
|
2019-11-07 11:57:01 +01:00
|
|
|
return nil, false, fmt.Errorf("decrypt: chunksize is too large (%d)", m)
|
2020-01-29 16:47:14 +05:30
|
|
|
|
|
|
|
case m == 0:
|
|
|
|
if !eof {
|
|
|
|
return nil, false, fmt.Errorf("decrypt: block %d: zero-sized chunk without EOF", i)
|
|
|
|
}
|
|
|
|
return p, eof, nil
|
|
|
|
|
|
|
|
case m < ovh:
|
|
|
|
return nil, false, fmt.Errorf("decrypt: chunksize is too small (%d)", m)
|
|
|
|
|
|
|
|
default:
|
2019-10-19 21:12:57 -05:00
|
|
|
}
|
|
|
|
|
2019-11-07 11:57:01 +01:00
|
|
|
binary.BigEndian.PutUint32(b[4:], i)
|
2019-10-17 14:29:01 -07:00
|
|
|
h := sha256.New()
|
|
|
|
h.Write(d.Salt)
|
|
|
|
h.Write(b[:])
|
2020-03-23 10:44:40 -07:00
|
|
|
nonce := h.Sum(nonceb[:0])[:d.ae.NonceSize()]
|
2019-10-17 14:29:01 -07:00
|
|
|
|
2020-01-29 16:47:14 +05:30
|
|
|
z := m + ovh
|
|
|
|
n, err = io.ReadFull(d.rd, d.buf[:z])
|
|
|
|
if err != nil {
|
|
|
|
return nil, false, fmt.Errorf("decrypt: premature EOF while reading block %d: %s", i, err)
|
2019-10-09 14:52:34 -07:00
|
|
|
}
|
|
|
|
|
2020-01-29 16:47:14 +05:30
|
|
|
p, err = d.ae.Open(d.buf[:0], nonce, d.buf[:n], b[:])
|
|
|
|
if err != nil {
|
|
|
|
return nil, false, fmt.Errorf("decrypt: can't decrypt chunk %d: %s", i, err)
|
2019-10-09 14:52:34 -07:00
|
|
|
}
|
|
|
|
|
2020-01-29 16:47:14 +05:30
|
|
|
return p[:m], eof, nil
|
2019-10-09 14:52:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// generate a KEK from a shared DH key and a Pub Key
|
|
|
|
func expand(shared, pk []byte) ([]byte, error) {
|
|
|
|
kek := make([]byte, 32)
|
|
|
|
h := hkdf.New(sha512.New, shared, pk, nil)
|
|
|
|
_, err := io.ReadFull(h, kek)
|
|
|
|
return kek, err
|
|
|
|
}
|
|
|
|
|
2020-03-20 17:40:52 -07:00
|
|
|
func newSender() (sk, pk []byte, err error) {
|
|
|
|
var csk [32]byte
|
2019-10-09 14:52:34 -07:00
|
|
|
|
2020-03-23 10:44:40 -07:00
|
|
|
randRead(csk[:])
|
|
|
|
clamp(csk[:])
|
2020-03-20 17:40:52 -07:00
|
|
|
pk, err = curve25519.X25519(csk[:], curve25519.Basepoint)
|
|
|
|
sk = csk[:]
|
|
|
|
return
|
2019-10-09 14:52:34 -07:00
|
|
|
}
|
2020-03-23 10:44:40 -07:00
|
|
|
|
|
|
|
func makeNonce(v ...[]byte) []byte {
|
|
|
|
h := sha256.New()
|
|
|
|
for _, x := range v {
|
|
|
|
h.Write(x)
|
|
|
|
}
|
|
|
|
return h.Sum(nil)[:]
|
|
|
|
}
|
|
|
|
|
|
|
|
// EOF
|