Skip to content

Commit c9bf4fb

Browse files
author
marinthiercelin
authored
Merge pull request #208 from ProtonMail/feat/encrypt_compression_streaming
Add streaming APIs to encrypt with compression
2 parents ffcaa7f + eccc1df commit c9bf4fb

File tree

8 files changed

+205
-25
lines changed

8 files changed

+205
-25
lines changed

.github/workflows/go.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,4 @@ jobs:
4848
- name: golangci-lint
4949
uses: golangci/golangci-lint-action@v3
5050
with:
51-
version: v1.46.2
51+
version: v1.50.1

.golangci.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,5 @@ linters:
4646
- ireturn # Prevents returning interfaces
4747
- forcetypeassert # Forces to assert types in tests
4848
- nonamedreturns # Disallows named returns
49-
- exhaustruct # Forces all structs to be named
49+
- exhaustruct # Forces all structs to be named
50+
- nosnakecase # Disallows snake case

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## Unreleased
8+
9+
### Added
10+
- Streaming API to encrypt with compression:
11+
- `func (keyRing *KeyRing) EncryptStreamWithCompression`
12+
- `func (keyRing *KeyRing) EncryptSplitStreamWithCompression`
13+
- `func (sk *SessionKey) EncryptStreamWithCompression`
14+
715
## [2.5.0] 2022-12-16
816
### Changed
917
- Update `github.com/ProtonMail/go-crypto` to the latest version

crypto/keyring_streaming.go

Lines changed: 84 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
"github.com/ProtonMail/go-crypto/openpgp"
1010
"github.com/ProtonMail/go-crypto/openpgp/packet"
11+
"github.com/ProtonMail/gopenpgp/v2/constants"
1112
"github.com/pkg/errors"
1213
)
1314

@@ -44,6 +45,47 @@ func (keyRing *KeyRing) EncryptStream(
4445
) (plainMessageWriter WriteCloser, err error) {
4546
config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: getTimeGenerator()}
4647

