// Copyright Martin Dosch.
// Use of this source code is governed by the BSD-2-clause
// license that can be found in the LICENSE file.

// TODO: Add more logging
// TODO: Deduplicate code

package main

import (
	"encoding/base64"
	"fmt"
	"io"
	"log/slog"
	"os"
	"runtime"
	"strings"
	"time"

	"github.com/ProtonMail/gopenpgp/v3/crypto" // MIT License
	"github.com/beevik/etree"                  // BSD-2-clause
	"github.com/xmppo/go-xmpp"                 // BSD-3-Clause
)

func oxDeleteNodes(jid string, client *xmpp.Client, iqc chan xmpp.IQ, disc chan xmpp.DiscoItems) error {
	discoItems, err := getDiscoItems(client, disc, jid)
	if err != nil {
		return fmt.Errorf("ox: delete nodes: failed to create node list request %w", err)
	}
	slog.Info("ox: parsing reply to IQ")
	for _, item := range discoItems.Items {
		if !strings.Contains(item.Node, nsOx) {
			continue
		}
		deleteNodeRequest := etree.NewDocument()
		deleteNodeRequest.WriteSettings.AttrSingleQuote = true
		pubsub := deleteNodeRequest.CreateElement("pubsub")
		pubsub.CreateAttr("xmlns", nsPubsubOwner)
		del := pubsub.CreateElement("delete")
		del.CreateAttr("node", item.Node)
		dnr, err := deleteNodeRequest.WriteToString()
		if err != nil {
			continue
		}
		slog.Info("ox: sending IQ to list pep nodes", "node", item.Node)
		_, err = sendIQ(client, iqc, jid, "set", dnr)
		if err != nil {
			continue
		}
	}
	return nil
}

func oxDecrypt(m xmpp.Chat, client *xmpp.Client, iqc chan xmpp.IQ, drc chan xmpp.DiscoResult,
	user string, oxPrivKey *crypto.Key, oxTrustMode string,
) (string, time.Time, error) {
	var cryptMsgByte []byte
	var senderKeyRing *crypto.KeyRing
	var err error
	sender := strings.Split(m.Remote, "/")[0]
	for _, r := range m.OtherElem {
		if r.XMLName.Space == nsOx {
			cryptMsgByte, err = base64.StdEncoding.DecodeString(r.InnerXML)
			if err != nil {
				return strError, time.Now(), err
			}
			break
		}
	}
	oxMsg := crypto.NewPGPMessage(cryptMsgByte)
	keyRing, err := crypto.NewKeyRing(oxPrivKey)
	if err != nil {
		return strError, time.Now(), err
	}
	slog.Info("ox: decrypting: getting senders key", "sender", sender)
	if m.Type == "groupchat" {
		senderKeyRing, err = oxGetPublicKeyRingChatroom(client, iqc, drc, sender, oxTrustMode)
		if err != nil {
			return strError, time.Now(), err
		}
	} else {
		senderKeyRing, err = oxGetPublicKeyRing(client, iqc, sender, oxTrustMode)
		if err != nil {
			return strError, time.Now(), err
		}
	}
	slog.Info("ox: decrypting message")
	pgpDecrypt, err := crypto.PGP().Decryption().
		DecryptionKeys(keyRing).
		VerificationKeys(senderKeyRing).
		New()
	if err != nil {
		return strError, time.Now(), err
	}
	decryptMsg, err := pgpDecrypt.Decrypt(oxMsg.Bytes(), crypto.Bytes)
	if err != nil {
		return strError, time.Now(), err
	}
	// Remove invalid code points.
	slog.Info("ox: removing invalid code points from decrypted message")
	message := validUTF8(decryptMsg.String())
	doc := etree.NewDocument()
	err = doc.ReadFromString(message)
	if err != nil {
		return strError, time.Now(), err
	}
	slog.Info("ox: decrypting: checking signcrypt element")
	signcrypt := doc.SelectElement("signcrypt")
	if signcrypt == nil {
		return strError, time.Now(), fmt.Errorf("ox: no signcrypt element")
	}
	slog.Info("ox: decrypting: checking to element")
	to := signcrypt.SelectElement("to")
	if to == nil {
		return strError, time.Now(), fmt.Errorf("ox: no to element")
	}
	slog.Info("ox: decrypting: checking jid attribute")
	jid := to.SelectAttr("jid")
	if jid == nil {
		return strError, time.Now(), fmt.Errorf("ox: no jid attribute")
	}
	jidAttrib := strings.Split(jid.Value, "/")[0]
	slog.Info("ox: decrypting: comparing jid attribute with local user",
		"jid-attribute", jidAttrib, "user", user)
	if jidAttrib != user && m.Type != "groupchat" {
		return strError, time.Now(), fmt.Errorf("ox: encrypted for wrong user")
	}
	slog.Info("ox: decrypting: checking time element")
	timestamp := signcrypt.SelectElement("time")
	if timestamp == nil {
		return strError, time.Now(), fmt.Errorf("ox: no time element")
	}
	slog.Info("ox: decrypting: checking stamp attribute")
	stamp := timestamp.SelectAttr("stamp")
	if stamp == nil {
		return strError, time.Now(), fmt.Errorf("ox: no stamp attribute")
	}
	slog.Info("ox: decrypting: parsing time stamp")
	msgStamp, err := time.Parse("2006-01-02T15:04:05Z0700", stamp.Value)
	if err != nil {
		return strError, time.Now(), err
	}
	slog.Info("ox: decrypting: checking payload element")
	payload := signcrypt.SelectElement("payload")
	if payload == nil {
		return strError, time.Now(), fmt.Errorf("ox: no payload element")
	}
	slog.Info("ox: decrypting: checking body element")
	body := payload.SelectElement("body")
	if body == nil {
		return "", time.Now(), nil
	}
	slog.Info("ox: decrypting: returning body")
	return body.Text(), msgStamp, nil
}

func isOxMsg(m xmpp.Chat) bool {
	slog.Info("ox: checking for ox message")
	for _, r := range m.OtherElem {
		slog.Info("ox: checking for ox message: comparing", "XMLNameSpace", r.XMLName.Space,
			"OXNameSpace", nsOx)
		if r.XMLName.Space == nsOx {
			slog.Info("ox: checking for ox message:", "result", true)
			return true
		}
	}
	slog.Info("ox: checking for ox message:", "result", false)
	return false
}

