mirror of
https://github.com/rocky-linux/peridot.git
synced 2024-11-27 15:36:25 +00:00
584 lines
20 KiB
Go
584 lines
20 KiB
Go
// Copyright 2011 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package openpgp
|
|
|
|
import (
|
|
"crypto"
|
|
"hash"
|
|
"io"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/ProtonMail/go-crypto/openpgp/armor"
|
|
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
|
"github.com/ProtonMail/go-crypto/openpgp/internal/algorithm"
|
|
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
|
)
|
|
|
|
// DetachSign signs message with the private key from signer (which must
|
|
// already have been decrypted) and writes the signature to w.
|
|
// If config is nil, sensible defaults will be used.
|
|
func DetachSign(w io.Writer, signer *Entity, message io.Reader, config *packet.Config) error {
|
|
return detachSign(w, signer, message, packet.SigTypeBinary, config)
|
|
}
|
|
|
|
// ArmoredDetachSign signs message with the private key from signer (which
|
|
// must already have been decrypted) and writes an armored signature to w.
|
|
// If config is nil, sensible defaults will be used.
|
|
func ArmoredDetachSign(w io.Writer, signer *Entity, message io.Reader, config *packet.Config) (err error) {
|
|
return armoredDetachSign(w, signer, message, packet.SigTypeBinary, config)
|
|
}
|
|
|
|
// DetachSignText signs message (after canonicalising the line endings) with
|
|
// the private key from signer (which must already have been decrypted) and
|
|
// writes the signature to w.
|
|
// If config is nil, sensible defaults will be used.
|
|
func DetachSignText(w io.Writer, signer *Entity, message io.Reader, config *packet.Config) error {
|
|
return detachSign(w, signer, message, packet.SigTypeText, config)
|
|
}
|
|
|
|
// ArmoredDetachSignText signs message (after canonicalising the line endings)
|
|
// with the private key from signer (which must already have been decrypted)
|
|
// and writes an armored signature to w.
|
|
// If config is nil, sensible defaults will be used.
|
|
func ArmoredDetachSignText(w io.Writer, signer *Entity, message io.Reader, config *packet.Config) error {
|
|
return armoredDetachSign(w, signer, message, packet.SigTypeText, config)
|
|
}
|
|
|
|
func armoredDetachSign(w io.Writer, signer *Entity, message io.Reader, sigType packet.SignatureType, config *packet.Config) (err error) {
|
|
out, err := armor.Encode(w, SignatureType, nil)
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = detachSign(out, signer, message, sigType, config)
|
|
if err != nil {
|
|
return
|
|
}
|
|
return out.Close()
|
|
}
|
|
|
|
func detachSign(w io.Writer, signer *Entity, message io.Reader, sigType packet.SignatureType, config *packet.Config) (err error) {
|
|
signingKey, ok := signer.SigningKeyById(config.Now(), config.SigningKey())
|
|
if !ok {
|
|
return errors.InvalidArgumentError("no valid signing keys")
|
|
}
|
|
if signingKey.PrivateKey == nil {
|
|
return errors.InvalidArgumentError("signing key doesn't have a private key")
|
|
}
|
|
if signingKey.PrivateKey.Encrypted {
|
|
return errors.InvalidArgumentError("signing key is encrypted")
|
|
}
|
|
if _, ok := algorithm.HashToHashId(config.Hash()); !ok {
|
|
return errors.InvalidArgumentError("invalid hash function")
|
|
}
|
|
|
|
sig := createSignaturePacket(signingKey.PublicKey, sigType, config)
|
|
|
|
h, wrappedHash, err := hashForSignature(sig.Hash, sig.SigType)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if _, err = io.Copy(wrappedHash, message); err != nil {
|
|
return err
|
|
}
|
|
|
|
err = sig.Sign(h, signingKey.PrivateKey, config)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
return sig.Serialize(w)
|
|
}
|
|
|
|
// FileHints contains metadata about encrypted files. This metadata is, itself,
|
|
// encrypted.
|
|
type FileHints struct {
|
|
// IsBinary can be set to hint that the contents are binary data.
|
|
IsBinary bool
|
|
// FileName hints at the name of the file that should be written. It's
|
|
// truncated to 255 bytes if longer. It may be empty to suggest that the
|
|
// file should not be written to disk. It may be equal to "_CONSOLE" to
|
|
// suggest the data should not be written to disk.
|
|
FileName string
|
|
// ModTime contains the modification time of the file, or the zero time if not applicable.
|
|
ModTime time.Time
|
|
}
|
|
|
|
// SymmetricallyEncrypt acts like gpg -c: it encrypts a file with a passphrase.
|
|
// The resulting WriteCloser must be closed after the contents of the file have
|
|
// been written.
|
|
// If config is nil, sensible defaults will be used.
|
|
func SymmetricallyEncrypt(ciphertext io.Writer, passphrase []byte, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) {
|
|
if hints == nil {
|
|
hints = &FileHints{}
|
|
}
|
|
|
|
key, err := packet.SerializeSymmetricKeyEncrypted(ciphertext, passphrase, config)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
var w io.WriteCloser
|
|
cipherSuite := packet.CipherSuite{
|
|
Cipher: config.Cipher(),
|
|
Mode: config.AEAD().Mode(),
|
|
}
|
|
w, err = packet.SerializeSymmetricallyEncrypted(ciphertext, config.Cipher(), config.AEAD() != nil, cipherSuite, key, config)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
literalData := w
|
|
if algo := config.Compression(); algo != packet.CompressionNone {
|
|
var compConfig *packet.CompressionConfig
|
|
if config != nil {
|
|
compConfig = config.CompressionConfig
|
|
}
|
|
literalData, err = packet.SerializeCompressed(w, algo, compConfig)
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
var epochSeconds uint32
|
|
if !hints.ModTime.IsZero() {
|
|
epochSeconds = uint32(hints.ModTime.Unix())
|
|
}
|
|
return packet.SerializeLiteral(literalData, hints.IsBinary, hints.FileName, epochSeconds)
|
|
}
|
|
|
|
// intersectPreferences mutates and returns a prefix of a that contains only
|
|
// the values in the intersection of a and b. The order of a is preserved.
|
|
func intersectPreferences(a []uint8, b []uint8) (intersection []uint8) {
|
|
var j int
|
|
for _, v := range a {
|
|
for _, v2 := range b {
|
|
if v == v2 {
|
|
a[j] = v
|
|
j++
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
return a[:j]
|
|
}
|
|
|
|
// intersectPreferences mutates and returns a prefix of a that contains only
|
|
// the values in the intersection of a and b. The order of a is preserved.
|
|
func intersectCipherSuites(a [][2]uint8, b [][2]uint8) (intersection [][2]uint8) {
|
|
var j int
|
|
for _, v := range a {
|
|
for _, v2 := range b {
|
|
if v[0] == v2[0] && v[1] == v2[1] {
|
|
a[j] = v
|
|
j++
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
return a[:j]
|
|
}
|
|
|
|
func hashToHashId(h crypto.Hash) uint8 {
|
|
v, ok := algorithm.HashToHashId(h)
|
|
if !ok {
|
|
panic("tried to convert unknown hash")
|
|
}
|
|
return v
|
|
}
|
|
|
|
// EncryptText encrypts a message to a number of recipients and, optionally,
|
|
// signs it. Optional information is contained in 'hints', also encrypted, that
|
|
// aids the recipients in processing the message. The resulting WriteCloser
|
|
// must be closed after the contents of the file have been written. If config
|
|
// is nil, sensible defaults will be used. The signing is done in text mode.
|
|
func EncryptText(ciphertext io.Writer, to []*Entity, signed *Entity, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) {
|
|
return encrypt(ciphertext, ciphertext, to, signed, hints, packet.SigTypeText, config)
|
|
}
|
|
|
|
// Encrypt encrypts a message to a number of recipients and, optionally, signs
|
|
// it. hints contains optional information, that is also encrypted, that aids
|
|
// the recipients in processing the message. The resulting WriteCloser must
|
|
// be closed after the contents of the file have been written.
|
|
// If config is nil, sensible defaults will be used.
|
|
func Encrypt(ciphertext io.Writer, to []*Entity, signed *Entity, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) {
|
|
return encrypt(ciphertext, ciphertext, to, signed, hints, packet.SigTypeBinary, config)
|
|
}
|
|
|
|
// EncryptSplit encrypts a message to a number of recipients and, optionally, signs
|
|
// it. hints contains optional information, that is also encrypted, that aids
|
|
// the recipients in processing the message. The resulting WriteCloser must
|
|
// be closed after the contents of the file have been written.
|
|
// If config is nil, sensible defaults will be used.
|
|
func EncryptSplit(keyWriter io.Writer, dataWriter io.Writer, to []*Entity, signed *Entity, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) {
|
|
return encrypt(keyWriter, dataWriter, to, signed, hints, packet.SigTypeBinary, config)
|
|
}
|
|
|
|
// EncryptTextSplit encrypts a message to a number of recipients and, optionally, signs
|
|
// it. hints contains optional information, that is also encrypted, that aids
|
|
// the recipients in processing the message. The resulting WriteCloser must
|
|
// be closed after the contents of the file have been written.
|
|
// If config is nil, sensible defaults will be used.
|
|
func EncryptTextSplit(keyWriter io.Writer, dataWriter io.Writer, to []*Entity, signed *Entity, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) {
|
|
return encrypt(keyWriter, dataWriter, to, signed, hints, packet.SigTypeText, config)
|
|
}
|
|
|
|
// writeAndSign writes the data as a payload package and, optionally, signs
|
|
// it. hints contains optional information, that is also encrypted,
|
|
// that aids the recipients in processing the message. The resulting
|
|
// WriteCloser must be closed after the contents of the file have been
|
|
// written. If config is nil, sensible defaults will be used.
|
|
func writeAndSign(payload io.WriteCloser, candidateHashes []uint8, signed *Entity, hints *FileHints, sigType packet.SignatureType, config *packet.Config) (plaintext io.WriteCloser, err error) {
|
|
var signer *packet.PrivateKey
|
|
if signed != nil {
|
|
signKey, ok := signed.SigningKeyById(config.Now(), config.SigningKey())
|
|
if !ok {
|
|
return nil, errors.InvalidArgumentError("no valid signing keys")
|
|
}
|
|
signer = signKey.PrivateKey
|
|
if signer == nil {
|
|
return nil, errors.InvalidArgumentError("no private key in signing key")
|
|
}
|
|
if signer.Encrypted {
|
|
return nil, errors.InvalidArgumentError("signing key must be decrypted")
|
|
}
|
|
}
|
|
|
|
var hash crypto.Hash
|
|
for _, hashId := range candidateHashes {
|
|
if h, ok := algorithm.HashIdToHash(hashId); ok && h.Available() {
|
|
hash = h
|
|
break
|
|
}
|
|
}
|
|
|
|
// If the hash specified by config is a candidate, we'll use that.
|
|
if configuredHash := config.Hash(); configuredHash.Available() {
|
|
for _, hashId := range candidateHashes {
|
|
if h, ok := algorithm.HashIdToHash(hashId); ok && h == configuredHash {
|
|
hash = h
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if hash == 0 {
|
|
hashId := candidateHashes[0]
|
|
name, ok := algorithm.HashIdToString(hashId)
|
|
if !ok {
|
|
name = "#" + strconv.Itoa(int(hashId))
|
|
}
|
|
return nil, errors.InvalidArgumentError("cannot encrypt because no candidate hash functions are compiled in. (Wanted " + name + " in this case.)")
|
|
}
|
|
|
|
if signer != nil {
|
|
ops := &packet.OnePassSignature{
|
|
SigType: sigType,
|
|
Hash: hash,
|
|
PubKeyAlgo: signer.PubKeyAlgo,
|
|
KeyId: signer.KeyId,
|
|
IsLast: true,
|
|
}
|
|
if err := ops.Serialize(payload); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if hints == nil {
|
|
hints = &FileHints{}
|
|
}
|
|
|
|
w := payload
|
|
if signer != nil {
|
|
// If we need to write a signature packet after the literal
|
|
// data then we need to stop literalData from closing
|
|
// encryptedData.
|
|
w = noOpCloser{w}
|
|
|
|
}
|
|
var epochSeconds uint32
|
|
if !hints.ModTime.IsZero() {
|
|
epochSeconds = uint32(hints.ModTime.Unix())
|
|
}
|
|
literalData, err := packet.SerializeLiteral(w, hints.IsBinary, hints.FileName, epochSeconds)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if signer != nil {
|
|
h, wrappedHash, err := hashForSignature(hash, sigType)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
metadata := &packet.LiteralData{
|
|
Format: 't',
|
|
FileName: hints.FileName,
|
|
Time: epochSeconds,
|
|
}
|
|
if hints.IsBinary {
|
|
metadata.Format = 'b'
|
|
}
|
|
return signatureWriter{payload, literalData, hash, wrappedHash, h, signer, sigType, config, metadata}, nil
|
|
}
|
|
return literalData, nil
|
|
}
|
|
|
|
// encrypt encrypts a message to a number of recipients and, optionally, signs
|
|
// it. hints contains optional information, that is also encrypted, that aids
|
|
// the recipients in processing the message. The resulting WriteCloser must
|
|
// be closed after the contents of the file have been written.
|
|
// If config is nil, sensible defaults will be used.
|
|
func encrypt(keyWriter io.Writer, dataWriter io.Writer, to []*Entity, signed *Entity, hints *FileHints, sigType packet.SignatureType, config *packet.Config) (plaintext io.WriteCloser, err error) {
|
|
if len(to) == 0 {
|
|
return nil, errors.InvalidArgumentError("no encryption recipient provided")
|
|
}
|
|
|
|
// These are the possible ciphers that we'll use for the message.
|
|
candidateCiphers := []uint8{
|
|
uint8(packet.CipherAES256),
|
|
uint8(packet.CipherAES128),
|
|
}
|
|
|
|
// These are the possible hash functions that we'll use for the signature.
|
|
candidateHashes := []uint8{
|
|
hashToHashId(crypto.SHA256),
|
|
hashToHashId(crypto.SHA384),
|
|
hashToHashId(crypto.SHA512),
|
|
hashToHashId(crypto.SHA3_256),
|
|
hashToHashId(crypto.SHA3_512),
|
|
}
|
|
|
|
// Prefer GCM if everyone supports it
|
|
candidateCipherSuites := [][2]uint8{
|
|
{uint8(packet.CipherAES256), uint8(packet.AEADModeGCM)},
|
|
{uint8(packet.CipherAES256), uint8(packet.AEADModeEAX)},
|
|
{uint8(packet.CipherAES256), uint8(packet.AEADModeOCB)},
|
|
{uint8(packet.CipherAES128), uint8(packet.AEADModeGCM)},
|
|
{uint8(packet.CipherAES128), uint8(packet.AEADModeEAX)},
|
|
{uint8(packet.CipherAES128), uint8(packet.AEADModeOCB)},
|
|
}
|
|
|
|
candidateCompression := []uint8{
|
|
uint8(packet.CompressionNone),
|
|
uint8(packet.CompressionZIP),
|
|
uint8(packet.CompressionZLIB),
|
|
}
|
|
|
|
encryptKeys := make([]Key, len(to))
|
|
|
|
// AEAD is used only if config enables it and every key supports it
|
|
aeadSupported := config.AEAD() != nil
|
|
|
|
for i := range to {
|
|
var ok bool
|
|
encryptKeys[i], ok = to[i].EncryptionKey(config.Now())
|
|
if !ok {
|
|
return nil, errors.InvalidArgumentError("cannot encrypt a message to key id " + strconv.FormatUint(to[i].PrimaryKey.KeyId, 16) + " because it has no valid encryption keys")
|
|
}
|
|
|
|
sig := to[i].PrimaryIdentity().SelfSignature
|
|
if !sig.SEIPDv2 {
|
|
aeadSupported = false
|
|
}
|
|
|
|
candidateCiphers = intersectPreferences(candidateCiphers, sig.PreferredSymmetric)
|
|
candidateHashes = intersectPreferences(candidateHashes, sig.PreferredHash)
|
|
candidateCipherSuites = intersectCipherSuites(candidateCipherSuites, sig.PreferredCipherSuites)
|
|
candidateCompression = intersectPreferences(candidateCompression, sig.PreferredCompression)
|
|
}
|
|
|
|
// In the event that the intersection of supported algorithms is empty we use the ones
|
|
// labelled as MUST that every implementation supports.
|
|
if len(candidateCiphers) == 0 {
|
|
// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-9.3
|
|
candidateCiphers = []uint8{uint8(packet.CipherAES128)}
|
|
}
|
|
if len(candidateHashes) == 0 {
|
|
// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#hash-algos
|
|
candidateHashes = []uint8{hashToHashId(crypto.SHA256)}
|
|
}
|
|
if len(candidateCipherSuites) == 0 {
|
|
// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-9.6
|
|
candidateCipherSuites = [][2]uint8{{uint8(packet.CipherAES128), uint8(packet.AEADModeOCB)}}
|
|
}
|
|
|
|
cipher := packet.CipherFunction(candidateCiphers[0])
|
|
aeadCipherSuite := packet.CipherSuite{
|
|
Cipher: packet.CipherFunction(candidateCipherSuites[0][0]),
|
|
Mode: packet.AEADMode(candidateCipherSuites[0][1]),
|
|
}
|
|
|
|
// If the cipher specified by config is a candidate, we'll use that.
|
|
configuredCipher := config.Cipher()
|
|
for _, c := range candidateCiphers {
|
|
cipherFunc := packet.CipherFunction(c)
|
|
if cipherFunc == configuredCipher {
|
|
cipher = cipherFunc
|
|
break
|
|
}
|
|
}
|
|
|
|
symKey := make([]byte, cipher.KeySize())
|
|
if _, err := io.ReadFull(config.Random(), symKey); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, key := range encryptKeys {
|
|
if err := packet.SerializeEncryptedKey(keyWriter, key.PublicKey, cipher, symKey, config); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
var payload io.WriteCloser
|
|
payload, err = packet.SerializeSymmetricallyEncrypted(dataWriter, cipher, aeadSupported, aeadCipherSuite, symKey, config)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
payload, err = handleCompression(payload, candidateCompression, config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return writeAndSign(payload, candidateHashes, signed, hints, sigType, config)
|
|
}
|
|
|
|
// Sign signs a message. The resulting WriteCloser must be closed after the
|
|
// contents of the file have been written. hints contains optional information
|
|
// that aids the recipients in processing the message.
|
|
// If config is nil, sensible defaults will be used.
|
|
func Sign(output io.Writer, signed *Entity, hints *FileHints, config *packet.Config) (input io.WriteCloser, err error) {
|
|
if signed == nil {
|
|
return nil, errors.InvalidArgumentError("no signer provided")
|
|
}
|
|
|
|
// These are the possible hash functions that we'll use for the signature.
|
|
candidateHashes := []uint8{
|
|
hashToHashId(crypto.SHA256),
|
|
hashToHashId(crypto.SHA384),
|
|
hashToHashId(crypto.SHA512),
|
|
hashToHashId(crypto.SHA3_256),
|
|
hashToHashId(crypto.SHA3_512),
|
|
}
|
|
defaultHashes := candidateHashes[0:1]
|
|
preferredHashes := signed.PrimaryIdentity().SelfSignature.PreferredHash
|
|
if len(preferredHashes) == 0 {
|
|
preferredHashes = defaultHashes
|
|
}
|
|
candidateHashes = intersectPreferences(candidateHashes, preferredHashes)
|
|
if len(candidateHashes) == 0 {
|
|
return nil, errors.InvalidArgumentError("cannot sign because signing key shares no common algorithms with candidate hashes")
|
|
}
|
|
|
|
return writeAndSign(noOpCloser{output}, candidateHashes, signed, hints, packet.SigTypeBinary, config)
|
|
}
|
|
|
|
// signatureWriter hashes the contents of a message while passing it along to
|
|
// literalData. When closed, it closes literalData, writes a signature packet
|
|
// to encryptedData and then also closes encryptedData.
|
|
type signatureWriter struct {
|
|
encryptedData io.WriteCloser
|
|
literalData io.WriteCloser
|
|
hashType crypto.Hash
|
|
wrappedHash hash.Hash
|
|
h hash.Hash
|
|
signer *packet.PrivateKey
|
|
sigType packet.SignatureType
|
|
config *packet.Config
|
|
metadata *packet.LiteralData // V5 signatures protect document metadata
|
|
}
|
|
|
|
func (s signatureWriter) Write(data []byte) (int, error) {
|
|
s.wrappedHash.Write(data)
|
|
switch s.sigType {
|
|
case packet.SigTypeBinary:
|
|
return s.literalData.Write(data)
|
|
case packet.SigTypeText:
|
|
flag := 0
|
|
return writeCanonical(s.literalData, data, &flag)
|
|
}
|
|
return 0, errors.UnsupportedError("unsupported signature type: " + strconv.Itoa(int(s.sigType)))
|
|
}
|
|
|
|
func (s signatureWriter) Close() error {
|
|
sig := createSignaturePacket(&s.signer.PublicKey, s.sigType, s.config)
|
|
sig.Hash = s.hashType
|
|
sig.Metadata = s.metadata
|
|
|
|
if err := sig.Sign(s.h, s.signer, s.config); err != nil {
|
|
return err
|
|
}
|
|
if err := s.literalData.Close(); err != nil {
|
|
return err
|
|
}
|
|
if err := sig.Serialize(s.encryptedData); err != nil {
|
|
return err
|
|
}
|
|
return s.encryptedData.Close()
|
|
}
|
|
|
|
func createSignaturePacket(signer *packet.PublicKey, sigType packet.SignatureType, config *packet.Config) *packet.Signature {
|
|
sigLifetimeSecs := config.SigLifetime()
|
|
return &packet.Signature{
|
|
Version: signer.Version,
|
|
SigType: sigType,
|
|
PubKeyAlgo: signer.PubKeyAlgo,
|
|
Hash: config.Hash(),
|
|
CreationTime: config.Now(),
|
|
IssuerKeyId: &signer.KeyId,
|
|
IssuerFingerprint: signer.Fingerprint,
|
|
Notations: config.Notations(),
|
|
SigLifetimeSecs: &sigLifetimeSecs,
|
|
}
|
|
}
|
|
|
|
// noOpCloser is like an ioutil.NopCloser, but for an io.Writer.
|
|
// TODO: we have two of these in OpenPGP packages alone. This probably needs
|
|
// to be promoted somewhere more common.
|
|
type noOpCloser struct {
|
|
w io.Writer
|
|
}
|
|
|
|
func (c noOpCloser) Write(data []byte) (n int, err error) {
|
|
return c.w.Write(data)
|
|
}
|
|
|
|
func (c noOpCloser) Close() error {
|
|
return nil
|
|
}
|
|
|
|
func handleCompression(compressed io.WriteCloser, candidateCompression []uint8, config *packet.Config) (data io.WriteCloser, err error) {
|
|
data = compressed
|
|
confAlgo := config.Compression()
|
|
if confAlgo == packet.CompressionNone {
|
|
return
|
|
}
|
|
|
|
// Set algorithm labelled as MUST as fallback
|
|
// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-9.4
|
|
finalAlgo := packet.CompressionNone
|
|
// if compression specified by config available we will use it
|
|
for _, c := range candidateCompression {
|
|
if uint8(confAlgo) == c {
|
|
finalAlgo = confAlgo
|
|
break
|
|
}
|
|
}
|
|
|
|
if finalAlgo != packet.CompressionNone {
|
|
var compConfig *packet.CompressionConfig
|
|
if config != nil {
|
|
compConfig = config.CompressionConfig
|
|
}
|
|
data, err = packet.SerializeCompressed(compressed, finalAlgo, compConfig)
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
return data, nil
|
|
}
|