48+
return keyRing.encryptStreamWithConfig(
49+
config,
50+
pgpMessageWriter,
51+
pgpMessageWriter,
52+
plainMessageMetadata,
53+
signKeyRing,
54+
)
55+
}
56+
57+
// EncryptStreamWithCompression is used to encrypt data as a Writer.
58+
// The plaintext data is compressed before being encrypted.
59+
// It takes a writer for the encrypted data and returns a WriteCloser for the plaintext data
60+
// If signKeyRing is not nil, it is used to do an embedded signature.
61+
func (keyRing *KeyRing) EncryptStreamWithCompression(
62+
pgpMessageWriter Writer,
63+
plainMessageMetadata *PlainMessageMetadata,
64+
signKeyRing *KeyRing,
65+
) (plainMessageWriter WriteCloser, err error) {
66+
config := &packet.Config{
67+
DefaultCipher: packet.CipherAES256,
68+
Time: getTimeGenerator(),
69+
DefaultCompressionAlgo: constants.DefaultCompression,
70+
CompressionConfig: &packet.CompressionConfig{Level: constants.DefaultCompressionLevel},
71+
}
72+
73+
return keyRing.encryptStreamWithConfig(
74+
config,
75+
pgpMessageWriter,
76+
pgpMessageWriter,
77+
plainMessageMetadata,
78+
signKeyRing,
79+
)
80+
}
81+
82+
func (keyRing *KeyRing) encryptStreamWithConfig(
83+
config *packet.Config,
84+
keyPacketWriter Writer,
85+
dataPacketWriter Writer,
86+
plainMessageMetadata *PlainMessageMetadata,
87+
signKeyRing *KeyRing,
88+
) (plainMessageWriter WriteCloser, err error) {
4789
if plainMessageMetadata == nil {
4890
// Use sensible default metadata
4991
plainMessageMetadata = &PlainMessageMetadata{
@@ -59,7 +101,7 @@ func (keyRing *KeyRing) EncryptStream(
59101
ModTime: time.Unix(plainMessageMetadata.ModTime, 0),
60102
}
61103

62-
plainMessageWriter, err = asymmetricEncryptStream(hints, pgpMessageWriter, pgpMessageWriter, keyRing, signKeyRing, config)
104+
plainMessageWriter, err = asymmetricEncryptStream(hints, keyPacketWriter, dataPacketWriter, keyRing, signKeyRing, config)
63105
if err != nil {
64106
return nil, err
65107
}
@@ -109,26 +151,55 @@ func (keyRing *KeyRing) EncryptSplitStream(
109151
) (*EncryptSplitResult, error) {
110152
config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: getTimeGenerator()}
111153

112-
if plainMessageMetadata == nil {
113-
// Use sensible default metadata
114-
plainMessageMetadata = &PlainMessageMetadata{
115-
IsBinary: true,
116-
Filename: "",
117-
ModTime: GetUnixTime(),
118-
}
154+
var keyPacketBuf bytes.Buffer
155+
156+
plainMessageWriter, err := keyRing.encryptStreamWithConfig(
157+
config,
158+
&keyPacketBuf,
159+
dataPacketWriter,
160+
plainMessageMetadata,
161+
signKeyRing,
162+
)
163+
if err != nil {
164+
return nil, err
119165
}
120166

121-
hints := &openpgp.FileHints{
122-
FileName: plainMessageMetadata.Filename,
123-
IsBinary: plainMessageMetadata.IsBinary,
124-
ModTime: time.Unix(plainMessageMetadata.ModTime, 0),
167+
return &EncryptSplitResult{
168+
keyPacketBuf: &keyPacketBuf,
169+
plainMessageWriter: plainMessageWriter,
170+
}, nil
171+
}
172+
173+
// EncryptSplitStreamWithCompression is used to encrypt data as a stream.
174+
// It takes a writer for the Symmetrically Encrypted Data Packet
175+
// (https://datatracker.ietf.org/doc/html/rfc4880#section-5.7)
176+
// and returns a writer for the plaintext data and the key packet.
177+
// If signKeyRing is not nil, it is used to do an embedded signature.
178+
func (keyRing *KeyRing) EncryptSplitStreamWithCompression(
179+
dataPacketWriter Writer,
180+
plainMessageMetadata *PlainMessageMetadata,
181+
signKeyRing *KeyRing,
182+
) (*EncryptSplitResult, error) {
183+
config := &packet.Config{
184+
DefaultCipher: packet.CipherAES256,
185+
Time: getTimeGenerator(),
186+
DefaultCompressionAlgo: constants.DefaultCompression,
187+
CompressionConfig: &packet.CompressionConfig{Level: constants.DefaultCompressionLevel},
125188
}
126189

127190
var keyPacketBuf bytes.Buffer
128-
plainMessageWriter, err := asymmetricEncryptStream(hints, &keyPacketBuf, dataPacketWriter, keyRing, signKeyRing, config)
191+
192+
plainMessageWriter, err := keyRing.encryptStreamWithConfig(
193+
config,
194+
&keyPacketBuf,
195+
dataPacketWriter,
196+
plainMessageMetadata,
197+
signKeyRing,
198+
)
129199
if err != nil {
130200
return nil, err
131201
}
202+
132203
return &EncryptSplitResult{
133204
keyPacketBuf: &keyPacketBuf,
134205
plainMessageWriter: plainMessageWriter,

crypto/keyring_streaming_test.go

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,34 @@ func TestKeyRing_EncryptDecryptStream(t *testing.T) {
107107
}
108108

109109
func TestKeyRing_EncryptStreamCompatible(t *testing.T) {
110+
enc := func(w io.Writer, meta *PlainMessageMetadata, kr *KeyRing) (io.WriteCloser, error) {
111+
return keyRingTestPublic.EncryptStream(
112+
w,
113+
meta,
114+
kr,
115+
)
116+
}
117+
testKeyRing_EncryptStreamCompatible(enc, t)
118+
}
119+
120+
func TestKeyRing_EncryptStreamWithCompressionCompatible(t *testing.T) {
121+
enc := func(w io.Writer, meta *PlainMessageMetadata, kr *KeyRing) (io.WriteCloser, error) {
122+
return keyRingTestPublic.EncryptStreamWithCompression(
123+
w,
124+
meta,
125+
kr,
126+
)
127+
}
128+
testKeyRing_EncryptStreamCompatible(enc, t)
129+
}
130+
131+
type keyringEncryptionFunction = func(io.Writer, *PlainMessageMetadata, *KeyRing) (io.WriteCloser, error)
132+
133+
func testKeyRing_EncryptStreamCompatible(encrypt keyringEncryptionFunction, t *testing.T) {
110134
messageBytes := []byte("Hello World!")
111135
messageReader := bytes.NewReader(messageBytes)
112136
var ciphertextBuf bytes.Buffer
113-
messageWriter, err := keyRingTestPublic.EncryptStream(
137+
messageWriter, err := encrypt(
114138
&ciphertextBuf,
115139
testMeta,
116140
keyRingTestPrivate,
@@ -276,10 +300,34 @@ func TestKeyRing_EncryptDecryptSplitStream(t *testing.T) {
276300
}
277301

278302
func TestKeyRing_EncryptSplitStreamCompatible(t *testing.T) {
303+
enc := func(w io.Writer, meta *PlainMessageMetadata, kr *KeyRing) (*EncryptSplitResult, error) {
304+
return keyRingTestPublic.EncryptSplitStream(
305+
w,
306+
meta,
307+
kr,
308+
)
309+
}
310+
testKeyRing_EncryptSplitStreamCompatible(enc, t)
311+
}
312+
313+
func TestKeyRing_EncryptSplitStreamWithCompressionCompatible(t *testing.T) {
314+
enc := func(w io.Writer, meta *PlainMessageMetadata, kr *KeyRing) (*EncryptSplitResult, error) {
315+
return keyRingTestPublic.EncryptSplitStreamWithCompression(
316+
w,
317+
meta,
318+
kr,
319+
)
320+
}
321+
testKeyRing_EncryptSplitStreamCompatible(enc, t)
322+
}
323+
324+
type keyringEncryptionSplitFunction = func(io.Writer, *PlainMessageMetadata, *KeyRing) (*EncryptSplitResult, error)
325+
326+
func testKeyRing_EncryptSplitStreamCompatible(encrypt keyringEncryptionSplitFunction, t *testing.T) {
279327
messageBytes := []byte("Hello World!")
280328
messageReader := bytes.NewReader(messageBytes)
281329
var dataPacketBuf bytes.Buffer
282-
encryptionResult, err := keyRingTestPublic.EncryptSplitStream(
330+
encryptionResult, err := encrypt(
283331
&dataPacketBuf,
284332
testMeta,
285333
keyRingTestPrivate,

crypto/sessionkey_streaming.go

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package crypto
33
import (
44
"github.com/ProtonMail/go-crypto/openpgp"
55
"github.com/ProtonMail/go-crypto/openpgp/packet"
6+
"github.com/ProtonMail/gopenpgp/v2/constants"
67
"github.com/pkg/errors"
78
)
89

@@ -30,15 +31,50 @@ func (sk *SessionKey) EncryptStream(
3031
plainMessageMetadata *PlainMessageMetadata,
3132
signKeyRing *KeyRing,
3233
) (plainMessageWriter WriteCloser, err error) {
33-
dc, err := sk.GetCipherFunc()
34-
if err != nil {
35-
return nil, errors.Wrap(err, "gopenpgp: unable to encrypt with session key")
34+
config := &packet.Config{
35+
Time: getTimeGenerator(),
3636
}
37+
return sk.encryptStreamWithConfig(
38+
config,
39+
dataPacketWriter,
40+
plainMessageMetadata,
41+
signKeyRing,
42+
)
43+
}
3744

45+
// EncryptStreamWithCompression is used to encrypt data as a Writer.
46+
// The plaintext data is compressed before being encrypted.
47+
// It takes a writer for the encrypted data packet and returns a writer for the plaintext data.
48+
// If signKeyRing is not nil, it is used to do an embedded signature.
49+
func (sk *SessionKey) EncryptStreamWithCompression(
50+
dataPacketWriter Writer,
51+
plainMessageMetadata *PlainMessageMetadata,
52+
signKeyRing *KeyRing,
53+
) (plainMessageWriter WriteCloser, err error) {
3854
config := &packet.Config{
39-
Time: getTimeGenerator(),
40-
DefaultCipher: dc,
55+
Time: getTimeGenerator(),
56+
DefaultCompressionAlgo: constants.DefaultCompression,
57+
CompressionConfig: &packet.CompressionConfig{Level: constants.DefaultCompressionLevel},
58+
}
59+
return sk.encryptStreamWithConfig(
60+
config,
61+
dataPacketWriter,
62+
plainMessageMetadata,
63+
signKeyRing,
64+
)
65+
}
66+
67+
func (sk *SessionKey) encryptStreamWithConfig(
68+
config *packet.Config,
69+
dataPacketWriter Writer,
70+
plainMessageMetadata *PlainMessageMetadata,
71+
signKeyRing *KeyRing,
72+
) (plainMessageWriter WriteCloser, err error) {
73+
dc, err := sk.GetCipherFunc()
74+
if err != nil {
75+
return nil, errors.Wrap(err, "gopenpgp: unable to encrypt with session key")
4176
}
77+
config.DefaultCipher = dc
4278
var signEntity *openpgp.Entity
4379
if signKeyRing != nil {
4480
signEntity, err = signKeyRing.getSigningEntity()

crypto/sessionkey_streaming_test.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,26 @@ func TestSessionKey_EncryptDecryptStream(t *testing.T) {
7474
}
7575

7676
func TestSessionKey_EncryptStreamCompatible(t *testing.T) {
77+
enc := func(w io.Writer, meta *PlainMessageMetadata, kr *KeyRing) (io.WriteCloser, error) {
78+
return testSessionKey.EncryptStream(w, meta, kr)
79+
}
80+
testSessionKey_EncryptStreamCompatible(enc, t)
81+
}
82+
83+
func TestSessionKey_EncryptStreamWithCompressionCompatible(t *testing.T) {
84+
enc := func(w io.Writer, meta *PlainMessageMetadata, kr *KeyRing) (io.WriteCloser, error) {
85+
return testSessionKey.EncryptStreamWithCompression(w, meta, kr)
86+
}
87+
testSessionKey_EncryptStreamCompatible(enc, t)
88+
}
89+
90+
type sessionKeyEncryptionFunction = func(io.Writer, *PlainMessageMetadata, *KeyRing) (io.WriteCloser, error)
91+
92+
func testSessionKey_EncryptStreamCompatible(enc sessionKeyEncryptionFunction, t *testing.T) {
7793
messageBytes := []byte("Hello World!")
7894
messageReader := bytes.NewReader(messageBytes)
7995
var dataPacketBuf bytes.Buffer
80-
messageWriter, err := testSessionKey.EncryptStream(
96+
messageWriter, err := enc(
8197
&dataPacketBuf,
8298
testMeta,
8399
keyRingTestPrivate,

crypto/signature_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,8 +228,8 @@ func Test_KeyRing_GetVerifiedSignatureTimestampError(t *testing.T) {
228228
if err != nil {
229229
t.Errorf("Got an error while generating the signature: %v", err)
230230
}
231-
message_corrupted := NewPlainMessageFromString("Ciao world!")
232-
_, err = keyRingTestPublic.GetVerifiedSignatureTimestamp(message_corrupted, signature, 0)
231+
messageCorrupted := NewPlainMessageFromString("Ciao world!")
232+
_, err = keyRingTestPublic.GetVerifiedSignatureTimestamp(messageCorrupted, signature, 0)
233233
if err == nil {
234234
t.Errorf("Expected an error while parsing the creation time of a wrong signature, got nil")
235235
}

0 commit comments

Comments
 (0)