func oxImportPrivKey(jid string, privKeyLocation string, client *xmpp.Client, iqc chan xmpp.IQ,
	oxTrustMode string,
) error {
	xmppURI := "xmpp:" + jid
	slog.Info("ox: importing private key:", "xmpp-uri", xmppURI)
	slog.Info("ox: importing private key:", "location", privKeyLocation)
	buffer, err := readFile(privKeyLocation)
	if err != nil {
		return err
	}
	slog.Info("ox: importing private key: reading key")
	key, err := crypto.NewKey(buffer.Bytes())
	if err != nil {
		slog.Info("ox: importing private key: reading key failed: checking if armored")
		key, err = crypto.NewKeyFromArmored(buffer.String())
		if err != nil {
			keyDecoded, err := base64.StdEncoding.DecodeString(buffer.String())
			slog.Info("ox: importing private key: reading key failed: checking if base64 encoded")
			if err != nil {
				return fmt.Errorf("ox: import privkey: failed to import private key: %w", err)
			}
			slog.Info("ox: importing private key: reading key")
			key, err = crypto.NewKey(keyDecoded)
			if err != nil {
				return fmt.Errorf("ox: import privkey: failed to import private key: %w", err)
			}
		}
	}
	slog.Info("ox: importing private key: reading key: success")
	entity := key.GetEntity()
	slog.Info("ox: importing private key:", "entity", entity)
	if entity.Identities[xmppURI] == nil {
		return fmt.Errorf("ox: key identity is not %s", xmppURI)
	}
	slog.Info("ox: importing private key: deriving public key")
	pk, err := key.GetPublicKey()
	if err != nil {
		return fmt.Errorf("ox: import privkey: failed to get public key associated to private key: %w", err)
	}
	pubKey, err := crypto.NewKey(pk)
	if err != nil {
		return fmt.Errorf("ox: import privkey: failed to get public key associated to private key: %w", err)
	}
	fingerprint := strings.ToUpper(pubKey.GetFingerprint())
	slog.Info("ox: importing private key:", "fingerprint", fingerprint)
	slog.Info("ox: importing private key: checking if public key is already published")
	_, err = oxRecvPublicKeys(client, iqc, jid, fingerprint)
	if err != nil {
		slog.Info("ox: importing private key: publishing public key")
		err = oxPublishPubKey(jid, client, iqc, pubKey)
		if err != nil {
			return fmt.Errorf("ox: import privkey: failed to publish public key: %w", err)
		}
	}
	location, err := oxGetPrivKeyLoc(jid)
	if err != nil {
		return fmt.Errorf("ox: import privkey: failed to determine private key location: %w", err)
	}
	slog.Info("ox: importing private key: getting private key location", "jid", jid, "location", location)
	slog.Info("ox: importing private key: serializing key")
	keySerialized, err := key.Serialize()
	if err != nil {
		return fmt.Errorf("ox: import privkey: failed to serialize private key: %w", err)
	}
	slog.Info("ox: importing private key: storing key:", "location", location)
	err = oxStoreKey(location,
		base64.StdEncoding.EncodeToString(keySerialized))
	if err != nil {
		return fmt.Errorf("ox: import privkey: failed to store private key: %w", err)
	}
	slog.Info("ox: importing private key: requesting public key ring")
	pubKeyRing, err := oxGetPublicKeyRing(client, iqc, jid, oxTrustMode)
	if err == nil {
		slog.Info("ox: importing private key: checking if public key ring contains", "fingerprint", fingerprint)
		pubKeys := pubKeyRing.GetKeys()
		for _, r := range pubKeys {
			fp := strings.ToUpper(r.GetFingerprint())
			slog.Info("ox: importing private key: checking", "fingerprint", fp)
			if fp == fingerprint {
				slog.Info("ox: importing private key: checking if public key ring contains fingerprint: success")
				return nil
			}
		}
	}
	slog.Info("ox: importing private key: publishing public key")
	err = oxPublishPubKey(jid, client, iqc, pubKey)
	if err != nil {
		return fmt.Errorf("ox: import privkey: failed to publish public key: %w", err)
	}
	return nil
}

func oxPublishPubKey(jid string, client *xmpp.Client, iqc chan xmpp.IQ, pubKey *crypto.Key) error {
	keyCreated := time.Now().UTC().Format("2006-01-02T15:04:05Z")
	fingerprint := strings.ToUpper(pubKey.GetFingerprint())
	slog.Info("ox: publishing public key:", "fingerprint", fingerprint, "created", keyCreated)
	slog.Info("ox: publishing public key: serializing public key")
	keySerialized, err := pubKey.Serialize()
	if err != nil {
		return fmt.Errorf("ox: publish pubkey: failed to serialize pubkey: %w", err)
	}
	slog.Info("ox: publishing public key: base64 encoding public key")
	pubKeyBase64 := base64.StdEncoding.EncodeToString(keySerialized)
	root := etree.NewDocument()
	root.WriteSettings.AttrSingleQuote = true
	pubsub := root.CreateElement("pubsub")
	pubsub.CreateAttr("xmlns", nsPubsub)
	publish := pubsub.CreateElement("publish")
	publish.CreateAttr("node", nsOxPubKeys+":"+fingerprint)
	item := publish.CreateElement("item")
	item.CreateAttr("id", keyCreated)
	pubkey := item.CreateElement("pubkey")
	pubkey.CreateAttr("xmlns", nsOx)
	data := pubkey.CreateElement("data")
	data.CreateText(pubKeyBase64)
	publishoptions := pubsub.CreateElement("publish-options")
	x := publishoptions.CreateElement("x")
	x.CreateAttr("xmlns", nsJabberData)
	x.CreateAttr("type", "submit")
	field := x.CreateElement("field")
	field.CreateAttr("var", "FORM_TYPE")
	field.CreateAttr("type", "hidden")
	value := field.CreateElement("value")
	value.CreateText(pubsubPubOptions)
	field = x.CreateElement("field")
	field.CreateAttr("var", "pubsub#access_model")
	value = field.CreateElement("value")
	value.CreateText("open")
	xmlstring, err := root.WriteToString()
	if err != nil {
		return fmt.Errorf("ox: publish pubkey: failed to create publish public key iq xml: %w", err)
	}
	slog.Info("ox: publishing public key: sending IQ")
	iqReply, err := sendIQ(client, iqc, jid, "set", xmlstring)
	if err != nil {
		return fmt.Errorf("ox: publish pubkey: iq failure publishing public key: %w", err)
	}
	if iqReply.Type != strResult {
		return fmt.Errorf("ox: error while publishing public key")
	}
	slog.Info("ox: publishing public key: received result")
	slog.Info("ox: publishing public key: receiving own public key from pubsub")
	ownPubKeyRingFromPubsub, err := oxRecvPublicKeys(client, iqc, jid, fingerprint)
	if err != nil {
		return fmt.Errorf("ox: couldn't successfully verify public key upload")
	}
	ownPubKeyFromPubsub := ownPubKeyRingFromPubsub.GetKeys()[0]
	slog.Info("ox: publishing public key: serializing own public key received from pubsub")
	ownPubKeyFromPubsubSerialized, err := ownPubKeyFromPubsub.Serialize()
	if err != nil {
		return fmt.Errorf("ox: couldn't successfully verify public key upload")
	}
	slog.Info("ox: publishing public key: base64 encoding own public key received from pubsub")
	psPubKeyBase64 := base64.StdEncoding.EncodeToString(ownPubKeyFromPubsubSerialized)
	slog.Info("ox: publishing public key: comparing local and pubsub public key:", "local", pubKeyBase64, "pubsub", psPubKeyBase64)
	if pubKeyBase64 != psPubKeyBase64 {
		return fmt.Errorf("ox: couldn't successfully verify public key upload")
	}
	slog.Info("ox: publishing public key: comparing local and pubsub public key: success")
	root = etree.NewDocument()
	root.WriteSettings.AttrSingleQuote = true
	pubsub = root.CreateElement("pubsub")
	pubsub.CreateAttr("xmlns", nsPubsub)
	publish = pubsub.CreateElement("publish")
	publish.CreateAttr("node", nsOxPubKeys)
	item = publish.CreateElement("item")
	pubkeyslist := item.CreateElement("public-keys-list")
	pubkeyslist.CreateAttr("xmlns", nsOx)
	pubkeymeta := pubkeyslist.CreateElement("pubkey-metadata")
	pubkeymeta.CreateAttr("v4-fingerprint", fingerprint)
	pubkeymeta.CreateAttr("date", keyCreated)
	publishoptions = pubsub.CreateElement("publish-options")
	x = publishoptions.CreateElement("x")
	x.CreateAttr("xmlns", nsJabberData)
	x.CreateAttr("type", "submit")
	field = x.CreateElement("field")
	field.CreateAttr("var", "FORM_TYPE")
	field.CreateAttr("type", "hidden")
	value = field.CreateElement("value")
	value.CreateText(pubsubPubOptions)
	field = x.CreateElement("field")
	field.CreateAttr("var", "pubsub#access_model")
	value = field.CreateElement("value")
	value.CreateText("open")
	xmlstring, err = root.WriteToString()
	if err != nil {
		return fmt.Errorf("ox: publish pubkey: failed to create xml for iq to publish public key list: %w", err)
	}
	slog.Info("ox: publishing public key: sending IQ to publish key list to pubsub")
	iqReply, err = sendIQ(client, iqc, jid, "set", xmlstring)
	if err != nil {
		return fmt.Errorf("ox: publish pubkey: iq failure publishing public key list: %w", err)
	}
	if iqReply.Type != strResult {
		return fmt.Errorf("ox: couldn't publish public key list")
	}
	slog.Info("ox: publishing public key: received result")
	return nil
}

