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) ) (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 ### Changed
- Improved key and message armoring testing - Improved key and message armoring testing
- `EncryptSessionKey` now creates encrypted key packets for each valid encryption key in the provided keyring. - `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. - Use aes256 cipher for password-encrypted messages.
- The helpers `EncryptSignMessageArmored`, `DecryptVerifyMessageArmored`, `DecryptVerifyAttachment`, and`DecryptBinaryMessageArmored` - The helpers `EncryptSignMessageArmored`, `DecryptVerifyMessageArmored`, `DecryptVerifyAttachment`, and`DecryptBinaryMessageArmored`
now accept private keys as public keys and perform automatic casting if the keys are locked. 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 ### Fixed
- Public key armoring headers - Public key armoring headers

View file

@ -6,6 +6,7 @@ import (
"io/ioutil" "io/ioutil"
"runtime" "runtime"
"sync" "sync"
"time"
"golang.org/x/crypto/openpgp" "golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/packet" "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 // newAttachmentProcessor creates an AttachmentProcessor which can be used to encrypt
// a file. It takes an estimatedSize and fileName as hints about the file. // a file. It takes an estimatedSize and fileName as hints about the file.
func (keyRing *KeyRing) newAttachmentProcessor( func (keyRing *KeyRing) newAttachmentProcessor(
estimatedSize int, fileName string, garbageCollector int, estimatedSize int, filename string, isBinary bool, modTime uint32, garbageCollector int,
) (*AttachmentProcessor, error) { ) (*AttachmentProcessor, error) {
attachmentProc := &AttachmentProcessor{} attachmentProc := &AttachmentProcessor{}
// You could also add these one at a time if needed. // You could also add these one at a time if needed.
@ -60,7 +61,9 @@ func (keyRing *KeyRing) newAttachmentProcessor(
attachmentProc.garbageCollector = garbageCollector attachmentProc.garbageCollector = garbageCollector
hints := &openpgp.FileHints{ hints := &openpgp.FileHints{
FileName: fileName, FileName: filename,
IsBinary: isBinary,
ModTime: time.Unix(int64(modTime), 0),
} }
config := &packet.Config{ config := &packet.Config{
@ -93,11 +96,22 @@ func (keyRing *KeyRing) newAttachmentProcessor(
return attachmentProc, nil 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. // Returns a PGPSplitMessage containing a session key packet and symmetrically encrypted data.
// Specifically designed for attachments rather than text messages. // Specifically designed for attachments rather than text messages.
func (keyRing *KeyRing) EncryptAttachment(message *PlainMessage, fileName string) (*PGPSplitMessage, error) { func (keyRing *KeyRing) EncryptAttachment(message *PlainMessage, filename string) (*PGPSplitMessage, error) {
ap, err := keyRing.newAttachmentProcessor(len(message.GetBinary()), fileName, -1) if filename == "" {
filename = message.filename
}
ap, err := keyRing.newAttachmentProcessor(
len(message.GetBinary()),
filename,
message.IsBinary(),
message.GetTime(),
-1,
)
if err != nil { if err != nil {
return nil, err 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 // file. It is optimized for low-memory environments and collects garbage every
// megabyte. // megabyte.
func (keyRing *KeyRing) NewLowMemoryAttachmentProcessor( func (keyRing *KeyRing) NewLowMemoryAttachmentProcessor(
estimatedSize int, fileName string, estimatedSize int, filename string,
) (*AttachmentProcessor, error) { ) (*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 // 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 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) { func TestAttachmentEncryptDecrypt(t *testing.T) {
var testAttachmentCleartext = "cc,\ndille." 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 { if err != nil {
t.Fatal("Expected no error while encrypting attachment, got:", err) t.Fatal("Expected no error while encrypting attachment, got:", err)
} }
@ -59,9 +59,9 @@ func TestAttachmentEncryptDecrypt(t *testing.T) {
func TestAttachmentEncrypt(t *testing.T) { func TestAttachmentEncrypt(t *testing.T) {
var testAttachmentCleartext = "cc,\ndille." 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 { if err != nil {
t.Fatal("Expected no error while encrypting attachment, got:", err) t.Fatal("Expected no error while encrypting attachment, got:", err)
} }
@ -78,7 +78,7 @@ func TestAttachmentEncrypt(t *testing.T) {
func TestAttachmentDecrypt(t *testing.T) { func TestAttachmentDecrypt(t *testing.T) {
var testAttachmentCleartext = "cc,\ndille." var testAttachmentCleartext = "cc,\ndille."
var message = NewPlainMessage([]byte(testAttachmentCleartext)) var message = NewPlainMessageFromFile([]byte(testAttachmentCleartext), "test.txt", 1602518992)
encrypted, err := keyRingTestPrivate.Encrypt(message, nil) encrypted, err := keyRingTestPrivate.Encrypt(message, nil)
if err != nil { if err != nil {

View file

@ -5,6 +5,7 @@ import (
"crypto" "crypto"
"io" "io"
"io/ioutil" "io/ioutil"
"time"
"golang.org/x/crypto/openpgp" "golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/packet" "golang.org/x/crypto/openpgp/packet"
@ -15,7 +16,7 @@ import (
// * message : The plaintext input as a PlainMessage. // * message : The plaintext input as a PlainMessage.
// * privateKey : (optional) an unlocked private keyring to include signature in the message. // * privateKey : (optional) an unlocked private keyring to include signature in the message.
func (keyRing *KeyRing) Encrypt(message *PlainMessage, privateKey *KeyRing) (*PGPMessage, error) { 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 { if err != nil {
return nil, err return nil, err
} }
@ -33,9 +34,7 @@ func (keyRing *KeyRing) Encrypt(message *PlainMessage, privateKey *KeyRing) (*PG
func (keyRing *KeyRing) Decrypt( func (keyRing *KeyRing) Decrypt(
message *PGPMessage, verifyKey *KeyRing, verifyTime int64, message *PGPMessage, verifyKey *KeyRing, verifyTime int64,
) (*PlainMessage, error) { ) (*PlainMessage, error) {
decrypted, err := asymmetricDecrypt(message.NewReader(), keyRing, verifyKey, verifyTime) return asymmetricDecrypt(message.NewReader(), keyRing, verifyKey, verifyTime)
return NewPlainMessage(decrypted), err
} }
// SignDetached generates and returns a PGPSignature for a given PlainMessage. // SignDetached generates and returns a PGPSignature for a given PlainMessage.
@ -69,7 +68,7 @@ func (keyRing *KeyRing) VerifyDetached(message *PlainMessage, signature *PGPSign
// ------ INTERNAL FUNCTIONS ------- // ------ INTERNAL FUNCTIONS -------
// Core for encryption+signature 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 outBuf bytes.Buffer
var encryptWriter io.WriteCloser var encryptWriter io.WriteCloser
var signEntity *openpgp.Entity 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()} config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: getTimeGenerator()}
hints := &openpgp.FileHints{ hints := &openpgp.FileHints{
IsBinary: isBinary, IsBinary: plainMessage.IsBinary(),
FileName: "", 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) encryptWriter, err = openpgp.Encrypt(&outBuf, publicKey.entities, signEntity, hints, config)
} else { } else {
encryptWriter, err = openpgp.EncryptText(&outBuf, publicKey.entities, signEntity, hints, config) 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 return nil, err
} }
_, err = encryptWriter.Write(data) _, err = encryptWriter.Write(plainMessage.GetBinary())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -115,7 +115,7 @@ func asymmetricEncrypt(data []byte, publicKey *KeyRing, privateKey *KeyRing, isB
// Core for decryption+verification functions. // Core for decryption+verification functions.
func asymmetricDecrypt( func asymmetricDecrypt(
encryptedIO io.Reader, privateKey *KeyRing, verifyKey *KeyRing, verifyTime int64, encryptedIO io.Reader, privateKey *KeyRing, verifyKey *KeyRing, verifyTime int64,
) (plaintext []byte, err error) { ) (message *PlainMessage, err error) {
privKeyEntries := privateKey.entities privKeyEntries := privateKey.entities
var additionalEntries openpgp.EntityList var additionalEntries openpgp.EntityList
@ -141,11 +141,13 @@ func asymmetricDecrypt(
if verifyKey != nil { if verifyKey != nil {
processSignatureExpiration(messageDetails, verifyTime) processSignatureExpiration(messageDetails, verifyTime)
err = verifyDetailsSignature(messageDetails, verifyKey)
} }
if verifyKey != nil { return &PlainMessage{
return body, verifyDetailsSignature(messageDetails, verifyKey) Data: body,
} TextType: !messageDetails.LiteralData.IsBinary,
filename: messageDetails.LiteralData.FileName,
return body, nil time: messageDetails.LiteralData.Time,
}, err
} }

View file

@ -24,8 +24,12 @@ import (
type PlainMessage struct { type PlainMessage struct {
// The content of the message // The content of the message
Data []byte Data []byte
// if the content is text or binary // If the content is text or binary
TextType bool TextType bool
// The file's latest modification time
time uint32
// The encrypted message's filename
filename string
} }
// PGPMessage stores a PGP-encrypted message. // PGPMessage stores a PGP-encrypted message.
@ -62,6 +66,19 @@ func NewPlainMessage(data []byte) *PlainMessage {
return &PlainMessage{ return &PlainMessage{
Data: clone(data), Data: clone(data),
TextType: false, 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{ return &PlainMessage{
Data: []byte(text), Data: []byte(text),
TextType: true, TextType: true,
time: uint32(GetUnixTime()),
} }
} }
@ -201,6 +219,16 @@ func (msg *PlainMessage) IsBinary() bool {
return !msg.TextType 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. // GetBinary returns the unarmored binary content of the message as a []byte.
func (msg *PGPMessage) GetBinary() []byte { func (msg *PGPMessage) GetBinary() []byte {
return msg.Data return msg.Data

View file

@ -3,6 +3,7 @@ package crypto
import ( import (
"bytes" "bytes"
"io" "io"
"time"
"golang.org/x/crypto/openpgp" "golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/packet" "golang.org/x/crypto/openpgp/packet"
@ -16,7 +17,7 @@ import (
// * password: A password that will be derived into an encryption key. // * password: A password that will be derived into an encryption key.
// * output : The encrypted data as PGPMessage. // * output : The encrypted data as PGPMessage.
func EncryptMessageWithPassword(message *PlainMessage, password []byte) (*PGPMessage, error) { func EncryptMessageWithPassword(message *PlainMessage, password []byte) (*PGPMessage, error) {
encrypted, err := passwordEncrypt(message.GetBinary(), password, message.IsBinary()) encrypted, err := passwordEncrypt(message, password)
if err != nil { if err != nil {
return nil, err 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. // * password: A password that will be derived into an encryption key.
// * output: The decrypted data as PlainMessage. // * output: The decrypted data as PlainMessage.
func DecryptMessageWithPassword(message *PGPMessage, password []byte) (*PlainMessage, error) { func DecryptMessageWithPassword(message *PGPMessage, password []byte) (*PlainMessage, error) {
decrypted, err := passwordDecrypt(message.NewReader(), password) return passwordDecrypt(message.NewReader(), password)
if err != nil {
return nil, err
}
binMessage := NewPlainMessage(decrypted)
return binMessage, nil
} }
// DecryptSessionKeyWithPassword decrypts the binary symmetrically encrypted // DecryptSessionKeyWithPassword decrypts the binary symmetrically encrypted
@ -110,7 +105,7 @@ func EncryptSessionKeyWithPassword(sk *SessionKey, password []byte) ([]byte, err
// ----- INTERNAL FUNCTIONS ------ // ----- INTERNAL FUNCTIONS ------
func passwordEncrypt(message []byte, password []byte, isBinary bool) ([]byte, error) { func passwordEncrypt(message *PlainMessage, password []byte) ([]byte, error) {
var outBuf bytes.Buffer var outBuf bytes.Buffer
config := &packet.Config{ config := &packet.Config{
@ -118,13 +113,17 @@ func passwordEncrypt(message []byte, password []byte, isBinary bool) ([]byte, er
Time: getTimeGenerator(), 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) encryptWriter, err := openpgp.SymmetricallyEncrypt(&outBuf, password, hints, config)
if err != nil { if err != nil {
return nil, err return nil, err
} }
_, err = encryptWriter.Write(message) _, err = encryptWriter.Write(message.GetBinary())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -137,7 +136,7 @@ func passwordEncrypt(message []byte, password []byte, isBinary bool) ([]byte, er
return outBuf.Bytes(), nil return outBuf.Bytes(), nil
} }
func passwordDecrypt(encryptedIO io.Reader, password []byte) ([]byte, error) { func passwordDecrypt(encryptedIO io.Reader, password []byte) (*PlainMessage, error) {
firstTimeCalled := true firstTimeCalled := true
var prompt = func(keys []openpgp.Key, symmetric bool) ([]byte, error) { var prompt = func(keys []openpgp.Key, symmetric bool) ([]byte, error) {
if firstTimeCalled { if firstTimeCalled {
@ -163,5 +162,10 @@ func passwordDecrypt(encryptedIO io.Reader, password []byte) ([]byte, error) {
return nil, err 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 { if err != nil {
return nil, errors.Wrap(err, "gopenpgp: unable to serialize") 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 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 { 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 // Returns a PGPSplitMessage containing a session key packet and symmetrically
// encrypted data. Specifically designed for attachments rather than text // encrypted data. Specifically designed for attachments rather than text
// messages. // messages.
func EncryptAttachment(plainData []byte, fileName string, keyRing *crypto.KeyRing) (*crypto.PGPSplitMessage, error) { func EncryptAttachment(plainData []byte, filename string, keyRing *crypto.KeyRing) (*crypto.PGPSplitMessage, error) {
plainMessage := crypto.NewPlainMessage(plainData) plainMessage := crypto.NewPlainMessageFromFile(plainData, filename, uint32(crypto.GetUnixTime()))
decrypted, err := keyRing.EncryptAttachment(plainMessage, fileName) decrypted, err := keyRing.EncryptAttachment(plainMessage, "")
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -3,20 +3,22 @@
package helper 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 // EncryptSignAttachment encrypts an attachment using a detached signature, given a publicKey, a privateKey
// and its passphrase, the filename, and the unencrypted file data. // and its passphrase, the filename, and the unencrypted file data.
// Returns keypacket, dataPacket and unarmored (!) signature separate. // Returns keypacket, dataPacket and unarmored (!) signature separate.
func EncryptSignAttachment( 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) { ) (keyPacket, dataPacket, signature []byte, err error) {
var publicKeyObj, privateKeyObj, unlockedKeyObj *crypto.Key var publicKeyObj, privateKeyObj, unlockedKeyObj *crypto.Key
var publicKeyRing, privateKeyRing *crypto.KeyRing var publicKeyRing, privateKeyRing *crypto.KeyRing
var packets *crypto.PGPSplitMessage var packets *crypto.PGPSplitMessage
var signatureObj *crypto.PGPSignature 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 { if publicKeyObj, err = crypto.NewKeyFromArmored(publicKey); err != nil {
return nil, nil, nil, err return nil, nil, nil, err
@ -45,7 +47,7 @@ func EncryptSignAttachment(
return nil, nil, nil, err 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 return nil, nil, nil, err
} }