mirror of
https://github.com/rocky-linux/peridot.git
synced 2024-11-23 21:51:27 +00:00
481 lines
13 KiB
Go
481 lines
13 KiB
Go
package crypto
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"io"
|
|
"time"
|
|
|
|
"github.com/ProtonMail/gopenpgp/v2/constants"
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/ProtonMail/go-crypto/openpgp"
|
|
pgpErrors "github.com/ProtonMail/go-crypto/openpgp/errors"
|
|
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
|
)
|
|
|
|
// SessionKey stores a decrypted session key.
|
|
type SessionKey struct {
|
|
// The decrypted binary session key.
|
|
Key []byte
|
|
// The symmetric encryption algorithm used with this key.
|
|
Algo string
|
|
}
|
|
|
|
var symKeyAlgos = map[string]packet.CipherFunction{
|
|
constants.ThreeDES: packet.Cipher3DES,
|
|
constants.TripleDES: packet.Cipher3DES,
|
|
constants.CAST5: packet.CipherCAST5,
|
|
constants.AES128: packet.CipherAES128,
|
|
constants.AES192: packet.CipherAES192,
|
|
constants.AES256: packet.CipherAES256,
|
|
}
|
|
|
|
type checkReader struct {
|
|
decrypted io.ReadCloser
|
|
body io.Reader
|
|
}
|
|
|
|
func (cr checkReader) Read(buf []byte) (int, error) {
|
|
n, sensitiveParsingError := cr.body.Read(buf)
|
|
if sensitiveParsingError == io.EOF {
|
|
mdcErr := cr.decrypted.Close()
|
|
if mdcErr != nil {
|
|
return n, mdcErr
|
|
}
|
|
return n, io.EOF
|
|
}
|
|
|
|
if sensitiveParsingError != nil {
|
|
return n, pgpErrors.StructuralError("parsing error")
|
|
}
|
|
|
|
return n, nil
|
|
}
|
|
|
|
// GetCipherFunc returns the cipher function corresponding to the algorithm used
|
|
// with this SessionKey.
|
|
func (sk *SessionKey) GetCipherFunc() (packet.CipherFunction, error) {
|
|
cf, ok := symKeyAlgos[sk.Algo]
|
|
if !ok {
|
|
return cf, errors.New("gopenpgp: unsupported cipher function: " + sk.Algo)
|
|
}
|
|
return cf, nil
|
|
}
|
|
|
|
// GetBase64Key returns the session key as base64 encoded string.
|
|
func (sk *SessionKey) GetBase64Key() string {
|
|
return base64.StdEncoding.EncodeToString(sk.Key)
|
|
}
|
|
|
|
// RandomToken generates a random token with the specified key size.
|
|
func RandomToken(size int) ([]byte, error) {
|
|
config := &packet.Config{DefaultCipher: packet.CipherAES256}
|
|
symKey := make([]byte, size)
|
|
if _, err := io.ReadFull(config.Random(), symKey); err != nil {
|
|
return nil, errors.Wrap(err, "gopenpgp: error in generating random token")
|
|
}
|
|
return symKey, nil
|
|
}
|
|
|
|
// GenerateSessionKeyAlgo generates a random key of the correct length for the
|
|
// specified algorithm.
|
|
func GenerateSessionKeyAlgo(algo string) (sk *SessionKey, err error) {
|
|
cf, ok := symKeyAlgos[algo]
|
|
if !ok {
|
|
return nil, errors.New("gopenpgp: unknown symmetric key generation algorithm")
|
|
}
|
|
r, err := RandomToken(cf.KeySize())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sk = &SessionKey{
|
|
Key: r,
|
|
Algo: algo,
|
|
}
|
|
return sk, nil
|
|
}
|
|
|
|
// GenerateSessionKey generates a random key for the default cipher.
|
|
func GenerateSessionKey() (*SessionKey, error) {
|
|
return GenerateSessionKeyAlgo(constants.AES256)
|
|
}
|
|
|
|
func NewSessionKeyFromToken(token []byte, algo string) *SessionKey {
|
|
return &SessionKey{
|
|
Key: clone(token),
|
|
Algo: algo,
|
|
}
|
|
}
|
|
|
|
func newSessionKeyFromEncrypted(ek *packet.EncryptedKey) (*SessionKey, error) {
|
|
var algo string
|
|
for k, v := range symKeyAlgos {
|
|
if v == ek.CipherFunc {
|
|
algo = k
|
|
break
|
|
}
|
|
}
|
|
if algo == "" {
|
|
return nil, fmt.Errorf("gopenpgp: unsupported cipher function: %v", ek.CipherFunc)
|
|
}
|
|
|
|
sk := &SessionKey{
|
|
Key: ek.Key,
|
|
Algo: algo,
|
|
}
|
|
|
|
if err := sk.checkSize(); err != nil {
|
|
return nil, errors.Wrap(err, "gopenpgp: unable to decrypt session key")
|
|
}
|
|
|
|
return sk, nil
|
|
}
|
|
|
|
// Encrypt encrypts a PlainMessage to PGPMessage with a SessionKey.
|
|
// * message : The plain data as a PlainMessage.
|
|
// * output : The encrypted data as PGPMessage.
|
|
func (sk *SessionKey) Encrypt(message *PlainMessage) ([]byte, error) {
|
|
return encryptWithSessionKey(message, sk, nil, false, nil)
|
|
}
|
|
|
|
// EncryptAndSign encrypts a PlainMessage to PGPMessage with a SessionKey and signs it with a Private key.
|
|
// * message : The plain data as a PlainMessage.
|
|
// * signKeyRing: The KeyRing to sign the message
|
|
// * output : The encrypted data as PGPMessage.
|
|
func (sk *SessionKey) EncryptAndSign(message *PlainMessage, signKeyRing *KeyRing) ([]byte, error) {
|
|
return encryptWithSessionKey(message, sk, signKeyRing, false, nil)
|
|
}
|
|
|
|
// EncryptAndSignWithContext encrypts a PlainMessage to PGPMessage with a SessionKey and signs it with a Private key.
|
|
// * message : The plain data as a PlainMessage.
|
|
// * signKeyRing: The KeyRing to sign the message
|
|
// * output : The encrypted data as PGPMessage.
|
|
// * signingContext : (optional) the context for the signature.
|
|
func (sk *SessionKey) EncryptAndSignWithContext(message *PlainMessage, signKeyRing *KeyRing, signingContext *SigningContext) ([]byte, error) {
|
|
return encryptWithSessionKey(message, sk, signKeyRing, false, signingContext)
|
|
}
|
|
|
|
// EncryptWithCompression encrypts with compression support a PlainMessage to PGPMessage with a SessionKey.
|
|
// * message : The plain data as a PlainMessage.
|
|
// * output : The encrypted data as PGPMessage.
|
|
func (sk *SessionKey) EncryptWithCompression(message *PlainMessage) ([]byte, error) {
|
|
return encryptWithSessionKey(message, sk, nil, true, nil)
|
|
}
|
|
|
|
func encryptWithSessionKey(
|
|
message *PlainMessage,
|
|
sk *SessionKey,
|
|
signKeyRing *KeyRing,
|
|
compress bool,
|
|
signingContext *SigningContext,
|
|
) ([]byte, error) {
|
|
var encBuf = new(bytes.Buffer)
|
|
|
|
encryptWriter, signWriter, err := encryptStreamWithSessionKey(
|
|
NewPlainMessageMetadata(
|
|
message.IsBinary(),
|
|
message.Filename,
|
|
int64(message.Time),
|
|
),
|
|
encBuf,
|
|
sk,
|
|
signKeyRing,
|
|
compress,
|
|
signingContext,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if signKeyRing != nil {
|
|
_, err = signWriter.Write(message.GetBinary())
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "gopenpgp: error in writing signed message")
|
|
}
|
|
err = signWriter.Close()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "gopenpgp: error in closing signing writer")
|
|
}
|
|
} else {
|
|
_, err = encryptWriter.Write(message.GetBinary())
|
|
}
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "gopenpgp: error in writing message")
|
|
}
|
|
err = encryptWriter.Close()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "gopenpgp: error in closing encryption writer")
|
|
}
|
|
return encBuf.Bytes(), nil
|
|
}
|
|
|
|
func encryptStreamWithSessionKey(
|
|
plainMessageMetadata *PlainMessageMetadata,
|
|
dataPacketWriter io.Writer,
|
|
sk *SessionKey,
|
|
signKeyRing *KeyRing,
|
|
compress bool,
|
|
signingContext *SigningContext,
|
|
) (encryptWriter, signWriter io.WriteCloser, err error) {
|
|
dc, err := sk.GetCipherFunc()
|
|
if err != nil {
|
|
return nil, nil, errors.Wrap(err, "gopenpgp: unable to encrypt with session key")
|
|
}
|
|
|
|
config := &packet.Config{
|
|
Time: getTimeGenerator(),
|
|
DefaultCipher: dc,
|
|
}
|
|
|
|
var signEntity *openpgp.Entity
|
|
if signKeyRing != nil {
|
|
signEntity, err = signKeyRing.getSigningEntity()
|
|
if err != nil {
|
|
return nil, nil, errors.Wrap(err, "gopenpgp: unable to sign")
|
|
}
|
|
}
|
|
|
|
if compress {
|
|
config.DefaultCompressionAlgo = constants.DefaultCompression
|
|
config.CompressionConfig = &packet.CompressionConfig{Level: constants.DefaultCompressionLevel}
|
|
}
|
|
|
|
if signingContext != nil {
|
|
config.SignatureNotations = append(config.SignatureNotations, signingContext.getNotation())
|
|
}
|
|
|
|
if plainMessageMetadata == nil {
|
|
// Use sensible default metadata
|
|
plainMessageMetadata = &PlainMessageMetadata{
|
|
IsBinary: true,
|
|
Filename: "",
|
|
ModTime: GetUnixTime(),
|
|
}
|
|
}
|
|
|
|
return encryptStreamWithSessionKeyAndConfig(
|
|
plainMessageMetadata.IsBinary,
|
|
plainMessageMetadata.Filename,
|
|
uint32(plainMessageMetadata.ModTime),
|
|
dataPacketWriter,
|
|
sk,
|
|
signEntity,
|
|
config,
|
|
)
|
|
}
|
|
|
|
func encryptStreamWithSessionKeyAndConfig(
|
|
isBinary bool,
|
|
filename string,
|
|
modTime uint32,
|
|
dataPacketWriter io.Writer,
|
|
sk *SessionKey,
|
|
signEntity *openpgp.Entity,
|
|
config *packet.Config,
|
|
) (encryptWriter, signWriter io.WriteCloser, err error) {
|
|
encryptWriter, err = packet.SerializeSymmetricallyEncrypted(
|
|
dataPacketWriter,
|
|
config.Cipher(),
|
|
config.AEAD() != nil,
|
|
packet.CipherSuite{Cipher: config.Cipher(), Mode: config.AEAD().Mode()},
|
|
sk.Key,
|
|
config,
|
|
)
|
|
|
|
if err != nil {
|
|
return nil, nil, errors.Wrap(err, "gopenpgp: unable to encrypt")
|
|
}
|
|
|
|
if algo := config.Compression(); algo != packet.CompressionNone {
|
|
encryptWriter, err = packet.SerializeCompressed(encryptWriter, algo, config.CompressionConfig)
|
|
if err != nil {
|
|
return nil, nil, errors.Wrap(err, "gopenpgp: error in compression")
|
|
}
|
|
}
|
|
|
|
if signEntity != nil {
|
|
hints := &openpgp.FileHints{
|
|
IsBinary: isBinary,
|
|
FileName: filename,
|
|
ModTime: time.Unix(int64(modTime), 0),
|
|
}
|
|
|
|
signWriter, err = openpgp.Sign(encryptWriter, signEntity, hints, config)
|
|
if err != nil {
|
|
return nil, nil, errors.Wrap(err, "gopenpgp: unable to sign")
|
|
}
|
|
} else {
|
|
encryptWriter, err = packet.SerializeLiteral(
|
|
encryptWriter,
|
|
isBinary,
|
|
filename,
|
|
modTime,
|
|
)
|
|
if err != nil {
|
|
return nil, nil, errors.Wrap(err, "gopenpgp: unable to serialize")
|
|
}
|
|
}
|
|
return encryptWriter, signWriter, nil
|
|
}
|
|
|
|
// Decrypt decrypts pgp data packets using directly a session key.
|
|
// * encrypted: PGPMessage.
|
|
// * output: PlainMessage.
|
|
func (sk *SessionKey) Decrypt(dataPacket []byte) (*PlainMessage, error) {
|
|
return sk.DecryptAndVerify(dataPacket, nil, 0)
|
|
}
|
|
|
|
// DecryptAndVerify decrypts pgp data packets using directly a session key and verifies embedded signatures.
|
|
// * encrypted: PGPMessage.
|
|
// * verifyKeyRing: KeyRing with verification public keys
|
|
// * verifyTime: when should the signature be valid, as timestamp. If 0 time verification is disabled.
|
|
// * output: PlainMessage.
|
|
func (sk *SessionKey) DecryptAndVerify(dataPacket []byte, verifyKeyRing *KeyRing, verifyTime int64) (*PlainMessage, error) {
|
|
return decryptWithSessionKeyAndContext(
|
|
sk,
|
|
dataPacket,
|
|
verifyKeyRing,
|
|
verifyTime,
|
|
nil,
|
|
)
|
|
}
|
|
|
|
// DecryptAndVerifyWithContext decrypts pgp data packets using directly a session key and verifies embedded signatures.
|
|
// * encrypted: PGPMessage.
|
|
// * verifyKeyRing: KeyRing with verification public keys
|
|
// * verifyTime: when should the signature be valid, as timestamp. If 0 time verification is disabled.
|
|
// * output: PlainMessage.
|
|
// * verificationContext (optional): context for the signature verification.
|
|
func (sk *SessionKey) DecryptAndVerifyWithContext(dataPacket []byte, verifyKeyRing *KeyRing, verifyTime int64, verificationContext *VerificationContext) (*PlainMessage, error) {
|
|
return decryptWithSessionKeyAndContext(
|
|
sk,
|
|
dataPacket,
|
|
verifyKeyRing,
|
|
verifyTime,
|
|
verificationContext,
|
|
)
|
|
}
|
|
|
|
func decryptWithSessionKeyAndContext(
|
|
sk *SessionKey,
|
|
dataPacket []byte,
|
|
verifyKeyRing *KeyRing,
|
|
verifyTime int64,
|
|
verificationContext *VerificationContext,
|
|
) (*PlainMessage, error) {
|
|
var messageReader = bytes.NewReader(dataPacket)
|
|
|
|
md, err := decryptStreamWithSessionKey(sk, messageReader, verifyKeyRing, verificationContext)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
messageBuf := new(bytes.Buffer)
|
|
_, err = messageBuf.ReadFrom(md.UnverifiedBody)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "gopenpgp: error in reading message body")
|
|
}
|
|
|
|
if verifyKeyRing != nil {
|
|
processSignatureExpiration(md, verifyTime)
|
|
err = verifyDetailsSignature(md, verifyKeyRing, verificationContext)
|
|
}
|
|
|
|
return &PlainMessage{
|
|
Data: messageBuf.Bytes(),
|
|
TextType: !md.LiteralData.IsBinary,
|
|
Filename: md.LiteralData.FileName,
|
|
Time: md.LiteralData.Time,
|
|
}, err
|
|
}
|
|
|
|
func decryptStreamWithSessionKey(
|
|
sk *SessionKey,
|
|
messageReader io.Reader,
|
|
verifyKeyRing *KeyRing,
|
|
verificationContext *VerificationContext,
|
|
) (*openpgp.MessageDetails, error) {
|
|
var decrypted io.ReadCloser
|
|
var keyring openpgp.EntityList
|
|
|
|
// Read symmetrically encrypted data packet
|
|
packets := packet.NewReader(messageReader)
|
|
p, err := packets.Next()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "gopenpgp: unable to read symmetric packet")
|
|
}
|
|
|
|
// Decrypt data packet
|
|
switch p := p.(type) {
|
|
case *packet.SymmetricallyEncrypted, *packet.AEADEncrypted:
|
|
if symPacket, ok := p.(*packet.SymmetricallyEncrypted); ok {
|
|
if !symPacket.IntegrityProtected {
|
|
return nil, errors.New("gopenpgp: message is not authenticated")
|
|
}
|
|
}
|
|
dc, err := sk.GetCipherFunc()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "gopenpgp: unable to decrypt with session key")
|
|
}
|
|
encryptedDataPacket, isDataPacket := p.(packet.EncryptedDataPacket)
|
|
if !isDataPacket {
|
|
return nil, errors.Wrap(err, "gopenpgp: unknown data packet")
|
|
}
|
|
decrypted, err = encryptedDataPacket.Decrypt(dc, sk.Key)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "gopenpgp: unable to decrypt symmetric packet")
|
|
}
|
|
default:
|
|
return nil, errors.New("gopenpgp: invalid packet type")
|
|
}
|
|
|
|
config := &packet.Config{
|
|
Time: getTimeGenerator(),
|
|
}
|
|
|
|
if verificationContext != nil {
|
|
config.KnownNotations = map[string]bool{constants.SignatureContextName: true}
|
|
}
|
|
|
|
// Push decrypted packet as literal packet and use openpgp's reader
|
|
if verifyKeyRing != nil {
|
|
keyring = verifyKeyRing.entities
|
|
} else {
|
|
keyring = openpgp.EntityList{}
|
|
}
|
|
|
|
md, err := openpgp.ReadMessage(decrypted, keyring, nil, config)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "gopenpgp: unable to decode symmetric packet")
|
|
}
|
|
|
|
md.UnverifiedBody = checkReader{decrypted, md.UnverifiedBody}
|
|
return md, nil
|
|
}
|
|
|
|
func (sk *SessionKey) checkSize() error {
|
|
cf, ok := symKeyAlgos[sk.Algo]
|
|
if !ok {
|
|
return errors.New("unknown symmetric key algorithm")
|
|
}
|
|
|
|
if cf.KeySize() != len(sk.Key) {
|
|
return errors.New("wrong session key size")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func getAlgo(cipher packet.CipherFunction) string {
|
|
algo := constants.AES256
|
|
for k, v := range symKeyAlgos {
|
|
if v == cipher {
|
|
algo = k
|
|
break
|
|
}
|
|
}
|
|
|
|
return algo
|
|
}
|