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.
|
|
|
|
|
|
|
|
package sign
|
|
|
|
|
|
|
|
import (
|
2019-10-21 13:28:27 -07:00
|
|
|
"bytes"
|
2019-10-09 14:52:34 -07:00
|
|
|
"crypto/aes"
|
|
|
|
"crypto/cipher"
|
|
|
|
"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-21 13:28:27 -07:00
|
|
|
"encoding/binary"
|
2019-10-09 14:52:34 -07:00
|
|
|
"fmt"
|
|
|
|
"golang.org/x/crypto/curve25519"
|
|
|
|
"golang.org/x/crypto/hkdf"
|
|
|
|
"io"
|
|
|
|
"math/big"
|
|
|
|
)
|
|
|
|
|
2019-10-17 14:29:01 -07:00
|
|
|
// Encryption chunk size = 4MB
|
|
|
|
const chunkSize int = 4 * 1048576
|
|
|
|
|
|
|
|
const _Magic = "SigTool"
|
|
|
|
const _MagicLen = len(_Magic)
|
|
|
|
const _AEADNonceLen = 32
|
|
|
|
|
|
|
|
// Encryptor holds the encryption context
|
|
|
|
type Encryptor struct {
|
|
|
|
Header
|
2019-10-21 13:28:27 -07:00
|
|
|
key [32]byte // file encryption key
|
2019-10-17 14:29:01 -07:00
|
|
|
|
2019-10-21 13:28:27 -07:00
|
|
|
ae cipher.AEAD
|
|
|
|
sender *PrivateKey
|
2019-10-17 14:29:01 -07:00
|
|
|
started bool
|
|
|
|
|
|
|
|
buf []byte
|
|
|
|
}
|
|
|
|
|
2019-10-21 13:28:27 -07:00
|
|
|
// Create a new Encryption context and use the optional private key 'sk' for
|
2019-10-17 14:29:01 -07:00
|
|
|
// signing any recipient keys. If 'sk' is nil, then ephmeral Curve25519 keys
|
|
|
|
// are generated and used with recipient's public key.
|
2019-10-21 13:28:27 -07:00
|
|
|
func NewEncryptor(sk *PrivateKey, blksize uint64) (*Encryptor, error) {
|
|
|
|
|
|
|
|
if blksize > (16 * 1048576) {
|
|
|
|
return nil, fmt.Errorf("encrypt: Blocksize is too large (max 16M)")
|
|
|
|
}
|
|
|
|
|
|
|
|
if blksize == 0 {
|
|
|
|
blksize = uint64(chunkSize)
|
|
|
|
}
|
2019-10-17 14:29:01 -07:00
|
|
|
|
|
|
|
e := &Encryptor{
|
|
|
|
Header: Header{
|
2019-10-21 13:28:27 -07:00
|
|
|
ChunkSize: uint32(blksize),
|
|
|
|
Salt: make([]byte, _AEADNonceLen),
|
2019-10-17 14:29:01 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
sender: sk,
|
|
|
|
}
|
|
|
|
|
|
|
|
randread(e.key[:])
|
|
|
|
randread(e.Salt)
|
|
|
|
|
|
|
|
aes, err := aes.NewCipher(e.key[:])
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("encrypt: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
e.ae, err = cipher.NewGCMWithNonceSize(aes, _AEADNonceLen)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("encrypt: %s", err)
|
|
|
|
}
|
|
|
|
|
2019-10-21 13:28:27 -07:00
|
|
|
e.buf = make([]byte, chunkSize+4+e.ae.Overhead())
|
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")
|
|
|
|
}
|
|
|
|
|
|
|
|
var w *WrappedKey
|
|
|
|
var err error
|
|
|
|
|
|
|
|
if e.sender != nil {
|
|
|
|
w, err = e.sender.WrapKey(pk, e.key[:])
|
|
|
|
} else {
|
|
|
|
w, err = pk.WrapKeyEphemeral(e.key[:])
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
e.Keys = append(e.Keys, w)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Begin the encryption process by writing the header
|
|
|
|
func (e *Encryptor) start(wr io.Writer) error {
|
|
|
|
msize := e.Size()
|
|
|
|
|
|
|
|
// marshal the header and recipients
|
|
|
|
hdrlen := _MagicLen + 1 + 4 + sha256.Size
|
|
|
|
|
2019-10-21 13:28:27 -07:00
|
|
|
buf := make([]byte, hdrlen+msize)
|
2019-10-17 14:29:01 -07:00
|
|
|
hdrbuf := buf[hdrlen:]
|
|
|
|
|
|
|
|
copy(buf[:], []byte(_Magic))
|
|
|
|
|
2019-10-21 13:28:27 -07:00
|
|
|
buf[_MagicLen] = 1 // file version#
|
2019-10-17 14:29:01 -07:00
|
|
|
|
|
|
|
// The fixed header is the magic _and _ the length of the variable segment.
|
|
|
|
// So, we capture the length of the variable portion first.
|
2019-10-21 13:28:27 -07:00
|
|
|
binary.BigEndian.PutUint32(buf[_MagicLen+1:], uint32(sha256.Size+msize))
|
2019-10-17 14:29:01 -07:00
|
|
|
|
|
|
|
// Now marshal the variable portion
|
|
|
|
_, err := e.MarshalToSizedBuffer(hdrbuf)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("encrypt: can't marshal header: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// and calculate the header checksum
|
2019-10-21 13:28:27 -07:00
|
|
|
cksum := buf[_MagicLen+1+4:]
|
2019-10-17 14:29:01 -07:00
|
|
|
h := sha256.New()
|
|
|
|
h.Write(hdrbuf)
|
|
|
|
h.Sum(cksum[:0])
|
|
|
|
|
|
|
|
// Finally write it out
|
|
|
|
err = fullwrite(buf, wr)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("encrypt: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
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 the input stream 'rd' and write encrypted stream to 'wr'
|
|
|
|
func (e *Encryptor) Encrypt(rd io.Reader, wr io.Writer) error {
|
|
|
|
if !e.started {
|
|
|
|
err := e.start(wr)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
buf := make([]byte, e.ChunkSize)
|
|
|
|
i := 0
|
|
|
|
|
|
|
|
for {
|
|
|
|
n, err := io.ReadAtLeast(rd, buf, int(e.ChunkSize))
|
|
|
|
if n == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if n > 0 {
|
|
|
|
err = e.encrypt(buf[:n], wr, i)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
i++
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil && err != io.EOF {
|
|
|
|
return fmt.Errorf("encrypt: I/O read error: %s", err)
|
|
|
|
}
|
|
|
|
}
|
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.
|
|
|
|
func (e *Encryptor) encrypt(buf []byte, wr io.Writer, i int) error {
|
|
|
|
var b [8]byte
|
|
|
|
var noncebuf [32]byte
|
|
|
|
|
2019-10-21 13:28:27 -07:00
|
|
|
binary.BigEndian.PutUint32(b[:4], uint32(e.ae.Overhead()+len(buf)))
|
2019-10-17 14:29:01 -07:00
|
|
|
binary.BigEndian.PutUint32(b[4:], uint32(i))
|
|
|
|
|
|
|
|
h := sha256.New()
|
|
|
|
h.Write(e.Salt)
|
|
|
|
h.Write(b[:])
|
|
|
|
nonce := h.Sum(noncebuf[:0])
|
|
|
|
|
|
|
|
copy(e.buf[:4], b[:4])
|
|
|
|
cbuf := e.buf[4:]
|
|
|
|
c := e.ae.Seal(cbuf[:0], nonce, buf, b[:])
|
|
|
|
|
|
|
|
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 {
|
|
|
|
Header
|
|
|
|
|
2019-10-21 13:28:27 -07:00
|
|
|
ae cipher.AEAD
|
|
|
|
rd io.Reader
|
2019-10-17 14:29:01 -07:00
|
|
|
buf []byte
|
|
|
|
|
|
|
|
// Decrypted key
|
|
|
|
key []byte
|
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-10-21 13:28:27 -07:00
|
|
|
var b [12]byte
|
2019-10-17 14:29:01 -07:00
|
|
|
|
2019-10-21 13:28:27 -07:00
|
|
|
_, err := io.ReadFull(rd, b[:])
|
2019-10-17 14:29:01 -07:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
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])
|
|
|
|
}
|
|
|
|
|
|
|
|
hdrlen := binary.BigEndian.Uint32(b[_MagicLen+1:])
|
|
|
|
if hdrlen > 65536 {
|
|
|
|
return nil, fmt.Errorf("decrypt: header too large (max 65536)")
|
|
|
|
}
|
|
|
|
if hdrlen < 32 {
|
|
|
|
return nil, fmt.Errorf("decrypt: header too small (min 32)")
|
|
|
|
}
|
|
|
|
|
|
|
|
hdr := make([]byte, hdrlen)
|
|
|
|
|
|
|
|
_, err = io.ReadFull(rd, hdr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
verify := hdr[:32]
|
|
|
|
hdr = hdr[32:]
|
|
|
|
|
|
|
|
cksum := sha256.Sum256(hdr)
|
|
|
|
if subtle.ConstantTimeCompare(verify, cksum[:]) == 0 {
|
|
|
|
return nil, fmt.Errorf("decrypt: header corrupted")
|
|
|
|
}
|
|
|
|
|
|
|
|
d := &Decryptor{
|
2019-10-21 13:28:27 -07:00
|
|
|
rd: rd,
|
2019-10-09 14:52:34 -07:00
|
|
|
}
|
|
|
|
|
2019-10-17 14:29:01 -07:00
|
|
|
err = d.Header.Unmarshal(hdr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("decrypt: decode error: %s", err)
|
2019-10-09 14:52:34 -07:00
|
|
|
}
|
|
|
|
|
2019-10-21 13:28:27 -07:00
|
|
|
if d.ChunkSize == 0 || d.ChunkSize > (16*1048576) {
|
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
|
|
|
}
|
|
|
|
|
2019-10-17 14:29:01 -07:00
|
|
|
if len(d.Salt) != 32 {
|
|
|
|
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 {
|
|
|
|
if len(w.PkHash) != PKHashLength {
|
|
|
|
return nil, fmt.Errorf("decrypt: wrapped key %d: invalid PkHash", i)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(w.Pk) != 32 {
|
|
|
|
return nil, fmt.Errorf("decrypt: wrapped key %d: invalid Curve25519 PK", i)
|
|
|
|
}
|
2019-10-09 14:52:34 -07:00
|
|
|
|
2019-10-17 14:29:01 -07:00
|
|
|
// XXX Default AES-256-GCM Nonce size is 12
|
|
|
|
if len(w.Nonce) != 12 {
|
|
|
|
return nil, fmt.Errorf("decrypt: wrapped key %d: invalid Nonce", i)
|
2019-10-09 14:52:34 -07:00
|
|
|
}
|
|
|
|
|
2019-10-17 14:29:01 -07:00
|
|
|
if len(w.Key) == 0 {
|
|
|
|
return nil, fmt.Errorf("decrypt: wrapped key %d: missing encrypted key", i)
|
|
|
|
}
|
2019-10-09 14:52:34 -07:00
|
|
|
|
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
|
|
|
|
|
|
|
|
pkh := sk.PublicKey().Hash()
|
|
|
|
for i, w := range d.Keys {
|
|
|
|
if subtle.ConstantTimeCompare(pkh, w.PkHash) == 1 {
|
2019-10-18 15:42:08 -07:00
|
|
|
d.key, err = w.UnwrapKey(sk, senderPk)
|
2019-10-17 14:29:01 -07:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("decrypt: can't unwrap key %d: %s", i, err)
|
|
|
|
}
|
|
|
|
goto havekey
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Errorf("decrypt: Can't find any public key to match the given private key")
|
2019-10-09 14:52:34 -07:00
|
|
|
|
2019-10-17 14:29:01 -07:00
|
|
|
havekey:
|
|
|
|
aes, err := aes.NewCipher(d.key)
|
|
|
|
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-21 13:28:27 -07:00
|
|
|
d.buf = make([]byte, int(d.ChunkSize)+d.ae.Overhead())
|
2019-10-17 14:29:01 -07:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return a list of Wrapped keys in the encrypted file header
|
|
|
|
func (d *Decryptor) WrappedKeys() []*WrappedKey {
|
|
|
|
return d.Keys
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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()?")
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := 0; ; i++ {
|
|
|
|
c, err := d.decrypt(i)
|
2019-10-09 14:52:34 -07:00
|
|
|
if err != nil {
|
2019-10-17 14:29:01 -07:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
if len(c) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
2019-10-21 13:28:27 -07:00
|
|
|
|
2019-10-17 14:29:01 -07:00
|
|
|
if len(c) > 0 {
|
|
|
|
err = fullwrite(c, wr)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("decrypt: %s", err)
|
|
|
|
}
|
2019-10-09 14:52:34 -07:00
|
|
|
}
|
|
|
|
}
|
2019-10-17 14:29:01 -07:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Decrypt exactly one chunk of data
|
|
|
|
func (d *Decryptor) decrypt(i int) ([]byte, error) {
|
|
|
|
var b [8]byte
|
|
|
|
var nonceb [32]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])
|
|
|
|
if n == 0 || err == io.EOF {
|
|
|
|
return nil, nil
|
2019-10-09 14:52:34 -07:00
|
|
|
}
|
|
|
|
|
2019-10-17 14:29:01 -07:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("decrypt: can't read chunk %d length: %s", i, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
chunklen := int(binary.BigEndian.Uint32(b[:4]))
|
|
|
|
binary.BigEndian.PutUint32(b[4:], uint32(i))
|
|
|
|
h := sha256.New()
|
|
|
|
h.Write(d.Salt)
|
|
|
|
h.Write(b[:])
|
|
|
|
nonce := h.Sum(nonceb[:0])
|
|
|
|
|
|
|
|
n, err = io.ReadFull(d.rd, d.buf[:chunklen])
|
|
|
|
if n == 0 || err == io.EOF {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("decrypt: can't read chunk %d: %s", i, err)
|
2019-10-09 14:52:34 -07:00
|
|
|
}
|
|
|
|
|
2019-10-17 14:29:01 -07:00
|
|
|
p, err := d.ae.Open(d.buf[:0], nonce, d.buf[:chunklen], b[:])
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("decrypt: can't decrypt chunk %d: %s", i, err)
|
2019-10-09 14:52:34 -07:00
|
|
|
}
|
|
|
|
|
2019-10-17 14:29:01 -07:00
|
|
|
return p, nil
|
2019-10-09 14:52:34 -07:00
|
|
|
}
|
|
|
|
|
2019-10-18 15:42:08 -07:00
|
|
|
// Wrap a shared key with the recipient's public key 'pk' by generating an ephemeral
|
|
|
|
// Curve25519 keypair. This function does not identify the sender (non-repudiation).
|
|
|
|
func (pk *PublicKey) WrapKeyEphemeral(key []byte) (*WrappedKey, error) {
|
|
|
|
var newSK [32]byte
|
|
|
|
|
|
|
|
randread(newSK[:])
|
|
|
|
clamp(newSK[:])
|
|
|
|
|
|
|
|
return wrapKey(pk, key, &newSK)
|
|
|
|
}
|
|
|
|
|
2019-10-09 14:52:34 -07:00
|
|
|
// given a file-encryption-key, wrap it in the identity of the recipient 'pk' using our
|
|
|
|
// secret key. This function identifies the sender.
|
|
|
|
func (sk *PrivateKey) WrapKey(pk *PublicKey, key []byte) (*WrappedKey, error) {
|
2019-10-18 15:42:08 -07:00
|
|
|
var ourSK [32]byte
|
2019-10-09 14:52:34 -07:00
|
|
|
|
|
|
|
copy(ourSK[:], sk.toCurve25519SK())
|
|
|
|
|
2019-10-18 15:42:08 -07:00
|
|
|
return wrapKey(pk, key, &ourSK)
|
|
|
|
}
|
|
|
|
|
|
|
|
func wrapKey(pk *PublicKey, k []byte, ourSK *[32]byte) (*WrappedKey, error) {
|
|
|
|
var curvePK, theirPK, shared [32]byte
|
|
|
|
|
|
|
|
copy(theirPK[:], pk.toCurve25519PK())
|
|
|
|
curve25519.ScalarBaseMult(&curvePK, ourSK)
|
|
|
|
curve25519.ScalarMult(&shared, ourSK, &theirPK)
|
|
|
|
|
|
|
|
ek, nonce, err := aeadSeal(k, shared[:], pk.Pk)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("wrap: %s", err)
|
|
|
|
}
|
2019-10-09 14:52:34 -07:00
|
|
|
|
2019-10-18 15:42:08 -07:00
|
|
|
return &WrappedKey{
|
|
|
|
PkHash: pk.hash,
|
|
|
|
Pk: curvePK[:],
|
|
|
|
Nonce: nonce,
|
|
|
|
Key: ek,
|
|
|
|
}, nil
|
2019-10-09 14:52:34 -07:00
|
|
|
}
|
|
|
|
|
2019-10-17 14:29:01 -07:00
|
|
|
// Unwrap a wrapped key using the private key 'sk'
|
2019-10-18 15:42:08 -07:00
|
|
|
func (w *WrappedKey) UnwrapKey(sk *PrivateKey, senderPk *PublicKey) ([]byte, error) {
|
2019-10-17 14:29:01 -07:00
|
|
|
var shared, theirPK, ourSK [32]byte
|
|
|
|
|
|
|
|
pk := sk.PublicKey()
|
2019-10-18 15:42:08 -07:00
|
|
|
|
2019-10-17 14:29:01 -07:00
|
|
|
copy(ourSK[:], sk.toCurve25519SK())
|
|
|
|
copy(theirPK[:], w.Pk)
|
|
|
|
curve25519.ScalarMult(&shared, &ourSK, &theirPK)
|
|
|
|
|
2019-10-18 15:42:08 -07:00
|
|
|
if senderPk != nil {
|
|
|
|
var cPK, shared2 [32]byte
|
2019-10-09 14:52:34 -07:00
|
|
|
|
2019-10-18 15:42:08 -07:00
|
|
|
curvePK := senderPk.toCurve25519PK()
|
2019-10-09 14:52:34 -07:00
|
|
|
|
2019-10-18 15:42:08 -07:00
|
|
|
copy(cPK[:], curvePK)
|
|
|
|
curve25519.ScalarMult(&shared2, &ourSK, &cPK)
|
2019-10-09 14:52:34 -07:00
|
|
|
|
2019-10-18 15:42:08 -07:00
|
|
|
if subtle.ConstantTimeCompare(shared2[:], shared[:]) != 1 {
|
|
|
|
return nil, fmt.Errorf("unwrap: sender validation failed")
|
|
|
|
}
|
|
|
|
}
|
2019-10-09 14:52:34 -07:00
|
|
|
|
2019-10-18 15:42:08 -07:00
|
|
|
key, err := aeadOpen(w.Key, w.Nonce, shared[:], pk.Pk)
|
2019-10-09 14:52:34 -07:00
|
|
|
if err != nil {
|
2019-10-18 15:42:08 -07:00
|
|
|
return nil, err
|
2019-10-09 14:52:34 -07:00
|
|
|
}
|
2019-10-18 15:42:08 -07:00
|
|
|
return key, nil
|
2019-10-09 14:52:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Convert an Ed25519 Private Key to Curve25519 Private key
|
|
|
|
func (sk *PrivateKey) toCurve25519SK() []byte {
|
|
|
|
if sk.ck == nil {
|
|
|
|
var ek [64]byte
|
|
|
|
|
|
|
|
h := sha512.New()
|
|
|
|
h.Write(sk.Sk[:32])
|
|
|
|
h.Sum(ek[:0])
|
|
|
|
|
|
|
|
sk.ck = clamp(ek[:32])
|
|
|
|
}
|
|
|
|
|
|
|
|
return sk.ck
|
|
|
|
}
|
|
|
|
|
|
|
|
// from github.com/FiloSottile/age
|
|
|
|
var curve25519P, _ = new(big.Int).SetString("57896044618658097711785492504343953926634992332820282019728792003956564819949", 10)
|
|
|
|
|
|
|
|
// Convert an Ed25519 Public Key to Curve25519 public key
|
|
|
|
// from github.com/FiloSottile/age
|
|
|
|
func (pk *PublicKey) toCurve25519PK() []byte {
|
|
|
|
if pk.ck != nil {
|
|
|
|
return pk.ck
|
|
|
|
}
|
|
|
|
|
|
|
|
// ed25519.PublicKey is a little endian representation of the y-coordinate,
|
|
|
|
// with the most significant bit set based on the sign of the x-ccordinate.
|
|
|
|
bigEndianY := make([]byte, ed25519.PublicKeySize)
|
|
|
|
for i, b := range pk.Pk {
|
|
|
|
bigEndianY[ed25519.PublicKeySize-i-1] = b
|
|
|
|
}
|
|
|
|
bigEndianY[0] &= 0b0111_1111
|
|
|
|
|
|
|
|
// The Montgomery u-coordinate is derived through the bilinear map
|
|
|
|
//
|
|
|
|
// u = (1 + y) / (1 - y)
|
|
|
|
//
|
|
|
|
// See https://blog.filippo.io/using-ed25519-keys-for-encryption.
|
|
|
|
y := new(big.Int).SetBytes(bigEndianY)
|
|
|
|
denom := big.NewInt(1)
|
|
|
|
denom.ModInverse(denom.Sub(denom, y), curve25519P) // 1 / (1 - y)
|
|
|
|
u := y.Mul(y.Add(y, big.NewInt(1)), denom)
|
|
|
|
u.Mod(u, curve25519P)
|
|
|
|
|
|
|
|
out := make([]byte, 32)
|
|
|
|
uBytes := u.Bytes()
|
|
|
|
n := len(uBytes)
|
|
|
|
for i, b := range uBytes {
|
|
|
|
out[n-i-1] = b
|
|
|
|
}
|
|
|
|
|
|
|
|
pk.ck = out
|
|
|
|
return out
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2019-10-21 13:28:27 -07:00
|
|
|
// seal the data via AEAD after suitably expanding 'shared'
|
2019-10-17 14:29:01 -07:00
|
|
|
func aeadSeal(data, shared, pk []byte) ([]byte, []byte, error) {
|
|
|
|
kek, err := expand(shared[:], pk)
|
2019-10-09 14:52:34 -07:00
|
|
|
if err != nil {
|
2019-10-17 14:29:01 -07:00
|
|
|
return nil, nil, fmt.Errorf("wrap: %s", err)
|
2019-10-09 14:52:34 -07:00
|
|
|
}
|
|
|
|
|
2019-10-17 14:29:01 -07:00
|
|
|
aes, err := aes.NewCipher(kek)
|
2019-10-09 14:52:34 -07:00
|
|
|
if err != nil {
|
2019-10-17 14:29:01 -07:00
|
|
|
return nil, nil, fmt.Errorf("wrap: %s", err)
|
2019-10-09 14:52:34 -07:00
|
|
|
}
|
|
|
|
|
2019-10-17 14:29:01 -07:00
|
|
|
ae, err := cipher.NewGCM(aes)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, fmt.Errorf("wrap: %s", err)
|
|
|
|
}
|
2019-10-09 14:52:34 -07:00
|
|
|
|
2019-10-17 14:29:01 -07:00
|
|
|
noncesize := ae.NonceSize()
|
|
|
|
tagsize := ae.Overhead()
|
2019-10-09 14:52:34 -07:00
|
|
|
|
2019-10-21 13:28:27 -07:00
|
|
|
buf := make([]byte, tagsize+len(kek))
|
2019-10-17 14:29:01 -07:00
|
|
|
nonce := make([]byte, noncesize)
|
2019-10-09 14:52:34 -07:00
|
|
|
|
2019-10-17 14:29:01 -07:00
|
|
|
randread(nonce)
|
2019-10-09 14:52:34 -07:00
|
|
|
|
2019-10-17 14:29:01 -07:00
|
|
|
out := ae.Seal(buf[:0], nonce, data, nil)
|
|
|
|
return out, nonce, nil
|
|
|
|
}
|
2019-10-09 14:52:34 -07:00
|
|
|
|
2019-10-17 14:29:01 -07:00
|
|
|
func aeadOpen(data, nonce, shared, pk []byte) ([]byte, error) {
|
|
|
|
// hkdf or HMAC-sha-256
|
|
|
|
kek, err := expand(shared, pk)
|
2019-10-09 14:52:34 -07:00
|
|
|
if err != nil {
|
2019-10-17 14:29:01 -07:00
|
|
|
return nil, fmt.Errorf("unwrap: %s", err)
|
|
|
|
}
|
|
|
|
aes, err := aes.NewCipher(kek)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("unwrap: %s", err)
|
2019-10-09 14:52:34 -07:00
|
|
|
}
|
|
|
|
|
2019-10-17 14:29:01 -07:00
|
|
|
ae, err := cipher.NewGCM(aes)
|
2019-10-09 14:52:34 -07:00
|
|
|
if err != nil {
|
2019-10-17 14:29:01 -07:00
|
|
|
return nil, fmt.Errorf("unwrap: %s", err)
|
2019-10-09 14:52:34 -07:00
|
|
|
}
|
|
|
|
|
2019-10-17 14:29:01 -07:00
|
|
|
want := 32 + ae.Overhead()
|
|
|
|
if len(data) != want {
|
|
|
|
return nil, fmt.Errorf("unwrap: incorrect decrypt bytes (need %d, saw %d)", want, len(data))
|
|
|
|
}
|
|
|
|
|
|
|
|
c, err := ae.Open(data[:0], nonce, data, nil)
|
2019-10-09 14:52:34 -07:00
|
|
|
if err != nil {
|
2019-10-17 14:29:01 -07:00
|
|
|
return nil, fmt.Errorf("unwrap: %s", err)
|
2019-10-09 14:52:34 -07:00
|
|
|
}
|
2019-10-17 14:29:01 -07:00
|
|
|
|
2019-10-09 14:52:34 -07:00
|
|
|
return c, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func clamp(k []byte) []byte {
|
|
|
|
k[0] &= 248
|
|
|
|
k[31] &= 127
|
|
|
|
k[31] |= 64
|
|
|
|
return k
|
|
|
|
}
|