mirror of
https://github.com/rocky-linux/peridot.git
synced 2024-12-26 20:20:56 +00:00
294 lines
8.9 KiB
Go
294 lines
8.9 KiB
Go
|
package crypto
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"crypto"
|
||
|
"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) {
|
||
|
config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: getTimeGenerator()}
|
||
|
encrypted, err := asymmetricEncrypt(message, keyRing, privateKey, config)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return NewPGPMessage(encrypted), nil
|
||
|
}
|
||
|
|
||
|
// 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) {
|
||
|
config := &packet.Config{
|
||
|
DefaultCipher: packet.CipherAES256,
|
||
|
Time: getTimeGenerator(),
|
||
|
DefaultCompressionAlgo: constants.DefaultCompression,
|
||
|
CompressionConfig: &packet.CompressionConfig{Level: constants.DefaultCompressionLevel},
|
||
|
}
|
||
|
|
||
|
encrypted, err := asymmetricEncrypt(message, keyRing, privateKey, config)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return NewPGPMessage(encrypted), nil
|
||
|
}
|
||
|
|
||
|
// 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)
|
||
|
//
|
||
|
// 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)
|
||
|
}
|
||
|
|
||
|
// SignDetached generates and returns a PGPSignature for a given PlainMessage.
|
||
|
func (keyRing *KeyRing) SignDetached(message *PlainMessage) (*PGPSignature, error) {
|
||
|
signEntity, err := keyRing.getSigningEntity()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
config := &packet.Config{DefaultHash: crypto.SHA512, Time: getTimeGenerator()}
|
||
|
var outBuf bytes.Buffer
|
||
|
// sign bin
|
||
|
if err := openpgp.DetachSign(&outBuf, signEntity, message.NewReader(), config); err != nil {
|
||
|
return nil, errors.Wrap(err, "gopenpgp: error in signing")
|
||
|
}
|
||
|
|
||
|
return NewPGPSignature(outBuf.Bytes()), nil
|
||
|
}
|
||
|
|
||
|
// 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 {
|
||
|
return verifySignature(
|
||
|
keyRing.entities,
|
||
|
message.NewReader(),
|
||
|
signature.GetBinary(),
|
||
|
verifyTime,
|
||
|
)
|
||
|
}
|
||
|
|
||
|
// 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) {
|
||
|
packets := packet.NewReader(bytes.NewReader(signature.Data))
|
||
|
var err error
|
||
|
var p packet.Packet
|
||
|
for {
|
||
|
p, err = packets.Next()
|
||
|
if errors.Is(err, io.EOF) {
|
||
|
break
|
||
|
}
|
||
|
if err != nil {
|
||
|
continue
|
||
|
}
|
||
|
sigPacket, ok := p.(*packet.Signature)
|
||
|
if !ok {
|
||
|
continue
|
||
|
}
|
||
|
var outBuf bytes.Buffer
|
||
|
err = sigPacket.Serialize(&outBuf)
|
||
|
if err != nil {
|
||
|
continue
|
||
|
}
|
||
|
err = verifySignature(
|
||
|
keyRing.entities,
|
||
|
message.NewReader(),
|
||
|
outBuf.Bytes(),
|
||
|
verifyTime,
|
||
|
)
|
||
|
if err != nil {
|
||
|
continue
|
||
|
}
|
||
|
return sigPacket.CreationTime.Unix(), nil
|
||
|
}
|
||
|
return 0, errors.Wrap(err, "gopenpgp: can't verify any signature packets")
|
||
|
}
|
||
|
|
||
|
// ------ INTERNAL FUNCTIONS -------
|
||
|
|
||
|
// Core for encryption+signature (non-streaming) functions.
|
||
|
func asymmetricEncrypt(
|
||
|
plainMessage *PlainMessage,
|
||
|
publicKey, privateKey *KeyRing,
|
||
|
config *packet.Config,
|
||
|
) ([]byte, 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, config)
|
||
|
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 outBuf.Bytes(), nil
|
||
|
}
|
||
|
|
||
|
// Core for encryption+signature (all) functions.
|
||
|
func asymmetricEncryptStream(
|
||
|
hints *openpgp.FileHints,
|
||
|
keyPacketWriter io.Writer,
|
||
|
dataPacketWriter io.Writer,
|
||
|
publicKey, privateKey *KeyRing,
|
||
|
config *packet.Config,
|
||
|
) (encryptWriter io.WriteCloser, err error) {
|
||
|
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,
|
||
|
) (message *PlainMessage, err error) {
|
||
|
messageDetails, err := asymmetricDecryptStream(
|
||
|
encryptedIO,
|
||
|
privateKey,
|
||
|
verifyKey,
|
||
|
verifyTime,
|
||
|
)
|
||
|
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)
|
||
|
}
|
||
|
|
||
|
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,
|
||
|
) (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)
|
||
|
},
|
||
|
}
|
||
|
|
||
|
messageDetails, err = openpgp.ReadMessage(encryptedIO, privKeyEntries, nil, config)
|
||
|
if err != nil {
|
||
|
return nil, errors.Wrap(err, "gopenpgp: error in reading message")
|
||
|
}
|
||
|
return messageDetails, err
|
||
|
}
|