func oxGetPrivKeyLoc(jid string) (string, error) {
	dataDir, err := getDataPath(fsFriendlyJid(jid), true)
	slog.Info("ox: get private key location: trying", "location", dataDir)
	if err != nil {
		return strError, fmt.Errorf("ox: get privkey location: %w", err)
	}
	oldDataDir, err := getDataPath("oxprivkeys/", false)
	slog.Info("ox: get private key location: trying", "location", oldDataDir)
	if err != nil {
		return strError, fmt.Errorf("ox: get privkey location: %w", err)
	}
	// TODO: Remove handling of oldDataFile in a later version when it's very likely that there are no
	// more versions in use using the oldDataFile (<0.8.3).
	oldDataFile := oldDataDir + base64.StdEncoding.EncodeToString([]byte(jid))
	oldDataFile2 := oldDataDir + strings.ReplaceAll(jid, "@", "_at_")
	oldDataFile3 := oldDataDir + fsFriendlyJid(jid)
	dataFile := dataDir + "oxprivkey"
	if _, err := os.Stat(oldDataFile); err == nil {
		err := os.Rename(oldDataFile, dataFile)
		slog.Info("ox: get private key location: moving file to new location", "old", oldDataFile, "new", dataFile)
		if err != nil {
			return dataFile, err
		}
	}
	if _, err := os.Stat(oldDataFile2); err == nil {
		slog.Info("ox: get private key location: moving file to new location", "old", oldDataFile2, "new", dataFile)
		err := os.Rename(oldDataFile2, dataFile)
		if err != nil {
			return dataFile, err
		}
	}
	if _, err := os.Stat(oldDataFile3); err == nil {
		slog.Info("ox: get private key location: moving file to new location", "old", oldDataFile3, "new", dataFile)
		err := os.Rename(oldDataFile3, dataFile)
		if err != nil {
			return dataFile, err
		}
	}
	// #nosec G304 -- old data dir comes from the local system, it's no untrusted input from the network
	dir, err := os.Open(oldDataDir)
	if err == nil {
		slog.Info("ox: get private key location: entering old data", "folder", oldDataDir)
		slog.Info("ox: get private key location: checking if directory is empty")
		_, err = dir.ReadDir(1)
		// Delete old data dir if empty.
		if err == io.EOF {
			slog.Info("ox: get private key location: trying to delete empty", "folder", oldDataDir)
			err = os.Remove(oldDataDir)
			if err != nil {
				fmt.Printf("oxGetPrivKeyLoc: failed to delete old private key location: %v\n", err)
			}
		}
	}
	return dataFile, nil
}

func oxGetPubKeyLoc(jid string) (string, error) {
	dataDir, err := getDataPath("oxpubkeys/", true)
	slog.Info("ox: getting public key", "location", dataDir)
	if err != nil {
		return strError, fmt.Errorf("ox: get pubkey location: %w", err)
	}
	dataFile := dataDir + fsFriendlyJid(jid)
	slog.Info("ox: getting public key location", "file", dataFile)
	return dataFile, nil
}

