// Copyright 2022 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" "encoding/xml" "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 oxImportPrivKey(jid string, privKeyLocation string, client *xmpp.Client) 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 { 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, jid, fingerprint) if err != nil { return errors.New("Key not found in pubsub: " + fingerprint) } 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) } 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("Couldn't unlock private key.") } } isLocked, err := key.IsLocked() if err != nil { return nil, err } if isLocked { log.Fatal("Private key is locked.") } if key.IsExpired() { return nil, errors.New("Key is expired: " + key.GetFingerprint()) } return key, nil } func oxStoreKey(location string, key string) error { var file *os.File if _, err := os.Stat(location); os.IsNotExist(err) { file, err = os.Create(location) if err != nil { return err } } else { file, err = os.OpenFile(location, os.O_RDWR, os.FileMode(0600)) 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, passphrase string) error { xmppUri := "xmpp:" + jid key, err := crypto.GenerateKey(xmppUri, xmppUri, "x25519", 0) if err != nil { return err } decodedPubKey, err := key.GetPublicKey() if err != nil { return err } pubKeyBase64 := base64.StdEncoding.EncodeToString(decodedPubKey) 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) } keyCreated := time.Now().UTC().Format("2006-01-02T15:04:05Z") pubKey, err := crypto.NewKey(decodedPubKey) if err != nil { return err } fingerprint := strings.ToUpper(pubKey.GetFingerprint()) root := etree.NewDocument() 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, jid, "set", xmlstring) if err != nil { return err } if iqReply.Type != "result" { return errors.New("Error while publishing public key.") } ownPubKeyRingFromPubsub, err := oxRecvPublicKeys(client, 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() 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, jid, "set", xmlstring) if err != nil { return err } if iqReply.Type != "result" { return errors.New("Couldn't publish public key list.") } return nil } func oxRecvPublicKeys(client *xmpp.Client, recipient string, fingerprint string) (*crypto.KeyRing, error) { var oxPublicKeyRequest IQPubsubRequest var oxPublicKeyXML OxPublicKey oxPublicKeyRequest.Xmlns = nsPubsub oxPublicKeyRequest.Items.Node = nsOxPubKeys + ":" + fingerprint oxPublicKeyRequest.Items.MaxItems = "1" opk, err := xml.Marshal(oxPublicKeyRequest) if err != nil { return nil, err } oxPublicKey, err := sendIQ(client, recipient, "get", string(opk)) if err != nil { return nil, err } if oxPublicKey.Type != "result" { return nil, errors.New("Error while requesting public key for " + recipient) } err = xml.Unmarshal(oxPublicKey.Query, &oxPublicKeyXML) if err != nil { return nil, err } keyring, err := crypto.NewKeyRing(nil) if err != nil { return nil, err } for _, r := range oxPublicKeyXML.Items.Item.Pubkey { decodedPubKey, err := base64.StdEncoding.DecodeString(r.Data) 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, recipient string) (*crypto.KeyRing, error) { var oxPublicKeyListRequest IQPubsubRequest var oxPublicKeyListXML OxPublicKeysList publicKeyRing, err := crypto.NewKeyRing(nil) if err != nil { return nil, err } oxPublicKeyListRequest.Xmlns = nsPubsub oxPublicKeyListRequest.Items.Node = nsOxPubKeys oxPublicKeyListRequest.Items.MaxItems = "1" opkl, err := xml.Marshal(oxPublicKeyListRequest) if err != nil { log.Fatal(err) } oxPublicKeyList, err := sendIQ(client, recipient, "get", string(opkl)) if err != nil { log.Fatal(err) } if oxPublicKeyList.Type != "result" { return nil, errors.New("Error while requesting public openpgp keys for " + recipient) } err = xml.Unmarshal(oxPublicKeyList.Query, &oxPublicKeyListXML) 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 } for _, r := range oxPublicKeyListXML.Items.Item.PublicKeysList.PubkeyMetadata { keyDate, err := time.Parse(time.RFC3339, r.Date) if err != nil { return nil, err } if keyDate.After(newestKey) { newestKey = keyDate pubKeyRingId = r.V4Fingerprint } } 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, 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, oxPrivKey *crypto.Key, recipient string, keyRing *crypto.KeyRing, message string) (string, error) { var oxCryptMessage OxCryptElement var oxMessage OxMessageElement 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.Xmlns = nsOx oxCryptMessage.To.Jid = recipient oxCryptMessage.Time.Stamp = time.Now().UTC().Format("2006-01-02T15:04:05Z") oxCryptMessage.Rpad = getRpad() oxCryptMessage.Payload.Body.Xmlns = nsJabberClient oxCryptMessage.Payload.Body.Text = message ocm, err := xml.Marshal(oxCryptMessage) if err != nil { return "error", err } plainMessage := crypto.NewPlainMessage([]byte(ocm)) pgpMessage, err := keyRing.Encrypt(plainMessage, privKeyRing) if err != nil { return "error", err } oxMessage.To = recipient oxMessage.Id = getID() oxMessage.Store.Xmlns = nsHints oxMessage.Openpgp.Text = base64.StdEncoding.EncodeToString(pgpMessage.Data) oxMessage.Openpgp.Xmlns = nsOx oxMessage.Encryption.Xmlns = nsEme oxMessage.Encryption.Namespace = nsOx oxMessage.Body = oxAltBody om, err := xml.Marshal(oxMessage) if err != nil { return "error", err } return string(om), nil }