go_pgp/DeCryption.go
2024-05-10 22:24:14 +02:00

166 lines
4.7 KiB
Go

package go_pgp
import (
"bytes"
"crypto"
"errors"
"hash"
"io"
"strconv"
// required in order to use the crypto.SHA256- and -SHA512-Hashes
_ "crypto/sha256"
_ "crypto/sha512"
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/ProtonMail/go-crypto/openpgp/packet"
)
type FileHints struct {
openpgp.FileHints
}
type EntityList struct {
openpgp.EntityList
}
// EncryptData encrypts a given payload with the public key of the target entity and optionally signs the data with the private key of the source entity
func EncryptData(data []byte, source, target *Entity, hints *FileHints) (encryptedData []byte, err error) {
if nil == target {
err = ErrUndefinedPGPEntity
return
}
if len(data) == 0 {
return data, nil
}
out := new(bytes.Buffer)
for _, identity := range target.Identities {
if nil != identity && nil != identity.SelfSignature {
identity.SelfSignature.PreferredHash = []uint8{8, 9, 10} //cf. "golang.org/x/crypto/openpgp/s2k" -> s2k.HashIdToHash
}
}
if hints == nil {
hints = &FileHints{}
}
var writer io.WriteCloser
if source != nil {
writer, err = openpgp.Encrypt(out, []*openpgp.Entity{&target.Entity}, &source.Entity, &hints.FileHints, target.cfg)
} else {
writer, err = openpgp.Encrypt(out, []*openpgp.Entity{&target.Entity}, nil, &hints.FileHints, target.cfg)
}
if nil != err {
return
}
_, err = writer.Write(data)
if nil != err {
return
}
writer.Close()
encryptedData = out.Bytes()
return
}
// DecryptData decrypts a given payload with the private key of the given entity
func DecryptData(data []byte, entity *Entity, entityPassword *string) (decryptedData []byte, err error) {
return DecryptDataVerify(data, entity, nil, entityPassword)
}
// DecryptDataVerify decrypts a given payload with the private key of the given entity
func DecryptDataVerify(data []byte, entity *Entity, remoteKeys *EntityList, entityPassword *string) (decryptedData []byte, err error) {
if nil == entity {
return nil, ErrUndefinedPGPEntity
}
if len(data) == 0 {
return nil, ErrNoData
}
if nil != entityPassword && 0 < len(*entityPassword) {
passphraseByte := []byte(*entityPassword)
if entity.PrivateKey.Encrypted {
entity.PrivateKey.Decrypt(passphraseByte)
}
if nil != entity.Subkeys {
for _, subkey := range entity.Subkeys {
if subkey.PrivateKey.Encrypted {
subkey.PrivateKey.Decrypt(passphraseByte)
}
}
}
}
keyring := openpgp.EntityList{&entity.Entity}
if remoteKeys != nil {
if el := remoteKeys.EntityList; len(el) > 0 {
keyring = append(keyring, el...)
}
}
message, err := openpgp.ReadMessage(bytes.NewBuffer(data), keyring, nil, entity.cfg)
if nil != err {
return
}
unverifiedBody, bodyErr := io.ReadAll(message.UnverifiedBody)
if bodyErr == nil && remoteKeys != nil && len(remoteKeys.EntityList) > 0 && message.IsSigned {
var keys []openpgp.Key
var signatureType packet.SignatureType
var hashFunc crypto.Hash
packetType := 0
if nil != message.Signature {
if nil != message.Signature.IssuerKeyId {
keys = remoteKeys.KeysById(*message.Signature.IssuerKeyId)
hashFunc = message.Signature.Hash
signatureType = message.Signature.SigType
if 0 < len(keys) {
packetType = 1
}
}
}
if hashFunc == 0 && packetType == 0 && len(keys) == 0 {
if keys = remoteKeys.KeysById(message.SignedByKeyId); len(keys) > 0 {
return unverifiedBody, nil
}
}
h, wrappedHash, err := hashForSignature(hashFunc, signatureType)
if err != nil {
return nil, &PGPError{err}
}
if _, err := wrappedHash.Write(unverifiedBody); err != nil && err != io.EOF {
return nil, &PGPError{err}
}
for _, key := range keys {
switch packetType {
case 1:
err = key.PublicKey.VerifySignature(h, message.Signature)
default:
return nil, &PGPError{errors.New("bad signature")}
}
if err == nil {
return unverifiedBody, nil
}
}
return nil, &PGPError{err}
}
return unverifiedBody, bodyErr
}
// hashForSignature returns a pair of hashes that can be used to verify a
// signature. The signature may specify that the contents of the signed message
// should be preprocessed (i.e. to normalize line endings). Thus this function
// returns two hashes. The second should be used to hash the message itself and
// performs any needed preprocessing.
func hashForSignature(hashID crypto.Hash, sigType packet.SignatureType) (hash.Hash, hash.Hash, error) {
if !hashID.Available() {
return nil, nil, errors.New("hash not available: " + strconv.Itoa(int(hashID)))
}
h := hashID.New()
switch sigType {
case packet.SigTypeBinary:
return h, h, nil
case packet.SigTypeText:
return h, openpgp.NewCanonicalTextHash(h), nil
}
return nil, nil, errors.New("unsupported signature type: " + strconv.Itoa(int(sigType)))
}