func oxGetPrivKey(jid string, passphrase string) (*crypto.Key, error) {
	dataFile, err := oxGetPrivKeyLoc(jid)
	if err != nil {
		return nil, fmt.Errorf("ox: get privkey: failed to get private key location: %w", err)
	}
	slog.Info("ox: getting private key:", "file", dataFile)
	keyBuffer, err := readFile(dataFile)
	if err != nil {
		return nil, fmt.Errorf("ox: get privkey: failed to read private key: %w", err)
	}
	keyString := keyBuffer.String()
	slog.Info("ox: getting private key: base64 decoding key")
	decodedPrivKey, err := base64.StdEncoding.DecodeString(keyString)
	if err != nil {
		return nil, fmt.Errorf("ox: get privkey: failed to decode private key: %w", err)
	}
	slog.Info("ox: getting private key: decoding key")
	key, err := crypto.NewKey(decodedPrivKey)
	if err != nil {
		return nil, fmt.Errorf("ox: get privkey: failed to decode private key: %w", err)
	}
	if passphrase != "" {
		slog.Info("ox: getting private key: unlocking key", "passphrase", "REDACTED")
		key, err = key.Unlock([]byte(passphrase))
		if err != nil {
			return nil, fmt.Errorf("ox: failed to unlock private key: %w", err)
		}
	}
	isLocked, err := key.IsLocked()
	if err != nil {
		return nil, fmt.Errorf("ox: get privkey: failed to check whether private key is locked: %w", err)
	}
	if isLocked {
		return nil, fmt.Errorf("ox: get privkey: failed to unlock private key: %w", err)
	}
	slog.Info("ox: getting private  checking if key is expired")
	if key.IsExpired(time.Now().Unix()) {
		return nil, fmt.Errorf("ox: private key is expired: %s", key.GetFingerprint())
	}
	return key, nil
}

func oxStoreKey(location string, key string) error {
	var file *os.File
	slog.Info("ox: storing key:", "location", location)
	// #nosec G304 -- location comes from the local system, it's no untrusted input from the network
	file, err := os.Create(location)
	if err != nil {
		return fmt.Errorf("ox: store key: failed to create key location: %w", err)
	}
	if runtime.GOOS != "windows" {
		_ = file.Chmod(os.FileMode(defaultFileRights))
	} else {
		_ = file.Chmod(os.FileMode(defaultFileRightsWin))
	}
	slog.Info("ox: storing key: writing key to file")
	_, err = file.Write([]byte(key))
	if err != nil {
		return fmt.Errorf("ox: store key: failed to write key to file: %w", err)
	}
	err = file.Close()
	if err != nil {
		fmt.Println("error while closing file:", err)
	}
	return nil
}

func oxGenPrivKey(jid string, client *xmpp.Client, iqc chan xmpp.IQ,
	passphrase string, keyType int,
) error {
	xmppURI := "xmpp:" + jid
	slog.Info("ox: generating privkey:", "jid", xmppURI, "keytype", keyType)
	pgp := crypto.PGP()
	privKey, err := pgp.KeyGeneration().
		AddUserId(xmppURI, "").
		OverrideProfileAlgorithm(keyType).
		New().GenerateKey()
	if err != nil {
		return fmt.Errorf("ox: generate privkey: failed to generate private key: %w", err)
	}
	key := privKey
	if passphrase != "" {
		slog.Info("ox: generating privkey: setting", "passphrase", "REDACTED")
		key, err = pgp.LockKey(privKey, []byte(passphrase))
		if err != nil {
			return fmt.Errorf("ox: generate privkey: failed to lock private key with passphrase: %w", err)
		}
	}
	slog.Info("ox: generating privkey: serializing key")
	keySerialized, err := key.Serialize()
	if err != nil {
		return fmt.Errorf("ox: generate privkey: failed to serialize private key: %w", err)
	}
	location, err := oxGetPrivKeyLoc(jid)
	if err != nil {
		return fmt.Errorf("ox: generate privkey: failed to get private key location: %w", err)
	}
	slog.Info("ox: generating privkey: storing base64 encoded key:", "location", location)
	err = oxStoreKey(location,
		base64.StdEncoding.EncodeToString(keySerialized))
	if err != nil {
		return fmt.Errorf("ox: generate privkey: failed store private key: %w", err)
	}
	slog.Info("ox: generating privkey: deriving public key")
	decodedPubKey, err := key.GetPublicKey()
	if err != nil {
		return fmt.Errorf("ox: generate privkey: failed to decode public key: %w", err)
	}
	pubKey, err := crypto.NewKey(decodedPubKey)
	if err != nil {
		return fmt.Errorf("ox: generate privkey: failed to decode public key: %w", err)
	}
	err = oxPublishPubKey(jid, client, iqc, pubKey)
	if err != nil {
		return fmt.Errorf("ox: generate privkey: failed to publish public key: %w", err)
	}
	return nil
}

func oxRecvPublicKeys(client *xmpp.Client, iqc chan xmpp.IQ, recipient string, fingerprint string) (*crypto.KeyRing, error) {
	opkr := etree.NewDocument()
	opkr.WriteSettings.AttrSingleQuote = true
	opkrPs := opkr.CreateElement("pubsub")
	opkrPs.CreateAttr("xmlns", nsPubsub)
	opkrPsItems := opkrPs.CreateElement("items")
	opkrPsItems.CreateAttr("node", nsOxPubKeys+":"+fingerprint)
	opkrPsItems.CreateAttr("max_items", "1")
	opkrString, err := opkr.WriteToString()
	if err != nil {
		return nil, fmt.Errorf("ox: receive public keys: failed to generate xml for public key request: %w", err)
	}
	slog.Info("ox: receiving public keys:", "recipient", recipient)
	oxPublicKey, err := sendIQ(client, iqc, recipient, "get", opkrString)
	if err != nil {
		return nil, fmt.Errorf("ox: receive public keys: iq error requesting public keys: %w", err)
	}
	if oxPublicKey.Type != strResult {
		return nil, fmt.Errorf("ox: error while requesting public key for %s",
			recipient)
	}
	oxPublicKeyXML := etree.NewDocument()
	err = oxPublicKeyXML.ReadFromBytes(oxPublicKey.Query)
	if err != nil {
		return nil, fmt.Errorf("ox: receive public keys: failed parsing iq reply to public key request: %w", err)
	}
	keyring, err := crypto.NewKeyRing(nil)
	if err != nil {
		return nil, fmt.Errorf("ox: receive public keys: failed reading public key: %w", err)
	}
	oxPublicKeyXMLPubsub := oxPublicKeyXML.SelectElement("pubsub")
	if oxPublicKeyXMLPubsub == nil {
		return nil, fmt.Errorf("ox: no pubsub element in reply to public key request")
	}
	oxPublicKeyXMLItems := oxPublicKeyXMLPubsub.SelectElement("items")
	if oxPublicKeyXMLItems == nil {
		return nil, fmt.Errorf("ox: no items element in reply to public key request")
	}
	oxPublicKeyXMLItem := oxPublicKeyXMLItems.SelectElement("item")
	if oxPublicKeyXMLItem == nil {
		return nil, fmt.Errorf("ox: no item element in reply to public key request")
	}
	slog.Info("ox: receiving public keys: received public keys")
	oxPublicKeyXMLPubkey := oxPublicKeyXMLItem.SelectElements("pubkey")
	slog.Info("ox: receiving public keys: decoding public", "key", oxPublicKeyXMLPubkey[0])
	data := oxPublicKeyXMLPubkey[0].SelectElement("data")
	if data == nil {
		return nil, fmt.Errorf("ox: receive public keys: no data element")
	}
	decodedPubKey, err := base64.StdEncoding.DecodeString(data.Text())
	if err != nil {
		return nil, fmt.Errorf("ox: receive public keys: failed to decode public key: %w", err)
	}
	key, err := crypto.NewKey(decodedPubKey)
	if err != nil {
		return nil, fmt.Errorf("ox: receive public keys: failed to decode public key: %w", err)
	}
	if strings.ToUpper(key.GetFingerprint()) != fingerprint {
		return nil, fmt.Errorf("ox: receive public keys: received key has fingerprint %s but %s was advertised",
			strings.ToUpper(key.GetFingerprint()), fingerprint)
	}
	slog.Info("ox: receiving public keys: checking for expiry:", "fingerprint", fingerprint)
	if key.IsExpired(time.Now().Unix()) {
		return nil, fmt.Errorf("ox: key is expired: %s", fingerprint)
	}
	err = keyring.AddKey(key)
	if err != nil {
		return nil, fmt.Errorf("ox: receive public keys: failed adding public key to keyring: %w", err)
	}
	return keyring, nil
}

