Add streaming APIs to encrypt with compression

This commit is contained in:
M. Thiercelin 2023-01-24 17:27:38 +01:00
parent ffcaa7f87b
commit eccc1df619
No known key found for this signature in database
GPG key ID: 29581E7E24EBEC0A
8 changed files with 209 additions and 29 deletions

View file

@ -48,4 +48,4 @@ jobs:
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
with:
version: v1.46.2
version: v1.50.1

View file

@ -46,4 +46,5 @@ linters:
- ireturn # Prevents returning interfaces
- forcetypeassert # Forces to assert types in tests
- nonamedreturns # Disallows named returns
- exhaustruct # Forces all structs to be named
- exhaustruct # Forces all structs to be named
- nosnakecase # Disallows snake case

View file

@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### Added
- Streaming API to encrypt with compression:
- `func (keyRing *KeyRing) EncryptStreamWithCompression`
- `func (keyRing *KeyRing) EncryptSplitStreamWithCompression`
- `func (sk *SessionKey) EncryptStreamWithCompression`
## [2.5.0] 2022-12-16
### Changed
- Update `github.com/ProtonMail/go-crypto` to the latest version

View file

@ -8,6 +8,7 @@ import (
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/ProtonMail/go-crypto/openpgp/packet"
"github.com/ProtonMail/gopenpgp/v2/constants"
"github.com/pkg/errors"
)
@ -44,6 +45,47 @@ func (keyRing *KeyRing) EncryptStream(
) (plainMessageWriter WriteCloser, err error) {
config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: getTimeGenerator()}
return keyRing.encryptStreamWithConfig(
config,
pgpMessageWriter,
pgpMessageWriter,
plainMessageMetadata,
signKeyRing,
)
}
// EncryptStreamWithCompression is used to encrypt data as a Writer.
// The plaintext data is compressed before being encrypted.
// It takes a writer for the encrypted data and returns a WriteCloser for the plaintext data
// If signKeyRing is not nil, it is used to do an embedded signature.
func (keyRing *KeyRing) EncryptStreamWithCompression(
pgpMessageWriter Writer,
plainMessageMetadata *PlainMessageMetadata,
signKeyRing *KeyRing,
) (plainMessageWriter WriteCloser, err error) {
config := &packet.Config{
DefaultCipher: packet.CipherAES256,
Time: getTimeGenerator(),
DefaultCompressionAlgo: constants.DefaultCompression,
CompressionConfig: &packet.CompressionConfig{Level: constants.DefaultCompressionLevel},
}
return keyRing.encryptStreamWithConfig(
config,
pgpMessageWriter,
pgpMessageWriter,
plainMessageMetadata,
signKeyRing,
)
}
func (keyRing *KeyRing) encryptStreamWithConfig(
config *packet.Config,
keyPacketWriter Writer,
dataPacketWriter Writer,
plainMessageMetadata *PlainMessageMetadata,
signKeyRing *KeyRing,
) (plainMessageWriter WriteCloser, err error) {
if plainMessageMetadata == nil {
// Use sensible default metadata
plainMessageMetadata = &PlainMessageMetadata{
@ -59,7 +101,7 @@ func (keyRing *KeyRing) EncryptStream(
ModTime: time.Unix(plainMessageMetadata.ModTime, 0),
}
plainMessageWriter, err = asymmetricEncryptStream(hints, pgpMessageWriter, pgpMessageWriter, keyRing, signKeyRing, config)
plainMessageWriter, err = asymmetricEncryptStream(hints, keyPacketWriter, dataPacketWriter, keyRing, signKeyRing, config)
if err != nil {
return nil, err
}
@ -109,26 +151,55 @@ func (keyRing *KeyRing) EncryptSplitStream(
) (*EncryptSplitResult, error) {
config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: getTimeGenerator()}
if plainMessageMetadata == nil {
// Use sensible default metadata
plainMessageMetadata = &PlainMessageMetadata{
IsBinary: true,
Filename: "",
ModTime: GetUnixTime(),
}
}
hints := &openpgp.FileHints{
FileName: plainMessageMetadata.Filename,
IsBinary: plainMessageMetadata.IsBinary,
ModTime: time.Unix(plainMessageMetadata.ModTime, 0),
}
var keyPacketBuf bytes.Buffer
plainMessageWriter, err := asymmetricEncryptStream(hints, &keyPacketBuf, dataPacketWriter, keyRing, signKeyRing, config)
plainMessageWriter, err := keyRing.encryptStreamWithConfig(
config,
&keyPacketBuf,
dataPacketWriter,
plainMessageMetadata,
signKeyRing,
)
if err != nil {
return nil, err
}
return &EncryptSplitResult{
keyPacketBuf: &keyPacketBuf,
plainMessageWriter: plainMessageWriter,
}, nil
}
// EncryptSplitStreamWithCompression is used to encrypt data as a stream.
// It takes a writer for the Symmetrically Encrypted Data Packet
// (https://datatracker.ietf.org/doc/html/rfc4880#section-5.7)
// and returns a writer for the plaintext data and the key packet.
// If signKeyRing is not nil, it is used to do an embedded signature.
func (keyRing *KeyRing) EncryptSplitStreamWithCompression(
dataPacketWriter Writer,
plainMessageMetadata *PlainMessageMetadata,
signKeyRing *KeyRing,
) (*EncryptSplitResult, error) {
config := &packet.Config{
DefaultCipher: packet.CipherAES256,
Time: getTimeGenerator(),
DefaultCompressionAlgo: constants.DefaultCompression,
CompressionConfig: &packet.CompressionConfig{Level: constants.DefaultCompressionLevel},
}
var keyPacketBuf bytes.Buffer
plainMessageWriter, err := keyRing.encryptStreamWithConfig(
config,
&keyPacketBuf,
dataPacketWriter,
plainMessageMetadata,
signKeyRing,
)
if err != nil {
return nil, err
}
return &EncryptSplitResult{
keyPacketBuf: &keyPacketBuf,
plainMessageWriter: plainMessageWriter,

View file

@ -107,10 +107,34 @@ func TestKeyRing_EncryptDecryptStream(t *testing.T) {
}
func TestKeyRing_EncryptStreamCompatible(t *testing.T) {
enc := func(w io.Writer, meta *PlainMessageMetadata, kr *KeyRing) (io.WriteCloser, error) {
return keyRingTestPublic.EncryptStream(
w,
meta,
kr,
)
}
testKeyRing_EncryptStreamCompatible(enc, t)
}
func TestKeyRing_EncryptStreamWithCompressionCompatible(t *testing.T) {
enc := func(w io.Writer, meta *PlainMessageMetadata, kr *KeyRing) (io.WriteCloser, error) {
return keyRingTestPublic.EncryptStreamWithCompression(
w,
meta,
kr,
)
}
testKeyRing_EncryptStreamCompatible(enc, t)
}
type keyringEncryptionFunction = func(io.Writer, *PlainMessageMetadata, *KeyRing) (io.WriteCloser, error)
func testKeyRing_EncryptStreamCompatible(encrypt keyringEncryptionFunction, t *testing.T) {
messageBytes := []byte("Hello World!")
messageReader := bytes.NewReader(messageBytes)
var ciphertextBuf bytes.Buffer
messageWriter, err := keyRingTestPublic.EncryptStream(
messageWriter, err := encrypt(
&ciphertextBuf,
testMeta,
keyRingTestPrivate,
@ -276,10 +300,34 @@ func TestKeyRing_EncryptDecryptSplitStream(t *testing.T) {
}
func TestKeyRing_EncryptSplitStreamCompatible(t *testing.T) {
enc := func(w io.Writer, meta *PlainMessageMetadata, kr *KeyRing) (*EncryptSplitResult, error) {
return keyRingTestPublic.EncryptSplitStream(
w,
meta,
kr,
)
}
testKeyRing_EncryptSplitStreamCompatible(enc, t)
}
func TestKeyRing_EncryptSplitStreamWithCompressionCompatible(t *testing.T) {
enc := func(w io.Writer, meta *PlainMessageMetadata, kr *KeyRing) (*EncryptSplitResult, error) {
return keyRingTestPublic.EncryptSplitStreamWithCompression(
w,
meta,
kr,
)
}
testKeyRing_EncryptSplitStreamCompatible(enc, t)
}
type keyringEncryptionSplitFunction = func(io.Writer, *PlainMessageMetadata, *KeyRing) (*EncryptSplitResult, error)
func testKeyRing_EncryptSplitStreamCompatible(encrypt keyringEncryptionSplitFunction, t *testing.T) {
messageBytes := []byte("Hello World!")
messageReader := bytes.NewReader(messageBytes)
var dataPacketBuf bytes.Buffer
encryptionResult, err := keyRingTestPublic.EncryptSplitStream(
encryptionResult, err := encrypt(
&dataPacketBuf,
testMeta,
keyRingTestPrivate,

View file

@ -3,6 +3,7 @@ package crypto
import (
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/ProtonMail/go-crypto/openpgp/packet"
"github.com/ProtonMail/gopenpgp/v2/constants"
"github.com/pkg/errors"
)
@ -29,16 +30,51 @@ func (sk *SessionKey) EncryptStream(
dataPacketWriter Writer,
plainMessageMetadata *PlainMessageMetadata,
signKeyRing *KeyRing,
) (plainMessageWriter WriteCloser, err error) {
config := &packet.Config{
Time: getTimeGenerator(),
}
return sk.encryptStreamWithConfig(
config,
dataPacketWriter,
plainMessageMetadata,
signKeyRing,
)
}
// EncryptStreamWithCompression is used to encrypt data as a Writer.
// The plaintext data is compressed before being encrypted.
// It takes a writer for the encrypted data packet and returns a writer for the plaintext data.
// If signKeyRing is not nil, it is used to do an embedded signature.
func (sk *SessionKey) EncryptStreamWithCompression(
dataPacketWriter Writer,
plainMessageMetadata *PlainMessageMetadata,
signKeyRing *KeyRing,
) (plainMessageWriter WriteCloser, err error) {
config := &packet.Config{
Time: getTimeGenerator(),
DefaultCompressionAlgo: constants.DefaultCompression,
CompressionConfig: &packet.CompressionConfig{Level: constants.DefaultCompressionLevel},
}
return sk.encryptStreamWithConfig(
config,
dataPacketWriter,
plainMessageMetadata,
signKeyRing,
)
}
func (sk *SessionKey) encryptStreamWithConfig(
config *packet.Config,
dataPacketWriter Writer,
plainMessageMetadata *PlainMessageMetadata,
signKeyRing *KeyRing,
) (plainMessageWriter WriteCloser, err error) {
dc, err := sk.GetCipherFunc()
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: unable to encrypt with session key")
}
config := &packet.Config{
Time: getTimeGenerator(),
DefaultCipher: dc,
}
config.DefaultCipher = dc
var signEntity *openpgp.Entity
if signKeyRing != nil {
signEntity, err = signKeyRing.getSigningEntity()

View file

@ -74,10 +74,26 @@ func TestSessionKey_EncryptDecryptStream(t *testing.T) {
}
func TestSessionKey_EncryptStreamCompatible(t *testing.T) {
enc := func(w io.Writer, meta *PlainMessageMetadata, kr *KeyRing) (io.WriteCloser, error) {
return testSessionKey.EncryptStream(w, meta, kr)
}
testSessionKey_EncryptStreamCompatible(enc, t)
}
func TestSessionKey_EncryptStreamWithCompressionCompatible(t *testing.T) {
enc := func(w io.Writer, meta *PlainMessageMetadata, kr *KeyRing) (io.WriteCloser, error) {
return testSessionKey.EncryptStreamWithCompression(w, meta, kr)
}
testSessionKey_EncryptStreamCompatible(enc, t)
}
type sessionKeyEncryptionFunction = func(io.Writer, *PlainMessageMetadata, *KeyRing) (io.WriteCloser, error)
func testSessionKey_EncryptStreamCompatible(enc sessionKeyEncryptionFunction, t *testing.T) {
messageBytes := []byte("Hello World!")
messageReader := bytes.NewReader(messageBytes)
var dataPacketBuf bytes.Buffer
messageWriter, err := testSessionKey.EncryptStream(
messageWriter, err := enc(
&dataPacketBuf,
testMeta,
keyRingTestPrivate,

View file

@ -228,8 +228,8 @@ func Test_KeyRing_GetVerifiedSignatureTimestampError(t *testing.T) {
if err != nil {
t.Errorf("Got an error while generating the signature: %v", err)
}
message_corrupted := NewPlainMessageFromString("Ciao world!")
_, err = keyRingTestPublic.GetVerifiedSignatureTimestamp(message_corrupted, signature, 0)
messageCorrupted := NewPlainMessageFromString("Ciao world!")
_, err = keyRingTestPublic.GetVerifiedSignatureTimestamp(messageCorrupted, signature, 0)
if err == nil {
t.Errorf("Expected an error while parsing the creation time of a wrong signature, got nil")
}