166 lines
4.7 KiB
Go
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)))
|
|
}
|