func oxGetPublicKeyRing(client *xmpp.Client, iqc chan xmpp.IQ, recipient string, oxTrustMode string,
) (*crypto.KeyRing, error) {
	publicKeyRing, err := crypto.NewKeyRing(nil)
	if err != nil {
		return nil, fmt.Errorf("ox: get public keyring: failed to create a new keyring: %w", err)
	}

	oxPubKeyListReq := etree.NewDocument()
	oxPubKeyListReq.WriteSettings.AttrSingleQuote = true
	oxPubKeyListReqPs := oxPubKeyListReq.CreateElement("pubsub")
	oxPubKeyListReqPs.CreateAttr("xmlns", nsPubsub)
	oxPubKeyListReqPsItems := oxPubKeyListReqPs.CreateElement("items")
	oxPubKeyListReqPsItems.CreateAttr("node", nsOxPubKeys)
	oxPubKeyListReqPsItems.CreateAttr("max_items", "1")
	opkl, err := oxPubKeyListReq.WriteToString()
	if err != nil {
		return nil, fmt.Errorf("ox: %w", err)
	}
	oxPublicKeyList, err := sendIQ(client, iqc, recipient, "get", opkl)
	if err != nil {
		return nil, fmt.Errorf("ox: %w", err)
	}
	if oxPublicKeyList.Type != strResult {
		return nil, fmt.Errorf("ox: error while requesting public openpgp keys for %s",
			recipient)
	}
	oxPubKeyListXML := etree.NewDocument()
	err = oxPubKeyListXML.ReadFromBytes(oxPublicKeyList.Query)
	if err != nil {
		return nil, fmt.Errorf("ox: get public keyring: failed to parse answer to public key list request: %w", err)
	}

	pubKeyRingID := "none"
	newestKey, err := time.Parse(time.RFC3339, "1900-01-01T00:00:00Z")
	if err != nil {
		return nil, fmt.Errorf("ox: get public keyring: failed to set time for newest key to 1900-01-01: %w", err)
	}

	oxPubKeyListXMLPubsub := oxPubKeyListXML.SelectElement("pubsub")
	if oxPubKeyListXMLPubsub == nil {
		return nil, fmt.Errorf("ox: no pubsub element in public key list")
	}
	oxPubKeyListXMLPubsubItems := oxPubKeyListXMLPubsub.SelectElement("items")
	if oxPubKeyListXMLPubsubItems == nil {
		return nil, fmt.Errorf("ox: no items element in public key list")
	}
	oxPubKeyListXMLPubsubItemsItem := oxPubKeyListXMLPubsubItems.SelectElement("item")
	if oxPubKeyListXMLPubsubItemsItem == nil {
		return nil, fmt.Errorf("ox: no item element in public key list")
	}
	oxPubKeyListXMLPubsubItemsItemPkl := oxPubKeyListXMLPubsubItemsItem.SelectElement("public-keys-list")
	if oxPubKeyListXMLPubsubItemsItemPkl == nil {
		return nil, fmt.Errorf("ox: no public-keys-list element")
	}
	oxPubKeyListXMLPubsubItemsItemPklPm := oxPubKeyListXMLPubsubItemsItemPkl.SelectElements("pubkey-metadata")
	for _, r := range oxPubKeyListXMLPubsubItemsItemPklPm {
		date := r.SelectAttr("date")
		if date == nil {
			continue
		}
		fingerprint := r.SelectAttr("v4-fingerprint")
		if fingerprint == nil {
			continue
		}
		keyDate, err := time.Parse(time.RFC3339, date.Value)
		if err != nil {
			return nil, fmt.Errorf("ox: get public keyring: failed to parse time stamp for key: %w", err)
		}
		if keyDate.After(newestKey) {
			newestKey = keyDate
			pubKeyRingID = fingerprint.Value
		}
	}
	if pubKeyRingID == "none" {
		return nil, fmt.Errorf("ox: server didn't provide public key fingerprints for %s", recipient)
	}

	pubKeyRingLocation, err := oxGetPubKeyLoc(recipient)
	if err != nil {
		return nil, fmt.Errorf("ox: get public keyring: failed to get public key ring location: %w", err)
	}

	pubKeyReadXML := etree.NewDocument()
	err = pubKeyReadXML.ReadFromFile(pubKeyRingLocation)
	if err == nil {
		date := pubKeyReadXML.SelectElement("date")
		if date != nil {
			savedKeysDate, err := time.Parse(time.RFC3339, date.Text())
			if err != nil {
				return nil, fmt.Errorf("ox: get public keyring: failed to parse time for saved key: %w", err)
			}
			pubKey := pubKeyReadXML.SelectElements("pubkey")
			if pubKey == nil {
				return nil, fmt.Errorf("ox: couldn't read public key from cache")
			}
			keyByte, err := base64.StdEncoding.DecodeString(pubKey[0].Text())
			if err != nil {
				return nil, fmt.Errorf("ox: get public keyring: failed to decode saved key: %w", err)
			}
			key, err := crypto.NewKey(keyByte)
			if err != nil {
				return nil, fmt.Errorf("ox: get public keyring: failed to parse saved key: %w", err)
			}
			if !key.IsExpired(time.Now().Unix()) {
				err = publicKeyRing.AddKey(key)
				if err != nil {
					return nil, fmt.Errorf("ox: get public keyring: failed to add key to public keyring: %w", err)
				}
			}
			storedKeyFingerprint := strings.ToUpper(key.GetFingerprint())
			if oxTrustMode != "blind" && storedKeyFingerprint != pubKeyRingID {
				return nil, fmt.Errorf("ox: get public keyring: failed to use stored key %s for %s as server advertises %s but trust mode is set to \"%s\"",
					storedKeyFingerprint, recipient, pubKeyRingID, oxTrustMode)
			}
			if !savedKeysDate.Before(newestKey) {
				if publicKeyRing.CanEncrypt(time.Now().Unix()) {
					return publicKeyRing, nil
				}
			}
		} else if oxTrustMode != "blind" {
			return nil, fmt.Errorf("ox: get public keyring: failed to parse date of store key for %s but trust mode is set to \"%s\"",
				recipient, oxTrustMode)
		}
	}
	pubKeyRing, err := oxRecvPublicKeys(client, iqc, recipient, pubKeyRingID)
	if err != nil {
		return nil, fmt.Errorf("ox: get public keyring: failed to get public keyring: %w", err)
	}
	pubKeySaveXML := etree.NewDocument()
	date := pubKeySaveXML.CreateElement("date")
	date.SetText(newestKey.Format(time.RFC3339))
	for _, key := range pubKeyRing.GetKeys() {
		keySerialized, err := key.Serialize()
		if err != nil {
			return nil, fmt.Errorf("ox: get public keyring: failed to serialize key: %w", err)
		}
		saveKey := pubKeySaveXML.CreateElement("pubkey")
		saveKey.SetText(base64.StdEncoding.EncodeToString(keySerialized))
	}
	err = pubKeySaveXML.WriteToFile(pubKeyRingLocation)
	if err != nil {
		return nil, fmt.Errorf("ox: get public keyring: failed to create xml for saving public key: %w", err)
	}
	return pubKeyRing, nil
}

