Instead of parsing the signature packets manually, use the signature packet returned by VerifyDetachedSignatureAndHash to get the signature creation time.
372 lines
11 KiB
Go
372 lines
11 KiB
Go
package crypto
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto"
|
|
"io"
|
|
"time"
|
|
|
|
"github.com/ProtonMail/go-crypto/openpgp"
|
|
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
|
"github.com/ProtonMail/gopenpgp/v2/constants"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
type Reader interface {
|
|
Read(b []byte) (n int, err error)
|
|
}
|
|
|
|
type Writer interface {
|
|
Write(b []byte) (n int, err error)
|
|
}
|
|
|
|
type WriteCloser interface {
|
|
Write(b []byte) (n int, err error)
|
|
Close() (err error)
|
|
}
|
|
|
|
type PlainMessageMetadata struct {
|
|
IsBinary bool
|
|
Filename string
|
|
ModTime int64
|
|
}
|
|
|
|
func NewPlainMessageMetadata(isBinary bool, filename string, modTime int64) *PlainMessageMetadata {
|
|
return &PlainMessageMetadata{IsBinary: isBinary, Filename: filename, ModTime: modTime}
|
|
}
|
|
|
|
// EncryptStream is used to encrypt data as a Writer.
|
|
// It takes a writer for the encrypted data and returns a WriteCloser for the plaintext data
|
|
// If signKeyRing is not nil, it is used to do an embedded signature.
|
|
func (keyRing *KeyRing) EncryptStream(
|
|
pgpMessageWriter Writer,
|
|
plainMessageMetadata *PlainMessageMetadata,
|
|
signKeyRing *KeyRing,
|
|
) (plainMessageWriter WriteCloser, err error) {
|
|
config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: getTimeGenerator()}
|
|
|
|
return keyRing.encryptStreamWithConfig(
|
|
config,
|
|
pgpMessageWriter,
|
|
pgpMessageWriter,
|
|
plainMessageMetadata,
|
|
signKeyRing,
|
|
)
|
|
}
|
|
|
|
// EncryptStreamWithCompression is used to encrypt data as a Writer.
|
|
// The plaintext data is compressed before being encrypted.
|
|
// It takes a writer for the encrypted data and returns a WriteCloser for the plaintext data
|
|
// If signKeyRing is not nil, it is used to do an embedded signature.
|
|
func (keyRing *KeyRing) EncryptStreamWithCompression(
|
|
pgpMessageWriter Writer,
|
|
plainMessageMetadata *PlainMessageMetadata,
|
|
signKeyRing *KeyRing,
|
|
) (plainMessageWriter WriteCloser, err error) {
|
|
config := &packet.Config{
|
|
DefaultCipher: packet.CipherAES256,
|
|
Time: getTimeGenerator(),
|
|
DefaultCompressionAlgo: constants.DefaultCompression,
|
|
CompressionConfig: &packet.CompressionConfig{Level: constants.DefaultCompressionLevel},
|
|
}
|
|
|
|
return keyRing.encryptStreamWithConfig(
|
|
config,
|
|
pgpMessageWriter,
|
|
pgpMessageWriter,
|
|
plainMessageMetadata,
|
|
signKeyRing,
|
|
)
|
|
}
|
|
|
|
func (keyRing *KeyRing) encryptStreamWithConfig(
|
|
config *packet.Config,
|
|
keyPacketWriter Writer,
|
|
dataPacketWriter Writer,
|
|
plainMessageMetadata *PlainMessageMetadata,
|
|
signKeyRing *KeyRing,
|
|
) (plainMessageWriter WriteCloser, err error) {
|
|
if plainMessageMetadata == nil {
|
|
// Use sensible default metadata
|
|
plainMessageMetadata = &PlainMessageMetadata{
|
|
IsBinary: true,
|
|
Filename: "",
|
|
ModTime: GetUnixTime(),
|
|
}
|
|
}
|
|
|
|
hints := &openpgp.FileHints{
|
|
FileName: plainMessageMetadata.Filename,
|
|
IsBinary: plainMessageMetadata.IsBinary,
|
|
ModTime: time.Unix(plainMessageMetadata.ModTime, 0),
|
|
}
|
|
|
|
plainMessageWriter, err = asymmetricEncryptStream(hints, keyPacketWriter, dataPacketWriter, keyRing, signKeyRing, config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return plainMessageWriter, nil
|
|
}
|
|
|
|
// EncryptSplitResult is used to wrap the encryption writecloser while storing the key packet.
|
|
type EncryptSplitResult struct {
|
|
isClosed bool
|
|
keyPacketBuf *bytes.Buffer
|
|
keyPacket []byte
|
|
plainMessageWriter WriteCloser // The writer to writer plaintext data in.
|
|
}
|
|
|
|
func (res *EncryptSplitResult) Write(b []byte) (n int, err error) {
|
|
return res.plainMessageWriter.Write(b)
|
|
}
|
|
|
|
func (res *EncryptSplitResult) Close() (err error) {
|
|
err = res.plainMessageWriter.Close()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
res.isClosed = true
|
|
res.keyPacket = res.keyPacketBuf.Bytes()
|
|
return nil
|
|
}
|
|
|
|
// GetKeyPacket returns the Public-Key Encrypted Session Key Packets (https://datatracker.ietf.org/doc/html/rfc4880#section-5.1).
|
|
// This can be retrieved only after the message has been fully written and the writer is closed.
|
|
func (res *EncryptSplitResult) GetKeyPacket() (keyPacket []byte, err error) {
|
|
if !res.isClosed {
|
|
return nil, errors.New("gopenpgp: can't access key packet until the message writer has been closed")
|
|
}
|
|
return res.keyPacket, nil
|
|
}
|
|
|
|
// EncryptSplitStream is used to encrypt data as a stream.
|
|
// It takes a writer for the Symmetrically Encrypted Data Packet
|
|
// (https://datatracker.ietf.org/doc/html/rfc4880#section-5.7)
|
|
// and returns a writer for the plaintext data and the key packet.
|
|
// If signKeyRing is not nil, it is used to do an embedded signature.
|
|
func (keyRing *KeyRing) EncryptSplitStream(
|
|
dataPacketWriter Writer,
|
|
plainMessageMetadata *PlainMessageMetadata,
|
|
signKeyRing *KeyRing,
|
|
) (*EncryptSplitResult, error) {
|
|
config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: getTimeGenerator()}
|
|
|
|
var keyPacketBuf bytes.Buffer
|
|
|
|
plainMessageWriter, err := keyRing.encryptStreamWithConfig(
|
|
config,
|
|
&keyPacketBuf,
|
|
dataPacketWriter,
|
|
plainMessageMetadata,
|
|
signKeyRing,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &EncryptSplitResult{
|
|
keyPacketBuf: &keyPacketBuf,
|
|
plainMessageWriter: plainMessageWriter,
|
|
}, nil
|
|
}
|
|
|
|
// EncryptSplitStreamWithCompression is used to encrypt data as a stream.
|
|
// It takes a writer for the Symmetrically Encrypted Data Packet
|
|
// (https://datatracker.ietf.org/doc/html/rfc4880#section-5.7)
|
|
// and returns a writer for the plaintext data and the key packet.
|
|
// If signKeyRing is not nil, it is used to do an embedded signature.
|
|
func (keyRing *KeyRing) EncryptSplitStreamWithCompression(
|
|
dataPacketWriter Writer,
|
|
plainMessageMetadata *PlainMessageMetadata,
|
|
signKeyRing *KeyRing,
|
|
) (*EncryptSplitResult, error) {
|
|
config := &packet.Config{
|
|
DefaultCipher: packet.CipherAES256,
|
|
Time: getTimeGenerator(),
|
|
DefaultCompressionAlgo: constants.DefaultCompression,
|
|
CompressionConfig: &packet.CompressionConfig{Level: constants.DefaultCompressionLevel},
|
|
}
|
|
|
|
var keyPacketBuf bytes.Buffer
|
|
|
|
plainMessageWriter, err := keyRing.encryptStreamWithConfig(
|
|
config,
|
|
&keyPacketBuf,
|
|
dataPacketWriter,
|
|
plainMessageMetadata,
|
|
signKeyRing,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &EncryptSplitResult{
|
|
keyPacketBuf: &keyPacketBuf,
|
|
plainMessageWriter: plainMessageWriter,
|
|
}, nil
|
|
}
|
|
|
|
// PlainMessageReader is used to wrap the data of the decrypted plain message.
|
|
// It can be used to read the decrypted data and verify the embedded signature.
|
|
type PlainMessageReader struct {
|
|
details *openpgp.MessageDetails
|
|
verifyKeyRing *KeyRing
|
|
verifyTime int64
|
|
readAll bool
|
|
}
|
|
|
|
// GetMetadata returns the metadata of the decrypted message.
|
|
func (msg *PlainMessageReader) GetMetadata() *PlainMessageMetadata {
|
|
return &PlainMessageMetadata{
|
|
Filename: msg.details.LiteralData.FileName,
|
|
IsBinary: msg.details.LiteralData.IsBinary,
|
|
ModTime: int64(msg.details.LiteralData.Time),
|
|
}
|
|
}
|
|
|
|
// Read is used to access the message decrypted data.
|
|
// Makes PlainMessageReader implement the Reader interface.
|
|
func (msg *PlainMessageReader) Read(b []byte) (n int, err error) {
|
|
n, err = msg.details.UnverifiedBody.Read(b)
|
|
if errors.Is(err, io.EOF) {
|
|
msg.readAll = true
|
|
}
|
|
return
|
|
}
|
|
|
|
// VerifySignature is used to verify that the signature is valid.
|
|
// This method needs to be called once all the data has been read.
|
|
// It will return an error if the signature is invalid
|
|
// or if the message hasn't been read entirely.
|
|
func (msg *PlainMessageReader) VerifySignature() (err error) {
|
|
if !msg.readAll {
|
|
return errors.New("gopenpgp: can't verify the signature until the message reader has been read entirely")
|
|
}
|
|
if msg.verifyKeyRing != nil {
|
|
processSignatureExpiration(msg.details, msg.verifyTime)
|
|
err = verifyDetailsSignature(msg.details, msg.verifyKeyRing)
|
|
} else {
|
|
err = errors.New("gopenpgp: no verify keyring was provided before decryption")
|
|
}
|
|
return
|
|
}
|
|
|
|
// DecryptStream is used to decrypt a pgp message as a Reader.
|
|
// It takes a reader for the message data
|
|
// and returns a PlainMessageReader for the plaintext data.
|
|
// If verifyKeyRing is not nil, PlainMessageReader.VerifySignature() will
|
|
// verify the embedded signature with the given key ring and verification time.
|
|
func (keyRing *KeyRing) DecryptStream(
|
|
message Reader,
|
|
verifyKeyRing *KeyRing,
|
|
verifyTime int64,
|
|
) (plainMessage *PlainMessageReader, err error) {
|
|
messageDetails, err := asymmetricDecryptStream(
|
|
message,
|
|
keyRing,
|
|
verifyKeyRing,
|
|
verifyTime,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &PlainMessageReader{
|
|
messageDetails,
|
|
verifyKeyRing,
|
|
verifyTime,
|
|
false,
|
|
}, err
|
|
}
|
|
|
|
// DecryptSplitStream is used to decrypt a split pgp message as a Reader.
|
|
// It takes a key packet and a reader for the data packet
|
|
// and returns a PlainMessageReader for the plaintext data.
|
|
// If verifyKeyRing is not nil, PlainMessageReader.VerifySignature() will
|
|
// verify the embedded signature with the given key ring and verification time.
|
|
func (keyRing *KeyRing) DecryptSplitStream(
|
|
keypacket []byte,
|
|
dataPacketReader Reader,
|
|
verifyKeyRing *KeyRing, verifyTime int64,
|
|
) (plainMessage *PlainMessageReader, err error) {
|
|
messageReader := io.MultiReader(
|
|
bytes.NewReader(keypacket),
|
|
dataPacketReader,
|
|
)
|
|
return keyRing.DecryptStream(
|
|
messageReader,
|
|
verifyKeyRing,
|
|
verifyTime,
|
|
)
|
|
}
|
|
|
|
// SignDetachedStream generates and returns a PGPSignature for a given message Reader.
|
|
func (keyRing *KeyRing) SignDetachedStream(message Reader) (*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, config); err != nil {
|
|
return nil, errors.Wrap(err, "gopenpgp: error in signing")
|
|
}
|
|
|
|
return NewPGPSignature(outBuf.Bytes()), nil
|
|
}
|
|
|
|
// VerifyDetachedStream verifies a message reader with a detached PGPSignature
|
|
// and returns a SignatureVerificationError if fails.
|
|
func (keyRing *KeyRing) VerifyDetachedStream(
|
|
message Reader,
|
|
signature *PGPSignature,
|
|
verifyTime int64,
|
|
) error {
|
|
_, err := verifySignature(
|
|
keyRing.entities,
|
|
message,
|
|
signature.GetBinary(),
|
|
verifyTime,
|
|
)
|
|
return err
|
|
}
|
|
|
|
// SignDetachedEncryptedStream generates and returns a PGPMessage
|
|
// containing an encrypted detached signature for a given message Reader.
|
|
func (keyRing *KeyRing) SignDetachedEncryptedStream(
|
|
message Reader,
|
|
encryptionKeyRing *KeyRing,
|
|
) (encryptedSignature *PGPMessage, err error) {
|
|
if encryptionKeyRing == nil {
|
|
return nil, errors.New("gopenpgp: no encryption key ring provided")
|
|
}
|
|
signature, err := keyRing.SignDetachedStream(message)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
plainMessage := NewPlainMessage(signature.GetBinary())
|
|
encryptedSignature, err = encryptionKeyRing.Encrypt(plainMessage, nil)
|
|
return
|
|
}
|
|
|
|
// VerifyDetachedEncryptedStream verifies a PlainMessage
|
|
// with a PGPMessage containing an encrypted detached signature
|
|
// and returns a SignatureVerificationError if fails.
|
|
func (keyRing *KeyRing) VerifyDetachedEncryptedStream(
|
|
message Reader,
|
|
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.VerifyDetachedStream(message, signature, verifyTime)
|
|
}
|