mirror of
https://github.com/rocky-linux/peridot.git
synced 2024-12-20 01:38:29 +00:00
180 lines
5.6 KiB
Go
180 lines
5.6 KiB
Go
package crypto
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
|
|
"github.com/ProtonMail/go-crypto/openpgp"
|
|
pgpErrors "github.com/ProtonMail/go-crypto/openpgp/errors"
|
|
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// EncryptMessageWithPassword encrypts a PlainMessage to PGPMessage with a
|
|
// SymmetricKey.
|
|
// * message : The plain data as a PlainMessage.
|
|
// * password: A password that will be derived into an encryption key.
|
|
// * output : The encrypted data as PGPMessage.
|
|
func EncryptMessageWithPassword(message *PlainMessage, password []byte) (*PGPMessage, error) {
|
|
encrypted, err := passwordEncrypt(message, password)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return NewPGPMessage(encrypted), nil
|
|
}
|
|
|
|
// DecryptMessageWithPassword decrypts password protected pgp binary messages.
|
|
// * encrypted: The encrypted data as PGPMessage.
|
|
// * password: A password that will be derived into an encryption key.
|
|
// * output: The decrypted data as PlainMessage.
|
|
func DecryptMessageWithPassword(message *PGPMessage, password []byte) (*PlainMessage, error) {
|
|
return passwordDecrypt(message.NewReader(), password)
|
|
}
|
|
|
|
// DecryptSessionKeyWithPassword decrypts the binary symmetrically encrypted
|
|
// session key packet and returns the session key.
|
|
func DecryptSessionKeyWithPassword(keyPacket, password []byte) (*SessionKey, error) {
|
|
keyReader := bytes.NewReader(keyPacket)
|
|
packets := packet.NewReader(keyReader)
|
|
|
|
var symKeys []*packet.SymmetricKeyEncrypted
|
|
for {
|
|
var p packet.Packet
|
|
var err error
|
|
if p, err = packets.Next(); err != nil {
|
|
break
|
|
}
|
|
|
|
if p, ok := p.(*packet.SymmetricKeyEncrypted); ok {
|
|
symKeys = append(symKeys, p)
|
|
}
|
|
}
|
|
|
|
// Try the symmetric passphrase first
|
|
if len(symKeys) != 0 && password != nil {
|
|
for _, s := range symKeys {
|
|
key, cipherFunc, err := s.Decrypt(password)
|
|
if err == nil {
|
|
sk := &SessionKey{
|
|
Key: key,
|
|
Algo: getAlgo(cipherFunc),
|
|
}
|
|
|
|
if err = sk.checkSize(); err != nil {
|
|
return nil, errors.Wrap(err, "gopenpgp: unable to decrypt session key with password")
|
|
}
|
|
|
|
return sk, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil, errors.New("gopenpgp: unable to decrypt any packet")
|
|
}
|
|
|
|
// EncryptSessionKeyWithPassword encrypts the session key with the password and
|
|
// returns a binary symmetrically encrypted session key packet.
|
|
func EncryptSessionKeyWithPassword(sk *SessionKey, password []byte) ([]byte, error) {
|
|
outbuf := &bytes.Buffer{}
|
|
|
|
cf, err := sk.GetCipherFunc()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "gopenpgp: unable to encrypt session key with password")
|
|
}
|
|
|
|
if len(password) == 0 {
|
|
return nil, errors.New("gopenpgp: password can't be empty")
|
|
}
|
|
|
|
if err = sk.checkSize(); err != nil {
|
|
return nil, errors.Wrap(err, "gopenpgp: unable to encrypt session key with password")
|
|
}
|
|
|
|
config := &packet.Config{
|
|
DefaultCipher: cf,
|
|
}
|
|
|
|
err = packet.SerializeSymmetricKeyEncryptedReuseKey(outbuf, sk.Key, password, config)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "gopenpgp: unable to encrypt session key with password")
|
|
}
|
|
return outbuf.Bytes(), nil
|
|
}
|
|
|
|
// ----- INTERNAL FUNCTIONS ------
|
|
|
|
func passwordEncrypt(message *PlainMessage, password []byte) ([]byte, error) {
|
|
var outBuf bytes.Buffer
|
|
|
|
config := &packet.Config{
|
|
DefaultCipher: packet.CipherAES256,
|
|
Time: getTimeGenerator(),
|
|
}
|
|
|
|
hints := &openpgp.FileHints{
|
|
IsBinary: message.IsBinary(),
|
|
FileName: message.Filename,
|
|
ModTime: message.getFormattedTime(),
|
|
}
|
|
|
|
encryptWriter, err := openpgp.SymmetricallyEncrypt(&outBuf, password, hints, config)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "gopenpgp: error in encrypting message symmetrically")
|
|
}
|
|
_, err = encryptWriter.Write(message.GetBinary())
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "gopenpgp: error in writing data to message")
|
|
}
|
|
|
|
err = encryptWriter.Close()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "gopenpgp: error in closing writer")
|
|
}
|
|
|
|
return outBuf.Bytes(), nil
|
|
}
|
|
|
|
func passwordDecrypt(encryptedIO io.Reader, password []byte) (*PlainMessage, error) {
|
|
firstTimeCalled := true
|
|
var prompt = func(keys []openpgp.Key, symmetric bool) ([]byte, error) {
|
|
if firstTimeCalled {
|
|
firstTimeCalled = false
|
|
return password, nil
|
|
}
|
|
// Re-prompt still occurs if SKESK pasrsing fails (i.e. when decrypted cipher algo is invalid).
|
|
// For most (but not all) cases, inputting a wrong passwords is expected to trigger this error.
|
|
return nil, errors.New("gopenpgp: wrong password in symmetric decryption")
|
|
}
|
|
|
|
config := &packet.Config{
|
|
Time: getTimeGenerator(),
|
|
}
|
|
|
|
var emptyKeyRing openpgp.EntityList
|
|
md, err := openpgp.ReadMessage(encryptedIO, emptyKeyRing, prompt, config)
|
|
if err != nil {
|
|
// Parsing errors when reading the message are most likely caused by incorrect password, but we cannot know for sure
|
|
return nil, errors.New("gopenpgp: error in reading password protected message: wrong password or malformed message")
|
|
}
|
|
|
|
messageBuf := bytes.NewBuffer(nil)
|
|
_, err = io.Copy(messageBuf, md.UnverifiedBody)
|
|
if errors.Is(err, pgpErrors.ErrMDCHashMismatch) {
|
|
// This MDC error may also be triggered if the password is correct, but the encrypted data was corrupted.
|
|
// To avoid confusion, we do not inform the user about the second possibility.
|
|
return nil, errors.New("gopenpgp: wrong password in symmetric decryption")
|
|
}
|
|
if err != nil {
|
|
// Parsing errors after decryption, triggered before parsing the MDC packet, are also usually the result of wrong password
|
|
return nil, errors.New("gopenpgp: error in reading password protected message: wrong password or malformed message")
|
|
}
|
|
|
|
return &PlainMessage{
|
|
Data: messageBuf.Bytes(),
|
|
TextType: !md.LiteralData.IsBinary,
|
|
Filename: md.LiteralData.FileName,
|
|
Time: md.LiteralData.Time,
|
|
}, nil
|
|
}
|