// Copyright Martin Dosch. // Use of this source code is governed by the BSD-2-clause // license that can be found in the LICENSE file. package main import ( "encoding/base64" "errors" "log" "os" "runtime" "strings" "time" "github.com/ProtonMail/gopenpgp/v2/crypto" // MIT License "github.com/beevik/etree" // BSD-2-clause "github.com/mattn/go-xmpp" // BSD-3-Clause ) func oxDeleteNodes(jid string, client *xmpp.Client, iqc chan xmpp.IQ) error { nodeListRequest := etree.NewDocument() nodeListRequest.WriteSettings.AttrSingleQuote = true query := nodeListRequest.CreateElement("query") query.CreateAttr("xmlns", nsDiscoItems) nlr, err := nodeListRequest.WriteToString() if err != nil { return err } iqReply, err := sendIQ(client, iqc, jid, "get", nlr) if err != nil { return err } nodeListReply := etree.NewDocument() err = nodeListReply.ReadFromBytes(iqReply.Query) if err != nil { return err } query = nodeListReply.SelectElement("query") if query == nil { return errors.New("error parsing iq reply") } items := query.SelectElements("item") if items == nil { return errors.New("error parsing iq reply") } for _, item := range items { node := item.SelectAttr("node") if node == nil { continue } if !strings.Contains(node.Value, nsOx) { continue } deleteNodeRequest := etree.NewDocument() deleteNodeRequest.WriteSettings.AttrSingleQuote = true pubsub := deleteNodeRequest.CreateElement("pubsub") pubsub.CreateAttr("xmlns", nsPubsubOwner) delete := pubsub.CreateElement("delete") delete.CreateAttr("node", node.Value) dnr, err := deleteNodeRequest.WriteToString() if err != nil { continue } _, 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, user string, oxPrivKey *crypto.Key) (string, time.Time, error) { var cryptMsgByte []byte 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 "error", time.Now(), err } break } } oxMsg := crypto.NewPGPMessage(cryptMsgByte) keyRing, err := crypto.NewKeyRing(oxPrivKey) if err != nil { return "error", time.Now(), err } senderKeyRing, err := oxGetPublicKeyRing(client, iqc, sender) if err != nil { return "error", time.Now(), err } decryptMsg, err := keyRing.Decrypt(oxMsg, senderKeyRing, crypto.GetUnixTime()) if err != nil { return "error", time.Now(), err } // Remove invalid code points. message := validUTF8(string(decryptMsg.Data)) doc := etree.NewDocument() err = doc.ReadFromString(message) if err != nil { return "error", time.Now(), err } signcrypt := doc.SelectElement("signcrypt") if signcrypt == nil { return "error", time.Now(), errors.New("ox: no signcrypt element") } to := signcrypt.SelectElement("to") if to == nil { return "error", time.Now(), errors.New("ox: no to element") } jid := to.SelectAttr("jid") if jid == nil { return "error", time.Now(), errors.New("ox: no jid attribute") } if strings.Split(jid.Value, "/")[0] != user { return "error", time.Now(), errors.New("ox: encrypted for wrong user") } timestamp := signcrypt.SelectElement("time") if timestamp == nil { return "error", time.Now(), errors.New("ox: no time element") } stamp := timestamp.SelectAttr("stamp") if stamp == nil { return "error", time.Now(), errors.New("ox: no stamp attribute") } msgStamp, err := time.Parse("2006-01-02T15:04:05Z0700", stamp.Value) if err != nil { return "error", time.Now(), err } payload := signcrypt.SelectElement("payload") if payload == nil { return "error", time.Now(), errors.New("ox: no payload element") } body := payload.SelectElement("body") if body == nil { return "", time.Now(), nil } return body.Text(), msgStamp, nil } func isOxMsg(m xmpp.Chat) bool { for _, r := range m.OtherElem { if r.XMLName.Space == nsOx { return true } } return false } func oxImportPrivKey(jid string, privKeyLocation string, client *xmpp.Client, iqc chan xmpp.IQ) error { xmppURI := "xmpp:" + jid buffer, err := readFile(privKeyLocation) if err != nil { return err } key, err := crypto.NewKey(buffer.Bytes()) if err != nil { key, err = crypto.NewKeyFromArmored(buffer.String()) if err != nil { keyDecoded, err := base64.StdEncoding.DecodeString(buffer.String()) if err != nil { return err } key, err = crypto.NewKey(keyDecoded) if err != nil { return err } } } entity := key.GetEntity() if entity.Identities[xmppURI] == nil { return errors.New("Key identity is not " + xmppURI) } pk, err := key.GetPublicKey() if err != nil { return err } pubKey, err := crypto.NewKey(pk) if err != nil { return err } fingerprint := strings.ToUpper(pubKey.GetFingerprint()) _, err = oxRecvPublicKeys(client, iqc, jid, fingerprint) if err != nil { err = oxPublishPubKey(jid, client, iqc, pubKey) if err != nil { return err } } location, err := oxGetPrivKeyLoc(jid) if err != nil { return err } keySerialized, err := key.Serialize() if err != nil { return err } err = oxStoreKey(location, base64.StdEncoding.EncodeToString(keySerialized)) if err != nil { log.Fatal(err) } pubKeyRing, err := oxGetPublicKeyRing(client, iqc, jid) if err == nil { pubKeys := pubKeyRing.GetKeys() for _, r := range pubKeys { if strings.ToUpper(r.GetFingerprint()) == fingerprint { return nil } } } err = oxPublishPubKey(jid, client, iqc, pubKey) if err != nil { return 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()) keySerialized, err := pubKey.Serialize() if err != nil { return err } 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 err } iqReply, err := sendIQ(client, iqc, jid, "set", xmlstring) if err != nil { return err } if iqReply.Type != "result" { return errors.New("error while publishing public key") } ownPubKeyRingFromPubsub, err := oxRecvPublicKeys(client, iqc, jid, fingerprint) if err != nil { return errors.New("couldn't successfully verify public key upload") } ownPubKeyFromPubsub := ownPubKeyRingFromPubsub.GetKeys()[0] ownPubKeyFromPubsubSerialized, err := ownPubKeyFromPubsub.Serialize() if err != nil { return errors.New("couldn't successfully verify public key upload") } if pubKeyBase64 != base64.StdEncoding.EncodeToString(ownPubKeyFromPubsubSerialized) { return errors.New("couldn't successfully verify public key upload") } 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 err } iqReply, err = sendIQ(client, iqc, jid, "set", xmlstring) if err != nil { return err } if iqReply.Type != "result" { return errors.New("couldn't publish public key list") } return nil } func oxGetPrivKeyLoc(jid string) (string, error) { var err error var homeDir, dataDir string switch { case os.Getenv("$XDG_DATA_HOME") != "": dataDir = os.Getenv("$XDG_DATA_HOME") case os.Getenv("$XDG_HOME") != "": homeDir = os.Getenv("$XDG_HOME") dataDir = homeDir + "/.local/share" case os.Getenv("$HOME") != "": homeDir = os.Getenv("$HOME") dataDir = homeDir + "/.local/share" default: homeDir, err = os.UserHomeDir() if err != nil { return "error", err } if homeDir == "" { return "error", err } dataDir = homeDir + "/.local/share" } dataDir = dataDir + "/go-sendxmpp/oxprivkeys/" if _, err = os.Stat(dataDir); os.IsNotExist(err) { err = os.MkdirAll(dataDir, 0700) if err != nil { return "error", err } } dataFile := dataDir + base64.StdEncoding.EncodeToString([]byte(jid)) return dataFile, nil } func oxGetPubKeyLoc(fingerprint string) (string, error) { var err error var homeDir, dataDir string switch { case os.Getenv("$XDG_DATA_HOME") != "": dataDir = os.Getenv("$XDG_DATA_HOME") case os.Getenv("$XDG_HOME") != "": homeDir = os.Getenv("$XDG_HOME") dataDir = homeDir + "/.local/share" case os.Getenv("$HOME") != "": homeDir = os.Getenv("$HOME") dataDir = homeDir + "/.local/share" default: homeDir, err = os.UserHomeDir() if err != nil { return "error", err } if homeDir == "" { return "error", err } dataDir = homeDir + "/.local/share" } dataDir = dataDir + "/go-sendxmpp/oxpubkeys/" if _, err = os.Stat(dataDir); os.IsNotExist(err) { err = os.MkdirAll(dataDir, 0700) if err != nil { return "error", err } } dataFile := dataDir + fingerprint return dataFile, nil } func oxGetPrivKey(jid string, passphrase string) (*crypto.Key, error) { dataFile, err := oxGetPrivKeyLoc(jid) if err != nil { log.Fatal(err) } keyBuffer, err := readFile(dataFile) if err != nil { log.Fatal(err) } keyString := keyBuffer.String() decodedPrivKey, err := base64.StdEncoding.DecodeString(keyString) if err != nil { return nil, err } key, err := crypto.NewKey(decodedPrivKey) if err != nil { return nil, err } if passphrase != "" { key, err = key.Unlock([]byte(passphrase)) if err != nil { log.Fatal("Ox: couldn't unlock private key.") } } isLocked, err := key.IsLocked() if err != nil { return nil, err } if isLocked { log.Fatal("Ox: private key is locked.") } if key.IsExpired() { return nil, errors.New("Ox: private key is expired: " + key.GetFingerprint()) } return key, nil } func oxStoreKey(location string, key string) error { var file *os.File file, err := os.Create(location) if err != nil { return err } defer file.Close() if runtime.GOOS != "windows" { _ = file.Chmod(os.FileMode(0600)) } else { _ = file.Chmod(os.FileMode(0200)) } _, err = file.Write([]byte(key)) if err != nil { return err } return nil } func oxGenPrivKey(jid string, client *xmpp.Client, iqc chan xmpp.IQ, passphrase string, keyType string) error { xmppURI := "xmpp:" + jid key, err := crypto.GenerateKey(xmppURI, "", keyType, 4096) if err != nil { return err } if passphrase != "" { key, err = key.Lock([]byte(passphrase)) if err != nil { return err } } keySerialized, err := key.Serialize() if err != nil { return err } location, err := oxGetPrivKeyLoc(jid) if err != nil { return err } err = oxStoreKey(location, base64.StdEncoding.EncodeToString(keySerialized)) if err != nil { log.Fatal(err) } decodedPubKey, err := key.GetPublicKey() if err != nil { return err } pubKey, err := crypto.NewKey(decodedPubKey) if err != nil { return err } err = oxPublishPubKey(jid, client, iqc, pubKey) if err != nil { return 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, err } oxPublicKey, err := sendIQ(client, iqc, recipient, "get", opkrString) if err != nil { return nil, err } if oxPublicKey.Type != "result" { return nil, errors.New("error while requesting public key for " + recipient) } oxPublicKeyXML := etree.NewDocument() err = oxPublicKeyXML.ReadFromBytes(oxPublicKey.Query) if err != nil { return nil, err } keyring, err := crypto.NewKeyRing(nil) if err != nil { return nil, err } oxPublicKeyXMLPubsub := oxPublicKeyXML.SelectElement("pubsub") if oxPublicKeyXMLPubsub == nil { return nil, errors.New("ox: no pubsub element in reply to public " + "key request") } oxPublicKeyXMLItems := oxPublicKeyXMLPubsub.SelectElement("items") if oxPublicKeyXMLItems == nil { return nil, errors.New("ox: no items element in reply to public " + "key request") } oxPublicKeyXMLItem := oxPublicKeyXMLItems.SelectElement("item") if oxPublicKeyXMLItem == nil { return nil, errors.New("ox: no item element in reply to public " + "key request") } oxPublicKeyXMLPubkeys := oxPublicKeyXMLItem.SelectElements("pubkey") for _, r := range oxPublicKeyXMLPubkeys { data := r.SelectElement("data") if data == nil { continue } decodedPubKey, err := base64.StdEncoding.DecodeString(data.Text()) if err != nil { return nil, err } key, err := crypto.NewKey(decodedPubKey) if err != nil { return nil, err } if key.IsExpired() { return nil, errors.New("Key is expired: " + fingerprint) } err = keyring.AddKey(key) if err != nil { return nil, err } } return keyring, nil } func oxGetPublicKeyRing(client *xmpp.Client, iqc chan xmpp.IQ, recipient string) (*crypto.KeyRing, error) { publicKeyRing, err := crypto.NewKeyRing(nil) if err != nil { return nil, 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 { log.Fatal(err) } oxPublicKeyList, err := sendIQ(client, iqc, recipient, "get", opkl) if err != nil { log.Fatal(err) } if oxPublicKeyList.Type != "result" { return nil, errors.New("error while requesting public openpgp keys for " + recipient) } oxPubKeyListXML := etree.NewDocument() err = oxPubKeyListXML.ReadFromBytes(oxPublicKeyList.Query) if err != nil { return nil, err } pubKeyRingID := "none" newestKey, err := time.Parse(time.RFC3339, "1900-01-01T00:00:00Z") if err != nil { return nil, err } oxPubKeyListXMLPubsub := oxPubKeyListXML.SelectElement("pubsub") if oxPubKeyListXMLPubsub == nil { return nil, errors.New("ox: no pubsub element in public key list") } oxPubKeyListXMLPubsubItems := oxPubKeyListXMLPubsub.SelectElement("items") if oxPubKeyListXMLPubsubItems == nil { return nil, errors.New("ox: no items element in public key list") } oxPubKeyListXMLPubsubItemsItem := oxPubKeyListXMLPubsubItems.SelectElement("item") if oxPubKeyListXMLPubsubItemsItem == nil { return nil, errors.New("ox: no item element in public key list") } oxPubKeyListXMLPubsubItemsItemPkl := oxPubKeyListXMLPubsubItemsItem.SelectElement("public-keys-list") if oxPubKeyListXMLPubsubItemsItemPkl == nil { return nil, errors.New("ox: no keblic-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, err } if keyDate.After(newestKey) { newestKey = keyDate pubKeyRingID = fingerprint.Value } } if pubKeyRingID == "none" { return nil, errors.New("server didn't provide public key fingerprints for " + recipient) } pubKeyRingLocation, err := oxGetPubKeyLoc(pubKeyRingID) if err != nil { return nil, 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, err } if !savedKeysDate.Before(newestKey) { pubKeys := pubKeyReadXML.SelectElements("pubkey") if pubKeys == nil { return nil, errors.New("couldn't read public keys from cache") } for _, r := range pubKeys { keyByte, err := base64.StdEncoding.DecodeString(r.Text()) if err != nil { return nil, err } key, err := crypto.NewKey(keyByte) if err != nil { return nil, err } if !key.IsExpired() { err = publicKeyRing.AddKey(key) if err != nil { return nil, err } } } if publicKeyRing.CanEncrypt() { return publicKeyRing, nil } } } } pubKeyRing, err := oxRecvPublicKeys(client, iqc, recipient, pubKeyRingID) if err != nil { return nil, 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, err } if err != nil { return nil, err } saveKey := pubKeySaveXML.CreateElement("pubkey") saveKey.SetText(base64.StdEncoding.EncodeToString(keySerialized)) } err = pubKeySaveXML.WriteToFile(pubKeyRingLocation) if err != nil { return nil, err } return pubKeyRing, nil } func oxEncrypt(client *xmpp.Client, iqc chan xmpp.IQ, oxPrivKey *crypto.Key, recipient string, keyRing *crypto.KeyRing, message string) (string, error) { if message == "" { return "", nil } privKeyRing, err := crypto.NewKeyRing(oxPrivKey) if err != nil { return "error", err } ownJid := strings.Split(client.JID(), "/")[0] if recipient != ownJid { 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") oxCryptMessageScPayloadBody := oxCryptMessageScPayload.CreateElement("body") oxCryptMessageScPayloadBody.CreateAttr("xmlns", nsJabberClient) oxCryptMessageScPayloadBody.CreateText(message) ocm, err := oxCryptMessage.WriteToString() if err != nil { return "error", err } plainMessage := crypto.NewPlainMessage([]byte(ocm)) pgpMessage, err := keyRing.Encrypt(plainMessage, privKeyRing) if err != nil { return "error", err } om := etree.NewDocument() om.WriteSettings.AttrSingleQuote = true omMessage := om.CreateElement("message") omMessage.CreateAttr("to", recipient) omMessage.CreateAttr("id", getID()) 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.Data)) omMessageBody := omMessage.CreateElement("body") omMessageBody.CreateText(oxAltBody) oms, err := om.WriteToString() if err != nil { return "error", err } return oms, nil }