func oxEncrypt(client *xmpp.Client, oxPrivKey *crypto.Key, recipient string, keyRing crypto.KeyRing, message string, subject string, groupchat bool) (string, error) {
	if message == "" {
		return "", nil
	}
	ownJid := strings.Split(client.JID(), "/")[0]
	if recipient != ownJid && !groupchat {
		opk, err := oxPrivKey.GetPublicKey()
		if err == nil {
			ownKey, _ := crypto.NewKey(opk)
			_ = keyRing.AddKey(ownKey)
		}
	}
	oxCryptMessage := etree.NewDocument()
	oxCryptMessage.WriteSettings.AttrSingleQuote = true
	oxCryptMessageSc := oxCryptMessage.CreateElement("signcrypt")
	oxCryptMessageSc.CreateAttr("xmlns", nsOx)
	oxCryptMessageScTo := oxCryptMessageSc.CreateElement("to")
	oxCryptMessageScTo.CreateAttr("jid", recipient)
	oxCryptMessageScTime := oxCryptMessageSc.CreateElement("time")
	oxCryptMessageScTime.CreateAttr("stamp", time.Now().UTC().Format("2006-01-02T15:04:05Z"))
	oxCryptMessageScRpad := oxCryptMessageSc.CreateElement("rpad")
	oxCryptMessageScRpad.CreateText(getRpad(len(message)))
	oxCryptMessageScPayload := oxCryptMessageSc.CreateElement("payload")
	if subject != "" {
		oxCryptMessageScPayloadSub := oxCryptMessageScPayload.CreateElement("subject")
		oxCryptMessageScPayloadSub.CreateText(subject)
	}
	oxCryptMessageScPayloadBody := oxCryptMessageScPayload.CreateElement("body")
	oxCryptMessageScPayloadBody.CreateAttr("xmlns", nsJabberClient)
	oxCryptMessageScPayloadBody.CreateText(message)
	ocm, err := oxCryptMessage.WriteToString()
	if err != nil {
		return strError, fmt.Errorf("ox: encrypt: failed to create xml for ox crypt message: %w", err)
	}
	pgpEncrypt, err := crypto.PGP().Encryption().
		Recipients(&keyRing).
		SigningKey(oxPrivKey).New()
	if err != nil {
		return strError, fmt.Errorf("oxEncrypt: failed to create pgp encryption interface: %w", err)
	}
	pgpMessage, err := pgpEncrypt.Encrypt([]byte(ocm))
	if err != nil {
		return strError, fmt.Errorf("ox: encrypt: failed to create pgp message: %w", err)
	}
	id := getID()
	om := etree.NewDocument()
	om.WriteSettings.AttrSingleQuote = true
	omMessage := om.CreateElement("message")
	omMessage.CreateAttr("to", recipient)
	omMessage.CreateAttr("id", id)
	if groupchat {
		omMessage.CreateAttr("type", "groupchat")
	}
	omMessageStore := omMessage.CreateElement("store")
	omMessageStore.CreateAttr("xmlns", nsHints)
	omMessageEme := omMessage.CreateElement("encryption")
	omMessageEme.CreateAttr("xmlns", nsEme)
	omMessageEme.CreateAttr("namespace", nsOx)
	omMessageOpgp := omMessage.CreateElement("openpgp")
	omMessageOpgp.CreateAttr("xmlns", nsOx)
	omMessageOpgp.CreateText(base64.StdEncoding.EncodeToString(pgpMessage.Bytes()))
	omMessageBody := omMessage.CreateElement("body")
	omMessageBody.CreateText(oxAltBody)
	omMessageOrigID := omMessage.CreateElement("origin-id")
	omMessageOrigID.CreateAttr("xmlns", nsSid)
	omMessageOrigID.CreateAttr("id", id)
	oms, err := om.WriteToString()
	if err != nil {
		return strError, fmt.Errorf("ox: encrypt: failed to create xml for ox message: %w", err)
	}

	return oms, nil
}

