diff --git a/crypto/encryption_option.go b/crypto/encryption_option.go new file mode 100644 index 00000000..4f062356 --- /dev/null +++ b/crypto/encryption_option.go @@ -0,0 +1,49 @@ +package crypto + +import ( + "github.com/ProtonMail/go-crypto/openpgp/packet" + "github.com/ProtonMail/gopenpgp/v2/constants" +) + +// EncryptionOption defines an interface to apply configurations to packet.Config +type EncryptionOption interface { + apply(*packet.Config) +} + +type configFunc func(*packet.Config) + +func (f configFunc) apply(cfg *packet.Config) { + f(cfg) +} + +// WithDefault applies default settings for encryption +func WithDefault() EncryptionOption { + return configFunc(func(config *packet.Config) { + config.DefaultCipher = packet.CipherAES256 + config.DefaultCompressionAlgo = constants.DefaultCompression + config.CompressionConfig = &packet.CompressionConfig{Level: constants.DefaultCompressionLevel} + }) +} + +// WithCompression allows None, Zip or Zlib compression algorithms and sets compression level +func WithCompression(compressionAlgo packet.CompressionAlgo, + compressionConfig *packet.CompressionConfig) EncryptionOption { + return configFunc(func(config *packet.Config) { + config.DefaultCompressionAlgo = compressionAlgo + config.CompressionConfig = compressionConfig + }) +} + +// WithCipher allows Cipher3DES, CipherCAST5, CipherAES128, CipherAES192, CipherAES256 ciphers to be set +func WithCipher(cipher packet.CipherFunction) EncryptionOption { + return configFunc(func(config *packet.Config) { + config.DefaultCipher = cipher + }) +} + +// WithSigningContext defines signing context in encryption configuration +func WithSigningContext(signingContext *SigningContext) EncryptionOption { + return configFunc(func(config *packet.Config) { + config.SignatureNotations = append(config.SignatureNotations, signingContext.getNotation()) + }) +} diff --git a/crypto/keyring_message.go b/crypto/keyring_message.go index f0fc16e0..363e8317 100644 --- a/crypto/keyring_message.go +++ b/crypto/keyring_message.go @@ -46,6 +46,15 @@ func (keyRing *KeyRing) EncryptWithContextAndCompression(message *PlainMessage, return asymmetricEncrypt(message, keyRing, privateKey, true, signingContext) } +// EncryptWithOptions encrypts a PlainMessage to PGPMessage using public/private keys. +// * message : The plain data as a PlainMessage. +// * privateKey : (optional) an unlocked private keyring to include signature in the message. +// * opts : (optional) options to specify compression type and signing context. +// * output : The encrypted data as PGPMessage. +func (keyRing *KeyRing) EncryptWithOptions(message *PlainMessage, privateKey *KeyRing, opts ...EncryptionOption) (*PGPMessage, error) { + return asymmetricEncrypt(message, keyRing, privateKey, false, nil, opts...) +} + // Decrypt decrypts encrypted string using pgp keys, returning a PlainMessage // * message : The encrypted input as a PGPMessage // * verifyKey : Public key for signature verification (optional) @@ -201,6 +210,7 @@ func asymmetricEncrypt( publicKey, privateKey *KeyRing, compress bool, signingContext *SigningContext, + opts ...EncryptionOption, ) (*PGPMessage, error) { var outBuf bytes.Buffer var encryptWriter io.WriteCloser @@ -212,7 +222,7 @@ func asymmetricEncrypt( ModTime: plainMessage.getFormattedTime(), } - encryptWriter, err = asymmetricEncryptStream(hints, &outBuf, &outBuf, publicKey, privateKey, compress, signingContext) + encryptWriter, err = asymmetricEncryptStream(hints, &outBuf, &outBuf, publicKey, privateKey, compress, signingContext, opts...) if err != nil { return nil, err } @@ -238,6 +248,7 @@ func asymmetricEncryptStream( publicKey, privateKey *KeyRing, compress bool, signingContext *SigningContext, + opts ...EncryptionOption, ) (encryptWriter io.WriteCloser, err error) { config := &packet.Config{ DefaultCipher: packet.CipherAES256, @@ -253,6 +264,11 @@ func asymmetricEncryptStream( config.SignatureNotations = append(config.SignatureNotations, signingContext.getNotation()) } + // Overwrite defaults if EncryptionOptions are provided + for _, opt := range opts { + opt.apply(config) + } + var signEntity *openpgp.Entity if privateKey != nil && len(privateKey.entities) > 0 { var err error diff --git a/crypto/keyring_message_test.go b/crypto/keyring_message_test.go index de1f8eeb..8edc0b10 100644 --- a/crypto/keyring_message_test.go +++ b/crypto/keyring_message_test.go @@ -4,6 +4,8 @@ import ( "io/ioutil" "testing" + "github.com/ProtonMail/go-crypto/openpgp/packet" + "github.com/ProtonMail/gopenpgp/v2/constants" "github.com/stretchr/testify/assert" ) @@ -78,3 +80,27 @@ func TestTextMessageEncryptionWithSignatureAndContextAndCompression(t *testing.T } assert.Exactly(t, message.GetString(), decrypted.GetString()) } + +func TestTextMessageEncryptionWithOptions(t *testing.T) { + var message = NewPlainMessageFromString("plain text") + var testContext = "test-context" + + ciphertext, err := keyRingTestPublic.EncryptWithOptions(message, keyRingTestPrivate, + WithCompression(packet.CompressionZIP, &packet.CompressionConfig{Level: constants.DefaultCompressionLevel}), + WithCipher(packet.CipherAES256), + WithSigningContext(NewSigningContext(testContext, true))) + if err != nil { + t.Fatal("Expected no error when encrypting, got:", err) + } + + decrypted, err := keyRingTestPrivate.DecryptWithContext( + ciphertext, + keyRingTestPublic, + GetUnixTime(), + NewVerificationContext(testContext, true, 0), + ) + if err != nil { + t.Fatal("Expected no error when decrypting, got:", err) + } + assert.Exactly(t, message.GetString(), decrypted.GetString()) +}