Add filename and time properties to message (#85)
* Add filename and time properties to message * Message time defaults to current time
This commit is contained in:
parent
7de8833ff6
commit
a4d89bce32
9 changed files with 134 additions and 51 deletions
17
CHANGELOG.md
17
CHANGELOG.md
|
|
@ -73,6 +73,21 @@ EncryptSignArmoredDetachedMobile(
|
|||
) (wrappedTuple *EncryptSignArmoredDetachedMobileResult, err error)
|
||||
```
|
||||
|
||||
- `NewPlainMessageFromFile` Function to create new `PlainMessage`s with a filename:
|
||||
```go
|
||||
NewPlainMessageFromFile(data []byte, filename string, modTime int) *PlainMessage
|
||||
```
|
||||
|
||||
- `GetFilename` to get the filename from a message:
|
||||
```go
|
||||
(msg *PlainMessage) GetFilename() string
|
||||
```
|
||||
|
||||
- `GetModTime` to get the modification time of a file
|
||||
```go
|
||||
(msg *PlainMessage) GetModTime() uint32
|
||||
```
|
||||
|
||||
### Changed
|
||||
- Improved key and message armoring testing
|
||||
- `EncryptSessionKey` now creates encrypted key packets for each valid encryption key in the provided keyring.
|
||||
|
|
@ -80,6 +95,8 @@ EncryptSignArmoredDetachedMobile(
|
|||
- Use aes256 cipher for password-encrypted messages.
|
||||
- The helpers `EncryptSignMessageArmored`, `DecryptVerifyMessageArmored`, `DecryptVerifyAttachment`, and`DecryptBinaryMessageArmored`
|
||||
now accept private keys as public keys and perform automatic casting if the keys are locked.
|
||||
- The `PlainMessage` struct now contains the fields `filename` (string) and `time` (uint32)
|
||||
- All the Decrypt* functions return the filename, type, and time specified in the encrypted message
|
||||
|
||||
### Fixed
|
||||
- Public key armoring headers
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"io/ioutil"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/openpgp"
|
||||
"golang.org/x/crypto/openpgp/packet"
|
||||
|
|
@ -52,7 +53,7 @@ func (ap *AttachmentProcessor) Finish() (*PGPSplitMessage, error) {
|
|||
// newAttachmentProcessor creates an AttachmentProcessor which can be used to encrypt
|
||||
// a file. It takes an estimatedSize and fileName as hints about the file.
|
||||
func (keyRing *KeyRing) newAttachmentProcessor(
|
||||
estimatedSize int, fileName string, garbageCollector int,
|
||||
estimatedSize int, filename string, isBinary bool, modTime uint32, garbageCollector int,
|
||||
) (*AttachmentProcessor, error) {
|
||||
attachmentProc := &AttachmentProcessor{}
|
||||
// You could also add these one at a time if needed.
|
||||
|
|
@ -60,7 +61,9 @@ func (keyRing *KeyRing) newAttachmentProcessor(
|
|||
attachmentProc.garbageCollector = garbageCollector
|
||||
|
||||
hints := &openpgp.FileHints{
|
||||
FileName: fileName,
|
||||
FileName: filename,
|
||||
IsBinary: isBinary,
|
||||
ModTime: time.Unix(int64(modTime), 0),
|
||||
}
|
||||
|
||||
config := &packet.Config{
|
||||
|
|
@ -93,11 +96,22 @@ func (keyRing *KeyRing) newAttachmentProcessor(
|
|||
return attachmentProc, nil
|
||||
}
|
||||
|
||||
// EncryptAttachment encrypts a file given a PlainMessage and a fileName.
|
||||
// EncryptAttachment encrypts a file given a PlainMessage and a filename.
|
||||
// If given a filename it will override the information in the PlainMessage object.
|
||||
// Returns a PGPSplitMessage containing a session key packet and symmetrically encrypted data.
|
||||
// Specifically designed for attachments rather than text messages.
|
||||
func (keyRing *KeyRing) EncryptAttachment(message *PlainMessage, fileName string) (*PGPSplitMessage, error) {
|
||||
ap, err := keyRing.newAttachmentProcessor(len(message.GetBinary()), fileName, -1)
|
||||
func (keyRing *KeyRing) EncryptAttachment(message *PlainMessage, filename string) (*PGPSplitMessage, error) {
|
||||
if filename == "" {
|
||||
filename = message.filename
|
||||
}
|
||||
|
||||
ap, err := keyRing.newAttachmentProcessor(
|
||||
len(message.GetBinary()),
|
||||
filename,
|
||||
message.IsBinary(),
|
||||
message.GetTime(),
|
||||
-1,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -114,9 +128,9 @@ func (keyRing *KeyRing) EncryptAttachment(message *PlainMessage, fileName string
|
|||
// file. It is optimized for low-memory environments and collects garbage every
|
||||
// megabyte.
|
||||
func (keyRing *KeyRing) NewLowMemoryAttachmentProcessor(
|
||||
estimatedSize int, fileName string,
|
||||
estimatedSize int, filename string,
|
||||
) (*AttachmentProcessor, error) {
|
||||
return keyRing.newAttachmentProcessor(estimatedSize, fileName, 1<<20)
|
||||
return keyRing.newAttachmentProcessor(estimatedSize, filename, true, uint32(GetUnixTime()), 1<<20)
|
||||
}
|
||||
|
||||
// DecryptAttachment takes a PGPSplitMessage, containing a session key packet and symmetrically encrypted data
|
||||
|
|
@ -143,5 +157,10 @@ func (keyRing *KeyRing) DecryptAttachment(message *PGPSplitMessage) (*PlainMessa
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return NewPlainMessage(b), nil
|
||||
return &PlainMessage{
|
||||
Data: b,
|
||||
TextType: !md.LiteralData.IsBinary,
|
||||
filename: md.LiteralData.FileName,
|
||||
time: md.LiteralData.Time,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,9 +42,9 @@ func TestAttachmentSetKey(t *testing.T) {
|
|||
|
||||
func TestAttachmentEncryptDecrypt(t *testing.T) {
|
||||
var testAttachmentCleartext = "cc,\ndille."
|
||||
var message = NewPlainMessage([]byte(testAttachmentCleartext))
|
||||
var message = NewPlainMessageFromFile([]byte(testAttachmentCleartext), "test.txt", 1602518992)
|
||||
|
||||
encSplit, err := keyRingTestPrivate.EncryptAttachment(message, "s.txt")
|
||||
encSplit, err := keyRingTestPrivate.EncryptAttachment(message, "")
|
||||
if err != nil {
|
||||
t.Fatal("Expected no error while encrypting attachment, got:", err)
|
||||
}
|
||||
|
|
@ -59,9 +59,9 @@ func TestAttachmentEncryptDecrypt(t *testing.T) {
|
|||
|
||||
func TestAttachmentEncrypt(t *testing.T) {
|
||||
var testAttachmentCleartext = "cc,\ndille."
|
||||
var message = NewPlainMessage([]byte(testAttachmentCleartext))
|
||||
var message = NewPlainMessageFromFile([]byte(testAttachmentCleartext), "test.txt", 1602518992)
|
||||
|
||||
encSplit, err := keyRingTestPrivate.EncryptAttachment(message, "s.txt")
|
||||
encSplit, err := keyRingTestPrivate.EncryptAttachment(message, "")
|
||||
if err != nil {
|
||||
t.Fatal("Expected no error while encrypting attachment, got:", err)
|
||||
}
|
||||
|
|
@ -78,7 +78,7 @@ func TestAttachmentEncrypt(t *testing.T) {
|
|||
|
||||
func TestAttachmentDecrypt(t *testing.T) {
|
||||
var testAttachmentCleartext = "cc,\ndille."
|
||||
var message = NewPlainMessage([]byte(testAttachmentCleartext))
|
||||
var message = NewPlainMessageFromFile([]byte(testAttachmentCleartext), "test.txt", 1602518992)
|
||||
|
||||
encrypted, err := keyRingTestPrivate.Encrypt(message, nil)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"crypto"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/openpgp"
|
||||
"golang.org/x/crypto/openpgp/packet"
|
||||
|
|
@ -15,7 +16,7 @@ import (
|
|||
// * message : The plaintext input as a PlainMessage.
|
||||
// * privateKey : (optional) an unlocked private keyring to include signature in the message.
|
||||
func (keyRing *KeyRing) Encrypt(message *PlainMessage, privateKey *KeyRing) (*PGPMessage, error) {
|
||||
encrypted, err := asymmetricEncrypt(message.GetBinary(), keyRing, privateKey, message.IsBinary())
|
||||
encrypted, err := asymmetricEncrypt(message, keyRing, privateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -33,9 +34,7 @@ func (keyRing *KeyRing) Encrypt(message *PlainMessage, privateKey *KeyRing) (*PG
|
|||
func (keyRing *KeyRing) Decrypt(
|
||||
message *PGPMessage, verifyKey *KeyRing, verifyTime int64,
|
||||
) (*PlainMessage, error) {
|
||||
decrypted, err := asymmetricDecrypt(message.NewReader(), keyRing, verifyKey, verifyTime)
|
||||
|
||||
return NewPlainMessage(decrypted), err
|
||||
return asymmetricDecrypt(message.NewReader(), keyRing, verifyKey, verifyTime)
|
||||
}
|
||||
|
||||
// SignDetached generates and returns a PGPSignature for a given PlainMessage.
|
||||
|
|
@ -69,7 +68,7 @@ func (keyRing *KeyRing) VerifyDetached(message *PlainMessage, signature *PGPSign
|
|||
// ------ INTERNAL FUNCTIONS -------
|
||||
|
||||
// Core for encryption+signature functions.
|
||||
func asymmetricEncrypt(data []byte, publicKey *KeyRing, privateKey *KeyRing, isBinary bool) ([]byte, error) {
|
||||
func asymmetricEncrypt(plainMessage *PlainMessage, publicKey, privateKey *KeyRing) ([]byte, error) {
|
||||
var outBuf bytes.Buffer
|
||||
var encryptWriter io.WriteCloser
|
||||
var signEntity *openpgp.Entity
|
||||
|
|
@ -86,11 +85,12 @@ func asymmetricEncrypt(data []byte, publicKey *KeyRing, privateKey *KeyRing, isB
|
|||
config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: getTimeGenerator()}
|
||||
|
||||
hints := &openpgp.FileHints{
|
||||
IsBinary: isBinary,
|
||||
FileName: "",
|
||||
IsBinary: plainMessage.IsBinary(),
|
||||
FileName: plainMessage.GetFilename(),
|
||||
ModTime: time.Unix(int64(plainMessage.GetTime()), 0),
|
||||
}
|
||||
|
||||
if isBinary {
|
||||
if plainMessage.IsBinary() {
|
||||
encryptWriter, err = openpgp.Encrypt(&outBuf, publicKey.entities, signEntity, hints, config)
|
||||
} else {
|
||||
encryptWriter, err = openpgp.EncryptText(&outBuf, publicKey.entities, signEntity, hints, config)
|
||||
|
|
@ -99,7 +99,7 @@ func asymmetricEncrypt(data []byte, publicKey *KeyRing, privateKey *KeyRing, isB
|
|||
return nil, err
|
||||
}
|
||||
|
||||
_, err = encryptWriter.Write(data)
|
||||
_, err = encryptWriter.Write(plainMessage.GetBinary())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -115,7 +115,7 @@ func asymmetricEncrypt(data []byte, publicKey *KeyRing, privateKey *KeyRing, isB
|
|||
// Core for decryption+verification functions.
|
||||
func asymmetricDecrypt(
|
||||
encryptedIO io.Reader, privateKey *KeyRing, verifyKey *KeyRing, verifyTime int64,
|
||||
) (plaintext []byte, err error) {
|
||||
) (message *PlainMessage, err error) {
|
||||
privKeyEntries := privateKey.entities
|
||||
var additionalEntries openpgp.EntityList
|
||||
|
||||
|
|
@ -141,11 +141,13 @@ func asymmetricDecrypt(
|
|||
|
||||
if verifyKey != nil {
|
||||
processSignatureExpiration(messageDetails, verifyTime)
|
||||
err = verifyDetailsSignature(messageDetails, verifyKey)
|
||||
}
|
||||
|
||||
if verifyKey != nil {
|
||||
return body, verifyDetailsSignature(messageDetails, verifyKey)
|
||||
}
|
||||
|
||||
return body, nil
|
||||
return &PlainMessage{
|
||||
Data: body,
|
||||
TextType: !messageDetails.LiteralData.IsBinary,
|
||||
filename: messageDetails.LiteralData.FileName,
|
||||
time: messageDetails.LiteralData.Time,
|
||||
}, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,8 +24,12 @@ import (
|
|||
type PlainMessage struct {
|
||||
// The content of the message
|
||||
Data []byte
|
||||
// if the content is text or binary
|
||||
// If the content is text or binary
|
||||
TextType bool
|
||||
// The file's latest modification time
|
||||
time uint32
|
||||
// The encrypted message's filename
|
||||
filename string
|
||||
}
|
||||
|
||||
// PGPMessage stores a PGP-encrypted message.
|
||||
|
|
@ -62,6 +66,19 @@ func NewPlainMessage(data []byte) *PlainMessage {
|
|||
return &PlainMessage{
|
||||
Data: clone(data),
|
||||
TextType: false,
|
||||
time: uint32(GetUnixTime()),
|
||||
}
|
||||
}
|
||||
|
||||
// NewPlainMessageFromFile generates a new binary PlainMessage ready for encryption,
|
||||
// signature, or verification from the unencrypted binary data.
|
||||
// It assigns a filename and a modification time.
|
||||
func NewPlainMessageFromFile(data []byte, filename string, time uint32) *PlainMessage {
|
||||
return &PlainMessage{
|
||||
Data: clone(data),
|
||||
TextType: false,
|
||||
filename: filename,
|
||||
time: time,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -71,6 +88,7 @@ func NewPlainMessageFromString(text string) *PlainMessage {
|
|||
return &PlainMessage{
|
||||
Data: []byte(text),
|
||||
TextType: true,
|
||||
time: uint32(GetUnixTime()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -201,6 +219,16 @@ func (msg *PlainMessage) IsBinary() bool {
|
|||
return !msg.TextType
|
||||
}
|
||||
|
||||
// GetFilename returns the file name of the message as a string.
|
||||
func (msg *PlainMessage) GetFilename() string {
|
||||
return msg.filename
|
||||
}
|
||||
|
||||
// GetTime returns the modification time of a file (if provided in the ciphertext).
|
||||
func (msg *PlainMessage) GetTime() uint32 {
|
||||
return msg.time
|
||||
}
|
||||
|
||||
// GetBinary returns the unarmored binary content of the message as a []byte.
|
||||
func (msg *PGPMessage) GetBinary() []byte {
|
||||
return msg.Data
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package crypto
|
|||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/openpgp"
|
||||
"golang.org/x/crypto/openpgp/packet"
|
||||
|
|
@ -16,7 +17,7 @@ import (
|
|||
// * password: A password that will be derived into an encryption key.
|
||||
// * output : The encrypted data as PGPMessage.
|
||||
func EncryptMessageWithPassword(message *PlainMessage, password []byte) (*PGPMessage, error) {
|
||||
encrypted, err := passwordEncrypt(message.GetBinary(), password, message.IsBinary())
|
||||
encrypted, err := passwordEncrypt(message, password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -29,13 +30,7 @@ func EncryptMessageWithPassword(message *PlainMessage, password []byte) (*PGPMes
|
|||
// * password: A password that will be derived into an encryption key.
|
||||
// * output: The decrypted data as PlainMessage.
|
||||
func DecryptMessageWithPassword(message *PGPMessage, password []byte) (*PlainMessage, error) {
|
||||
decrypted, err := passwordDecrypt(message.NewReader(), password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
binMessage := NewPlainMessage(decrypted)
|
||||
return binMessage, nil
|
||||
return passwordDecrypt(message.NewReader(), password)
|
||||
}
|
||||
|
||||
// DecryptSessionKeyWithPassword decrypts the binary symmetrically encrypted
|
||||
|
|
@ -110,7 +105,7 @@ func EncryptSessionKeyWithPassword(sk *SessionKey, password []byte) ([]byte, err
|
|||
|
||||
// ----- INTERNAL FUNCTIONS ------
|
||||
|
||||
func passwordEncrypt(message []byte, password []byte, isBinary bool) ([]byte, error) {
|
||||
func passwordEncrypt(message *PlainMessage, password []byte) ([]byte, error) {
|
||||
var outBuf bytes.Buffer
|
||||
|
||||
config := &packet.Config{
|
||||
|
|
@ -118,13 +113,17 @@ func passwordEncrypt(message []byte, password []byte, isBinary bool) ([]byte, er
|
|||
Time: getTimeGenerator(),
|
||||
}
|
||||
|
||||
hints := &openpgp.FileHints{IsBinary: isBinary}
|
||||
hints := &openpgp.FileHints{
|
||||
IsBinary: message.IsBinary(),
|
||||
FileName: message.GetFilename(),
|
||||
ModTime: time.Unix(int64(message.GetTime()), 0),
|
||||
}
|
||||
|
||||
encryptWriter, err := openpgp.SymmetricallyEncrypt(&outBuf, password, hints, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = encryptWriter.Write(message)
|
||||
_, err = encryptWriter.Write(message.GetBinary())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -137,7 +136,7 @@ func passwordEncrypt(message []byte, password []byte, isBinary bool) ([]byte, er
|
|||
return outBuf.Bytes(), nil
|
||||
}
|
||||
|
||||
func passwordDecrypt(encryptedIO io.Reader, password []byte) ([]byte, error) {
|
||||
func passwordDecrypt(encryptedIO io.Reader, password []byte) (*PlainMessage, error) {
|
||||
firstTimeCalled := true
|
||||
var prompt = func(keys []openpgp.Key, symmetric bool) ([]byte, error) {
|
||||
if firstTimeCalled {
|
||||
|
|
@ -163,5 +162,10 @@ func passwordDecrypt(encryptedIO io.Reader, password []byte) ([]byte, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return messageBuf.Bytes(), nil
|
||||
return &PlainMessage{
|
||||
Data: messageBuf.Bytes(),
|
||||
TextType: !md.LiteralData.IsBinary,
|
||||
filename: md.LiteralData.FileName,
|
||||
time: md.LiteralData.Time,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -139,7 +139,13 @@ func (sk *SessionKey) Encrypt(message *PlainMessage) ([]byte, error) {
|
|||
}
|
||||
}
|
||||
|
||||
encryptWriter, err = packet.SerializeLiteral(encryptWriter, message.IsBinary(), "", 0)
|
||||
encryptWriter, err = packet.SerializeLiteral(
|
||||
encryptWriter,
|
||||
message.IsBinary(),
|
||||
message.GetFilename(),
|
||||
message.GetTime(),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gopenpgp: unable to serialize")
|
||||
}
|
||||
|
|
@ -210,7 +216,12 @@ func (sk *SessionKey) Decrypt(dataPacket []byte) (*PlainMessage, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return NewPlainMessage(messageBuf.Bytes()), nil
|
||||
return &PlainMessage{
|
||||
Data: messageBuf.Bytes(),
|
||||
TextType: !md.LiteralData.IsBinary,
|
||||
filename: md.LiteralData.FileName,
|
||||
time: md.LiteralData.Time,
|
||||
}, err
|
||||
}
|
||||
|
||||
func (sk *SessionKey) checkSize() error {
|
||||
|
|
|
|||
|
|
@ -60,9 +60,9 @@ func DecryptAttachment(keyPacket []byte, dataPacket []byte, keyRing *crypto.KeyR
|
|||
// Returns a PGPSplitMessage containing a session key packet and symmetrically
|
||||
// encrypted data. Specifically designed for attachments rather than text
|
||||
// messages.
|
||||
func EncryptAttachment(plainData []byte, fileName string, keyRing *crypto.KeyRing) (*crypto.PGPSplitMessage, error) {
|
||||
plainMessage := crypto.NewPlainMessage(plainData)
|
||||
decrypted, err := keyRing.EncryptAttachment(plainMessage, fileName)
|
||||
func EncryptAttachment(plainData []byte, filename string, keyRing *crypto.KeyRing) (*crypto.PGPSplitMessage, error) {
|
||||
plainMessage := crypto.NewPlainMessageFromFile(plainData, filename, uint32(crypto.GetUnixTime()))
|
||||
decrypted, err := keyRing.EncryptAttachment(plainMessage, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,20 +3,22 @@
|
|||
|
||||
package helper
|
||||
|
||||
import "github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||
import (
|
||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||
)
|
||||
|
||||
// EncryptSignAttachment encrypts an attachment using a detached signature, given a publicKey, a privateKey
|
||||
// and its passphrase, the filename, and the unencrypted file data.
|
||||
// Returns keypacket, dataPacket and unarmored (!) signature separate.
|
||||
func EncryptSignAttachment(
|
||||
publicKey, privateKey string, passphrase []byte, fileName string, plainData []byte,
|
||||
publicKey, privateKey string, passphrase []byte, filename string, plainData []byte,
|
||||
) (keyPacket, dataPacket, signature []byte, err error) {
|
||||
var publicKeyObj, privateKeyObj, unlockedKeyObj *crypto.Key
|
||||
var publicKeyRing, privateKeyRing *crypto.KeyRing
|
||||
var packets *crypto.PGPSplitMessage
|
||||
var signatureObj *crypto.PGPSignature
|
||||
|
||||
var binMessage = crypto.NewPlainMessage(plainData)
|
||||
var binMessage = crypto.NewPlainMessageFromFile(plainData, filename, uint32(crypto.GetUnixTime()))
|
||||
|
||||
if publicKeyObj, err = crypto.NewKeyFromArmored(publicKey); err != nil {
|
||||
return nil, nil, nil, err
|
||||
|
|
@ -45,7 +47,7 @@ func EncryptSignAttachment(
|
|||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
if packets, err = publicKeyRing.EncryptAttachment(binMessage, fileName); err != nil {
|
||||
if packets, err = publicKeyRing.EncryptAttachment(binMessage, ""); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue