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))) }