func oxGetPublicKeyRingChatroom(client *xmpp.Client, iqc chan xmpp.IQ, drc chan xmpp.DiscoResult,
	chatroom string, oxTrustMode string,
) (*crypto.KeyRing, error) {
	keyRing, err := crypto.NewKeyRing(nil)
	if err != nil {
		return nil, err
	}
	discoResult, err := getDiscoInfo(client, drc, chatroom)
	if err != nil {
		return nil, fmt.Errorf("ox: %w", err)
	}
	nonAnon := false
	for _, feature := range discoResult.Features {
		if feature == "muc_nonanonymous" {
			nonAnon = true
		}
	}
	if !nonAnon {
		return nil, fmt.Errorf("ox: muc is not nonanonymous")
	}
	affiliations := [3]string{"admin", "owner", "member"}
	for _, affiliation := range affiliations {
		memberlistRequest := etree.NewDocument()
		query := memberlistRequest.CreateElement("query")
		query.CreateAttr("xmlns", nsMUCAdmin)
		item := query.CreateElement("item")
		item.CreateAttr("affiliation", affiliation)
		mlr, err := memberlistRequest.WriteToString()
		if err != nil {
			return nil, err
		}
		iqReply, err := sendIQ(client, iqc, chatroom, "get", mlr)
		if err != nil {
			return nil, err
		}
		memberlist := etree.NewDocument()
		err = memberlist.ReadFromBytes(iqReply.Query)
		if err != nil {
			return nil, err
		}
		query = memberlist.SelectElement("query")
		if query == nil {
			return nil, fmt.Errorf("ox: error during disco info query")
		}
		items := query.SelectElements("item")
		if items == nil {
			continue
		}
		for _, item := range items {
			jid := item.SelectAttr("jid")
			if jid == nil {
				continue
			}
			memberKeyRing, err := oxGetPublicKeyRing(client, iqc, jid.Value, oxTrustMode)
			if err != nil {
				continue
			}
			for _, key := range memberKeyRing.GetKeys() {
				err = keyRing.AddKey(key)
				if err != nil {
					continue
				}
			}
		}
	}
	if keyRing == nil {
		return nil, fmt.Errorf("ox: didn't receive public keys for muc members")
	}
	return keyRing, nil
}

func oxCheckPrivKeyNode(jid string, client *xmpp.Client, iqc chan xmpp.IQ) (xmpp.IQ, error) {
	slog.Info("ox: parsing private key request to IQ")
	recvPrivKeyRequest := etree.NewDocument()
	recvPrivKeyRequest.WriteSettings.AttrSingleQuote = true
	rpkrPubsub := recvPrivKeyRequest.CreateElement("pubsub")
	rpkrPubsub.CreateAttr("xmlns", nsPubsub)
	rpkrItems := rpkrPubsub.CreateElement("items")
	rpkrItems.CreateAttr("node", nsOxSecretKey)
	rpkrItems.CreateAttr("max_items", "1")
	rpkr, err := recvPrivKeyRequest.WriteToString()
	if err != nil {
		return xmpp.IQ{}, fmt.Errorf("ox: check private key node: failed to create private key request: %w", err)
	}
	slog.Info("ox: sending IQ to request private key", "node", nsOxSecretKey)
	oxPrivKeyReply, err := sendIQ(client, iqc, jid, "get", rpkr)
	if err != nil {
		return xmpp.IQ{}, fmt.Errorf("ox: check private key node: failed parsing iq reply to private key request: %w", err)
	}
	if oxPrivKeyReply.Type != strResult {
		slog.Info("ox: received iq reply", "error", oxPrivKeyReply.Error.Condition)
		if oxPrivKeyReply.Error.Condition == "item-not-found" {
			slog.Info("ox: creating pep node")
			createPrivKeyNodeRequest := etree.NewDocument()
			createPrivKeyNodeRequest.WriteSettings.AttrSingleQuote = true
			cpnrPubsub := createPrivKeyNodeRequest.CreateElement("pubsub")
			cpnrPubsub.CreateAttr("xmlns", nsPubsub)
			cpnrCreate := cpnrPubsub.CreateElement("create")
			cpnrCreate.CreateAttr("node", nsOxSecretKey)
			cpnrConfigure := cpnrPubsub.CreateElement("configure")
			cpnrX := cpnrConfigure.CreateElement("x")
			cpnrX.CreateAttr("xmlns", nsJabberData)
			cpnrX.CreateAttr("type", "submit")
			cpnrField1 := cpnrX.CreateElement("field")
			cpnrField1.CreateAttr("var", "FORM_TYPE")
			cpnrField1.CreateAttr("type", "hidden")
			cpnrField1Val := cpnrField1.CreateElement("value")
			cpnrField1Val.CreateText(pubsubNodeConfig)
			cpnrField2 := cpnrX.CreateElement("field")
			cpnrField2.CreateAttr("var", "pubsub#access_model")
			cpnrField2Val := cpnrField2.CreateElement("value")
			cpnrField2Val.CreateText("whitelist")
			cpnrField3 := cpnrX.CreateElement("field")
			cpnrField3.CreateAttr("var", "pubsub#send_last_published_item")
			cpnrField3Val := cpnrField3.CreateElement("value")
			cpnrField3Val.CreateText("on_sub")
			cpnr, err := createPrivKeyNodeRequest.WriteToString()
			if err != nil {
				return xmpp.IQ{}, fmt.Errorf("ox: failed to create private key node: %w", err)
			}
			slog.Info("ox: sending IQ to create private key node", "node", nsOxSecretKey)
			oxCreateNodeReply, err := sendIQ(client, iqc, jid, "set", cpnr)
			if err != nil {
				return xmpp.IQ{}, fmt.Errorf("ox: create private key node: failed parsing iq reply to private key node creation request: %w", err)
			}
			if oxCreateNodeReply.Type != strResult {
				return xmpp.IQ{}, fmt.Errorf("ox: failed to create private key node: %s", oxCreateNodeReply.Error)
			}

		}
	}
	return oxPrivKeyReply, nil
}

