peridot/vendor/github.com/ProtonMail/gopenpgp/v2/crypto/keyring_message.go
2024-10-16 13:40:38 +02:00

356 lines
12 KiB
Go

package crypto
import (
"bytes"
"io"
"io/ioutil"
"time"
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/ProtonMail/go-crypto/openpgp/packet"
"github.com/ProtonMail/gopenpgp/v2/constants"
"github.com/pkg/errors"
)
// Encrypt encrypts a PlainMessage, outputs a PGPMessage.
// If an unlocked private key is also provided it will also sign the message.
// * 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) {
return asymmetricEncrypt(message, keyRing, privateKey, false, nil)
}
// EncryptWithContext encrypts a PlainMessage, outputs a PGPMessage.
// If an unlocked private key is also provided it will also sign the message.
// * message : The plaintext input as a PlainMessage.
// * privateKey : (optional) an unlocked private keyring to include signature in the message.
// * signingContext : (optional) the context for the signature.
func (keyRing *KeyRing) EncryptWithContext(message *PlainMessage, privateKey *KeyRing, signingContext *SigningContext) (*PGPMessage, error) {
return asymmetricEncrypt(message, keyRing, privateKey, false, signingContext)
}
// EncryptWithCompression encrypts with compression support 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.
// * output : The encrypted data as PGPMessage.
func (keyRing *KeyRing) EncryptWithCompression(message *PlainMessage, privateKey *KeyRing) (*PGPMessage, error) {
return asymmetricEncrypt(message, keyRing, privateKey, true, nil)
}
// EncryptWithContextAndCompression encrypts with compression support 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.
// * signingContext : (optional) the context for the signature.
// * output : The encrypted data as PGPMessage.
func (keyRing *KeyRing) EncryptWithContextAndCompression(message *PlainMessage, privateKey *KeyRing, signingContext *SigningContext) (*PGPMessage, error) {
return asymmetricEncrypt(message, keyRing, privateKey, true, signingContext)
}
// Decrypt decrypts encrypted string using pgp keys, returning a PlainMessage
// * message : The encrypted input as a PGPMessage
// * verifyKey : Public key for signature verification (optional)
// * verifyTime : Time at verification (necessary only if verifyKey is not nil)
// * verificationContext : (optional) the context for the signature verification.
//
// When verifyKey is not provided, then verifyTime should be zero, and
// signature verification will be ignored.
func (keyRing *KeyRing) Decrypt(
message *PGPMessage, verifyKey *KeyRing, verifyTime int64,
) (*PlainMessage, error) {
return asymmetricDecrypt(message.NewReader(), keyRing, verifyKey, verifyTime, nil)
}
// DecryptWithContext decrypts encrypted string using pgp keys, returning a PlainMessage
// * message : The encrypted input as a PGPMessage
// * verifyKey : Public key for signature verification (optional)
// * verifyTime : Time at verification (necessary only if verifyKey is not nil)
// * verificationContext : (optional) the context for the signature verification.
//
// When verifyKey is not provided, then verifyTime should be zero, and
// signature verification will be ignored.
func (keyRing *KeyRing) DecryptWithContext(
message *PGPMessage,
verifyKey *KeyRing,
verifyTime int64,
verificationContext *VerificationContext,
) (*PlainMessage, error) {
return asymmetricDecrypt(message.NewReader(), keyRing, verifyKey, verifyTime, verificationContext)
}
// SignDetached generates and returns a PGPSignature for a given PlainMessage.
func (keyRing *KeyRing) SignDetached(message *PlainMessage) (*PGPSignature, error) {
return keyRing.SignDetachedWithContext(message, nil)
}
// SignDetachedWithContext generates and returns a PGPSignature for a given PlainMessage.
// If a context is provided, it is added to the signature as notation data
// with the name set in `constants.SignatureContextName`.
func (keyRing *KeyRing) SignDetachedWithContext(message *PlainMessage, context *SigningContext) (*PGPSignature, error) {
return signMessageDetached(
keyRing,
message.NewReader(),
message.IsBinary(),
context,
)
}
// VerifyDetached verifies a PlainMessage with a detached PGPSignature
// and returns a SignatureVerificationError if fails.
func (keyRing *KeyRing) VerifyDetached(message *PlainMessage, signature *PGPSignature, verifyTime int64) error {
_, err := verifySignature(
keyRing.entities,
message.NewReader(),
signature.GetBinary(),
verifyTime,
nil,
)
return err
}
// VerifyDetachedWithContext verifies a PlainMessage with a detached PGPSignature
// and returns a SignatureVerificationError if fails.
// If a context is provided, it verifies that the signature is valid in the given context, using
// the signature notation with name the name set in `constants.SignatureContextName`.
func (keyRing *KeyRing) VerifyDetachedWithContext(message *PlainMessage, signature *PGPSignature, verifyTime int64, verificationContext *VerificationContext) error {
_, err := verifySignature(
keyRing.entities,
message.NewReader(),
signature.GetBinary(),
verifyTime,
verificationContext,
)
return err
}
// SignDetachedEncrypted generates and returns a PGPMessage
// containing an encrypted detached signature for a given PlainMessage.
func (keyRing *KeyRing) SignDetachedEncrypted(message *PlainMessage, encryptionKeyRing *KeyRing) (encryptedSignature *PGPMessage, err error) {
if encryptionKeyRing == nil {
return nil, errors.New("gopenpgp: no encryption key ring provided")
}
signature, err := keyRing.SignDetached(message)
if err != nil {
return nil, err
}
plainMessage := NewPlainMessage(signature.GetBinary())
encryptedSignature, err = encryptionKeyRing.Encrypt(plainMessage, nil)
return
}
// VerifyDetachedEncrypted verifies a PlainMessage
// with a PGPMessage containing an encrypted detached signature
// and returns a SignatureVerificationError if fails.
func (keyRing *KeyRing) VerifyDetachedEncrypted(message *PlainMessage, encryptedSignature *PGPMessage, decryptionKeyRing *KeyRing, verifyTime int64) error {
if decryptionKeyRing == nil {
return errors.New("gopenpgp: no decryption key ring provided")
}
plainMessage, err := decryptionKeyRing.Decrypt(encryptedSignature, nil, 0)
if err != nil {
return err
}
signature := NewPGPSignature(plainMessage.GetBinary())
return keyRing.VerifyDetached(message, signature, verifyTime)
}
// GetVerifiedSignatureTimestamp verifies a PlainMessage with a detached PGPSignature
// returns the creation time of the signature if it succeeds
// and returns a SignatureVerificationError if fails.
func (keyRing *KeyRing) GetVerifiedSignatureTimestamp(message *PlainMessage, signature *PGPSignature, verifyTime int64) (int64, error) {
sigPacket, err := verifySignature(
keyRing.entities,
message.NewReader(),
signature.GetBinary(),
verifyTime,
nil,
)
if err != nil {
return 0, err
}
return sigPacket.CreationTime.Unix(), nil
}
// GetVerifiedSignatureTimestampWithContext verifies a PlainMessage with a detached PGPSignature
// returns the creation time of the signature if it succeeds
// and returns a SignatureVerificationError if fails.
// If a context is provided, it verifies that the signature is valid in the given context, using
// the signature notation with name the name set in `constants.SignatureContextName`.
func (keyRing *KeyRing) GetVerifiedSignatureTimestampWithContext(
message *PlainMessage,
signature *PGPSignature,
verifyTime int64,
verificationContext *VerificationContext,
) (int64, error) {
sigPacket, err := verifySignature(
keyRing.entities,
message.NewReader(),
signature.GetBinary(),
verifyTime,
verificationContext,
)
if err != nil {
return 0, err
}
return sigPacket.CreationTime.Unix(), nil
}
// ------ INTERNAL FUNCTIONS -------
// Core for encryption+signature (non-streaming) functions.
func asymmetricEncrypt(
plainMessage *PlainMessage,
publicKey, privateKey *KeyRing,
compress bool,
signingContext *SigningContext,
) (*PGPMessage, error) {
var outBuf bytes.Buffer
var encryptWriter io.WriteCloser
var err error
hints := &openpgp.FileHints{
IsBinary: plainMessage.IsBinary(),
FileName: plainMessage.Filename,
ModTime: plainMessage.getFormattedTime(),
}
encryptWriter, err = asymmetricEncryptStream(hints, &outBuf, &outBuf, publicKey, privateKey, compress, signingContext)
if err != nil {
return nil, err
}
_, err = encryptWriter.Write(plainMessage.GetBinary())
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: error in writing to message")
}
err = encryptWriter.Close()
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: error in closing message")
}
return &PGPMessage{outBuf.Bytes()}, nil
}
// Core for encryption+signature (all) functions.
func asymmetricEncryptStream(
hints *openpgp.FileHints,
keyPacketWriter io.Writer,
dataPacketWriter io.Writer,
publicKey, privateKey *KeyRing,
compress bool,
signingContext *SigningContext,
) (encryptWriter io.WriteCloser, err error) {
config := &packet.Config{
DefaultCipher: packet.CipherAES256,
Time: getTimeGenerator(),
}
if compress {
config.DefaultCompressionAlgo = constants.DefaultCompression
config.CompressionConfig = &packet.CompressionConfig{Level: constants.DefaultCompressionLevel}
}
if signingContext != nil {
config.SignatureNotations = append(config.SignatureNotations, signingContext.getNotation())
}
var signEntity *openpgp.Entity
if privateKey != nil && len(privateKey.entities) > 0 {
var err error
signEntity, err = privateKey.getSigningEntity()
if err != nil {
return nil, err
}
}
if hints.IsBinary {
encryptWriter, err = openpgp.EncryptSplit(keyPacketWriter, dataPacketWriter, publicKey.entities, signEntity, hints, config)
} else {
encryptWriter, err = openpgp.EncryptTextSplit(keyPacketWriter, dataPacketWriter, publicKey.entities, signEntity, hints, config)
}
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: error in encrypting asymmetrically")
}
return encryptWriter, nil
}
// Core for decryption+verification (non streaming) functions.
func asymmetricDecrypt(
encryptedIO io.Reader,
privateKey *KeyRing,
verifyKey *KeyRing,
verifyTime int64,
verificationContext *VerificationContext,
) (message *PlainMessage, err error) {
messageDetails, err := asymmetricDecryptStream(
encryptedIO,
privateKey,
verifyKey,
verifyTime,
verificationContext,
)
if err != nil {
return nil, err
}
body, err := ioutil.ReadAll(messageDetails.UnverifiedBody)
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: error in reading message body")
}
if verifyKey != nil {
processSignatureExpiration(messageDetails, verifyTime)
err = verifyDetailsSignature(messageDetails, verifyKey, verificationContext)
}
return &PlainMessage{
Data: body,
TextType: !messageDetails.LiteralData.IsBinary,
Filename: messageDetails.LiteralData.FileName,
Time: messageDetails.LiteralData.Time,
}, err
}
// Core for decryption+verification (all) functions.
func asymmetricDecryptStream(
encryptedIO io.Reader,
privateKey *KeyRing,
verifyKey *KeyRing,
verifyTime int64,
verificationContext *VerificationContext,
) (messageDetails *openpgp.MessageDetails, err error) {
privKeyEntries := privateKey.entities
var additionalEntries openpgp.EntityList
if verifyKey != nil {
additionalEntries = verifyKey.entities
}
if additionalEntries != nil {
privKeyEntries = append(privKeyEntries, additionalEntries...)
}
config := &packet.Config{
Time: func() time.Time {
if verifyTime == 0 {
/*
We default to current time while decrypting and verifying
but the caller will remove signature expiration errors later on.
See processSignatureExpiration().
*/
return getNow()
}
return time.Unix(verifyTime, 0)
},
}
if verificationContext != nil {
config.KnownNotations = map[string]bool{constants.SignatureContextName: true}
}
messageDetails, err = openpgp.ReadMessage(encryptedIO, privKeyEntries, nil, config)
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: error in reading message")
}
return messageDetails, err
}