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:
wussler 2020-10-12 18:45:57 +02:00 committed by GitHub
parent 7de8833ff6
commit a4d89bce32
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 134 additions and 51 deletions

View file

@ -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

View file

@ -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
}

View file

@ -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 {

View file

@ -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
}

View file

@ -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

View file

@ -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
}

View file

@ -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 {

View file

@ -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
}

View file

@ -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
}