2019-06-03 17:00:01 +02:00
|
|
|
package crypto
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"crypto"
|
|
|
|
|
"errors"
|
|
|
|
|
"io"
|
|
|
|
|
"io/ioutil"
|
|
|
|
|
"math"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"golang.org/x/crypto/openpgp"
|
|
|
|
|
pgpErrors "golang.org/x/crypto/openpgp/errors"
|
|
|
|
|
"golang.org/x/crypto/openpgp/packet"
|
|
|
|
|
|
|
|
|
|
"github.com/ProtonMail/gopenpgp/constants"
|
|
|
|
|
"github.com/ProtonMail/gopenpgp/internal"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// 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) {
|
|
|
|
|
encrypted, err := asymmetricEncrypt(message.GetBinary(), keyRing, privateKey, true)
|
|
|
|
|
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)
|
|
|
|
|
func (keyRing *KeyRing) Decrypt(
|
|
|
|
|
message *PGPMessage, verifyKey *KeyRing, verifyTime int64,
|
|
|
|
|
) (*PlainMessage, *Verification, error) {
|
|
|
|
|
decrypted, verifyStatus, err := asymmetricDecrypt(message.NewReader(), keyRing, verifyKey, verifyTime)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NewPlainMessage(decrypted), newVerification(verifyStatus), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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: pgp.getTimeGenerator()}
|
|
|
|
|
var outBuf bytes.Buffer
|
|
|
|
|
//sign bin
|
|
|
|
|
if err := openpgp.DetachSign(&outBuf, signEntity, message.NewReader(), config); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NewPGPSignature(outBuf.Bytes()), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// VerifyDetached verifies a PlainMessage with embedded a PGPSignature
|
|
|
|
|
// and returns a Verification with the filled Verified field.
|
|
|
|
|
func (keyRing *KeyRing) VerifyDetached(
|
|
|
|
|
message *PlainMessage, signature *PGPSignature, verifyTime int64,
|
|
|
|
|
) (*Verification, error) {
|
|
|
|
|
var err error
|
|
|
|
|
verifyVal, err := verifySignature(
|
|
|
|
|
keyRing.GetEntities(),
|
|
|
|
|
message.NewReader(),
|
|
|
|
|
signature.GetBinary(),
|
|
|
|
|
verifyTime,
|
|
|
|
|
)
|
|
|
|
|
return newVerification(verifyVal), err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ------ INTERNAL FUNCTIONS -------
|
|
|
|
|
|
|
|
|
|
// Core for encryption+signature functions
|
|
|
|
|
func asymmetricEncrypt(data []byte, publicKey *KeyRing, privateKey *KeyRing, isBinary bool) ([]byte, error) {
|
|
|
|
|
var outBuf bytes.Buffer
|
|
|
|
|
var encryptWriter io.WriteCloser
|
|
|
|
|
var signEntity *openpgp.Entity
|
|
|
|
|
var err error
|
|
|
|
|
|
|
|
|
|
if privateKey != nil && len(privateKey.entities) > 0 {
|
|
|
|
|
var err error
|
|
|
|
|
signEntity, err = privateKey.GetSigningEntity()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: pgp.getTimeGenerator()}
|
|
|
|
|
|
|
|
|
|
hints := &openpgp.FileHints{
|
|
|
|
|
IsBinary: isBinary,
|
|
|
|
|
FileName: "",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if isBinary {
|
|
|
|
|
encryptWriter, err = openpgp.Encrypt(&outBuf, publicKey.entities, signEntity, hints, config)
|
|
|
|
|
} else {
|
|
|
|
|
encryptWriter, err = openpgp.EncryptText(&outBuf, publicKey.entities, signEntity, hints, config)
|
|
|
|
|
}
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, err = encryptWriter.Write(data)
|
|
|
|
|
encryptWriter.Close()
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return outBuf.Bytes(), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Core for decryption+verification functions
|
|
|
|
|
func asymmetricDecrypt(
|
|
|
|
|
encryptedIO io.Reader, privateKey *KeyRing, verifyKey *KeyRing, verifyTime int64,
|
|
|
|
|
) (plaintext []byte, verified int, err error) {
|
|
|
|
|
privKeyEntries := privateKey.GetEntities()
|
|
|
|
|
var additionalEntries openpgp.EntityList
|
|
|
|
|
|
|
|
|
|
if verifyKey != nil {
|
|
|
|
|
additionalEntries = verifyKey.GetEntities()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if additionalEntries != nil {
|
|
|
|
|
privKeyEntries = append(privKeyEntries, additionalEntries...)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
config := &packet.Config{Time: pgp.getTimeGenerator()}
|
|
|
|
|
|
|
|
|
|
messageDetails, err := openpgp.ReadMessage(encryptedIO, privKeyEntries, nil, config)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, constants.SIGNATURE_NOT_SIGNED, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
body, err := ioutil.ReadAll(messageDetails.UnverifiedBody)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, constants.SIGNATURE_NOT_SIGNED, err
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-04 18:10:31 +02:00
|
|
|
if verifyKey != nil {
|
|
|
|
|
processSignatureExpiration(messageDetails, verifyTime)
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-03 17:00:01 +02:00
|
|
|
if verifyKey != nil {
|
|
|
|
|
verifyStatus, verifyError := verifyDetailsSignature(messageDetails, verifyKey)
|
|
|
|
|
|
|
|
|
|
if verifyStatus == constants.SIGNATURE_FAILED {
|
|
|
|
|
return nil, verifyStatus, errors.New(verifyError)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return body, verifyStatus, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return body, constants.SIGNATURE_NOT_SIGNED, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// processSignatureExpiration handles signature time verification manually, so we can add a margin to the
|
|
|
|
|
// creationTime check.
|
|
|
|
|
func processSignatureExpiration(md *openpgp.MessageDetails, verifyTime int64) {
|
|
|
|
|
if md.SignatureError == pgpErrors.ErrSignatureExpired {
|
|
|
|
|
if verifyTime > 0 {
|
|
|
|
|
created := md.Signature.CreationTime.Unix()
|
|
|
|
|
expires := int64(math.MaxInt64)
|
|
|
|
|
if md.Signature.SigLifetimeSecs != nil {
|
|
|
|
|
expires = int64(*md.Signature.SigLifetimeSecs) + created
|
|
|
|
|
}
|
|
|
|
|
if created-internal.CreationTimeOffset <= verifyTime && verifyTime <= expires {
|
|
|
|
|
md.SignatureError = nil
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// verifyTime = 0: time check disabled, everything is okay
|
|
|
|
|
md.SignatureError = nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify signature from message details
|
|
|
|
|
func verifyDetailsSignature(md *openpgp.MessageDetails, verifierKey *KeyRing) (int, string) {
|
|
|
|
|
if md.IsSigned {
|
|
|
|
|
if md.SignedBy != nil {
|
|
|
|
|
if len(verifierKey.entities) > 0 {
|
|
|
|
|
matches := verifierKey.entities.KeysById(md.SignedByKeyId)
|
|
|
|
|
if len(matches) > 0 {
|
|
|
|
|
if md.SignatureError == nil {
|
|
|
|
|
return constants.SIGNATURE_OK, ""
|
|
|
|
|
}
|
|
|
|
|
return constants.SIGNATURE_FAILED, md.SignatureError.Error()
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
return constants.SIGNATURE_NO_VERIFIER, ""
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
return constants.SIGNATURE_NO_VERIFIER, ""
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return constants.SIGNATURE_NOT_SIGNED, ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// verifySignature verifies if a signature is valid with the entity list
|
|
|
|
|
func verifySignature(
|
|
|
|
|
pubKeyEntries openpgp.EntityList, origText io.Reader, signature []byte, verifyTime int64) (int, error) {
|
|
|
|
|
config := &packet.Config{}
|
|
|
|
|
if verifyTime == 0 {
|
|
|
|
|
config.Time = func() time.Time {
|
|
|
|
|
return time.Unix(0, 0)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
config.Time = func() time.Time {
|
|
|
|
|
return time.Unix(verifyTime+internal.CreationTimeOffset, 0)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
signatureReader := bytes.NewReader(signature)
|
|
|
|
|
|
|
|
|
|
signer, err := openpgp.CheckDetachedSignature(pubKeyEntries, origText, signatureReader, config)
|
|
|
|
|
|
|
|
|
|
if err == pgpErrors.ErrSignatureExpired && signer != nil {
|
|
|
|
|
if verifyTime > 0 { // if verifyTime = 0: time check disabled, everything is okay
|
|
|
|
|
// Maybe the creation time offset pushed it over the edge
|
|
|
|
|
// Retry with the actual verification time
|
|
|
|
|
config.Time = func() time.Time {
|
|
|
|
|
return time.Unix(verifyTime, 0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, err = signatureReader.Seek(0, io.SeekStart)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return constants.SIGNATURE_FAILED, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
signer, err = openpgp.CheckDetachedSignature(pubKeyEntries, origText, signatureReader, config)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return constants.SIGNATURE_FAILED, err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if signer == nil {
|
|
|
|
|
return constants.SIGNATURE_FAILED, errors.New("gopenpgp: signer is empty")
|
|
|
|
|
}
|
|
|
|
|
// if signer.PrimaryKey.KeyId != signed.PrimaryKey.KeyId {
|
|
|
|
|
// // t.Errorf("wrong signer got:%x want:%x", signer.PrimaryKey.KeyId, 0)
|
|
|
|
|
// return false, errors.New("signer is nil")
|
|
|
|
|
// }
|
|
|
|
|
return constants.SIGNATURE_OK, nil
|
|
|
|
|
}
|