Instead of parsing the signature packets manually, use the signature packet returned by VerifyDetachedSignatureAndHash to get the signature creation time.
276 lines
8.6 KiB
Go
276 lines
8.6 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
|
|
if message.IsBinary() {
|
|
err = openpgp.DetachSign(&outBuf, signEntity, message.NewReader(), config)
|
|
} else {
|
|
err = openpgp.DetachSignText(&outBuf, signEntity, message.NewReader(), config)
|
|
}
|
|
if 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 {
|
|
_, err := verifySignature(
|
|
keyRing.entities,
|
|
message.NewReader(),
|
|
signature.GetBinary(),
|
|
verifyTime,
|
|
)
|
|
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,
|
|
)
|
|
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,
|
|
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
|
|
}
|