func oxRecvPrivKey(jid string, secret string, client *xmpp.Client, iqc chan xmpp.IQ, drc chan xmpp.DiscoResult,
	oxTrustMode string,
) error {
	err := checkPEPsupport(jid, client, iqc, drc)
	if err != nil {
		return fmt.Errorf("ox: receive private key: %w", err)
	}
	oxPrivKeyReply, err := oxCheckPrivKeyNode(jid, client, iqc)
	if err != nil {
		return fmt.Errorf("ox: receive private key: %w", err)
	}
	slog.Info("ox: receiving private key: received result")
	slog.Info("ox: receiving private key: receiving own private key from pubsub")
	oxPrivKeyXML := etree.NewDocument()
	err = oxPrivKeyXML.ReadFromBytes(oxPrivKeyReply.Query)
	if err != nil {
		return fmt.Errorf("ox: receive private key: failed parsing iq reply to private key request: %w", err)
	}
	oxPrivKeyXMLPubsub := oxPrivKeyXML.SelectElement("pubsub")
	if oxPrivKeyXMLPubsub == nil {
		return fmt.Errorf("ox: receive private key: no pubsub element in reply to private key request")
	}
	oxPrivKeyXMLItems := oxPrivKeyXMLPubsub.SelectElement("items")
	if oxPrivKeyXMLItems == nil {
		return fmt.Errorf("ox: receive private key: no items element in reply to private key request")
	}
	oxPrivKeyXMLItemsNode := oxPrivKeyXMLItems.SelectAttr("node")
	if oxPrivKeyXMLItemsNode.Value != nsOxSecretKey {
		return fmt.Errorf("ox: receive private key: wrong node: %s instead of %s", oxPrivKeyXMLItemsNode.Value, nsOxSecretKey)
	}
	oxPrivKeyXMLItem := oxPrivKeyXMLItems.SelectElement("item")
	if oxPrivKeyXMLItem == nil {
		return fmt.Errorf("ox: receive private key: no item element in reply to private key request")
	}
	oxPrivKeyXMLSecretKey := oxPrivKeyXMLItem.SelectElement("secretkey")
	if oxPrivKeyXMLSecretKey == nil {
		return fmt.Errorf("ox: receive private key: no secretkey element in reply to private key request")
	}
	if oxPrivKeyXMLSecretKey.NamespaceURI() != nsOx {
		return fmt.Errorf("ox: receive private key: wrong namespace: %s instead of %s", oxPrivKeyXMLSecretKey.NamespaceURI(), nsOx)
	}
	decodedPrivKey, err := base64.StdEncoding.DecodeString(oxPrivKeyXMLSecretKey.Text())
	if err != nil {
		return fmt.Errorf("ox: receive private key: failed to decode private key: %w", err)
	}
	pgpDecrypt, err := crypto.PGP().
		Decryption().
		Password([]byte(secret)).
		New()
	if err != nil {
		return fmt.Errorf("ox: receive private key: failed to decode private key: %w", err)
	}
	oxPrivKey, err := pgpDecrypt.Decrypt(decodedPrivKey, crypto.Bytes)
	if err != nil {
		return fmt.Errorf("ox receive private key: failed to decrypt private key: %w", err)
	}
	location, err := oxGetPrivKeyLoc(jid)
	if err != nil {
		return fmt.Errorf("ox: receive private key: failed to determine private key location: %w", err)
	}
	slog.Info("ox: receive private key: getting private key location", "jid", jid, "location", location)
	key, err := crypto.NewKey(oxPrivKey.Bytes())
	if err != nil {
		return fmt.Errorf("ox: receive private key: failed to read private key: %w", err)
	}
	keySerialized, err := key.Serialize()
	if err != nil {
		return fmt.Errorf("ox: receive private key: failed to serialize private key: %w", err)
	}
	err = oxStoreKey(location, base64.StdEncoding.EncodeToString(keySerialized))
	if err != nil {
		return fmt.Errorf("ox: receive private key: failed to store private key: %w", err)
	}
	slog.Info("ox: receive private key: requesting public key ring")
	fingerprint := key.GetFingerprint()
	pubKeyRing, err := oxGetPublicKeyRing(client, iqc, jid, oxTrustMode)
	if err == nil {
		slog.Info("ox: receive private key: checking if public key ring contains", "fingerprint", fingerprint)
		pubKeys := pubKeyRing.GetKeys()
		for _, r := range pubKeys {
			fp := strings.ToUpper(r.GetFingerprint())
			slog.Info("ox: receive private key: checking", "fingerprint", fp)
			if fp == fingerprint {
				slog.Info("ox: receive private key: checking if public key ring contains fingerprint: success")
				return nil
			}
		}
	}
	pk, err := key.GetPublicKey()
	if err != nil {
		return fmt.Errorf("ox: receive private key: failed to derive public key: %w", err)
	}
	pubKey, err := crypto.NewKey(pk)
	if err != nil {
		return fmt.Errorf("ox: receive private key: failed to serialize public key: %w", err)
	}
	slog.Info("ox: import private key: publishing public key")
	err = oxPublishPubKey(jid, client, iqc, pubKey)
	if err != nil {
		return fmt.Errorf("ox: receive private key: failed to publish public key: %w", err)
	}
	return nil
}

func oxTransPrivKey(jid string, oxPrivKey *crypto.Key, client *xmpp.Client, iqc chan xmpp.IQ, drc chan xmpp.DiscoResult) error {
	err := checkPEPsupport(jid, client, iqc, drc)
	if err != nil {
		return fmt.Errorf("ox: transfer private key: %w", err)
	}
	slog.Info("ox: transferring private key: generating secret")
	secret, err := getSecret(oxSecretAlphabet, oxSecretLength, oxSecretChunkSize)
	if err != nil {
		return fmt.Errorf("ox: transfer private key: failed to get secret: %w", err)
	}
	slog.Info("ox: transferring private key: creating encryption interface")
	pgpEncrypt, err := crypto.PGP().
		Encryption().
		Password([]byte(secret)).
		New()
	if err != nil {
		return fmt.Errorf("ox: transfer private key: failed to create pgp encryption interface: %w", err)
	}
	slog.Info("ox: transferring private key: serializing private key")
	oxPrivKeySerialized, err := oxPrivKey.Serialize()
	if err != nil {
		return fmt.Errorf("ox: transfer private key: failed to serialize private key: %w", err)
	}
	slog.Info("ox: transferring private key: encrypting private key")
	pgpMessage, err := pgpEncrypt.Encrypt(oxPrivKeySerialized)
	if err != nil {
		return fmt.Errorf("ox: transfer private key: failed to create pgp message: %w", err)
	}
	_, err = oxCheckPrivKeyNode(jid, client, iqc)
	if err != nil {
		return fmt.Errorf("ox: transfer private key: %w", err)
	}
	slog.Info("ox: publishing private key")
	publishPrivKeyRequest := etree.NewDocument()
	publishPrivKeyRequest.WriteSettings.AttrSingleQuote = true
	ppkrPubsub := publishPrivKeyRequest.CreateElement("pubsub")
	ppkrPubsub.CreateAttr("xmlns", nsPubsub)
	ppkrPublish := ppkrPubsub.CreateElement("publish")
	ppkrPublish.CreateAttr("node", nsOxSecretKey)
	ppkrItem := ppkrPublish.CreateElement("item")
	ppkrSecretKey := ppkrItem.CreateElement("secretkey")
	ppkrSecretKey.CreateAttr("xmlns", nsOx)
	ppkrSecretKey.CreateText(base64.StdEncoding.EncodeToString(pgpMessage.Bytes()))
	ppkr, err := publishPrivKeyRequest.WriteToString()
	if err != nil {
		return fmt.Errorf("ox: failed to create private key publish request: %w", err)
	}
	slog.Info("ox: sending IQ to publish private key")
	oxPublishPrivateKeyReply, err := sendIQ(client, iqc, jid, "set", ppkr)
	if err != nil {
		return fmt.Errorf("ox: publish private key: failed parsing iq reply to publish private key request: %w", err)
	}
	if oxPublishPrivateKeyReply.Type != strResult {
		return fmt.Errorf("ox: publish private key: %s", oxPublishPrivateKeyReply.Error.Condition)
	}
	fmt.Printf("OX: Secret key published: Encrypted with the secret phrase: %s\n", secret)
	return nil
}
