Add experimental key generation.

ox
Martin Dosch 2 years ago
parent 672f303a6b
commit 0d6b36e500

@ -5,12 +5,14 @@
package main
const (
VERSION = "0.4.0-devel"
nsEme = "urn:xmpp:eme:0"
nsHttpUpload = "urn:xmpp:http:upload:0"
nsJabberClient = "jabber:client"
nsOx = "urn:xmpp:openpgp:0"
nsOxPubKeys = "urn:xmpp:openpgp:0:public-keys"
nsPubsub = "http://jabber.org/protocol/pubsub"
oxAltBody = "This message is encrypted (XEP-0373: OpenPGP for XMPP)."
VERSION = "0.4.0-devel"
nsEme = "urn:xmpp:eme:0"
nsHttpUpload = "urn:xmpp:http:upload:0"
nsJabberClient = "jabber:client"
nsJabberData = "jabber:x:data"
nsOx = "urn:xmpp:openpgp:0"
nsOxPubKeys = "urn:xmpp:openpgp:0:public-keys"
nsPubsub = "http://jabber.org/protocol/pubsub"
oxAltBody = "This message is encrypted (XEP-0373: OpenPGP for XMPP)."
pubsubPubOptions = "http://jabber.org/protocol/pubsub#publish-options"
)

@ -6,6 +6,77 @@ package main
import "encoding/xml"
// Created with https://github.com/miku/zek
type IQoxPublishKeyListType struct {
XMLName xml.Name `xml:"pubsub"`
Text string `xml:",chardata"`
Xmlns string `xml:"xmlns,attr"`
Publish struct {
Text string `xml:",chardata"`
Node string `xml:"node,attr"`
Item struct {
Text string `xml:",chardata"`
PublicKeysList struct {
Text string `xml:",chardata"`
Xmlns string `xml:"xmlns,attr"`
PubkeyMetadata [1]struct {
Text string `xml:",chardata"`
V4Fingerprint string `xml:"v4-fingerprint,attr"`
Date string `xml:"date,attr"`
} `xml:"pubkey-metadata"`
} `xml:"public-keys-list"`
} `xml:"item"`
} `xml:"publish"`
PublishOptions struct {
Text string `xml:",chardata"`
X struct {
Text string `xml:",chardata"`
Xmlns string `xml:"xmlns,attr"`
Type string `xml:"type,attr"`
Field [2]struct {
Text string `xml:",chardata"`
Var string `xml:"var,attr"`
Type string `xml:"type,attr"`
Value string `xml:"value"`
} `xml:"field"`
} `xml:"x"`
} `xml:"publish-options"`
}
// Created with https://github.com/miku/zek
type IQoxPublishKeyType struct {
XMLName xml.Name `xml:"pubsub"`
Text string `xml:",chardata"`
Xmlns string `xml:"xmlns,attr"`
Publish struct {
Text string `xml:",chardata"`
Node string `xml:"node,attr"`
Item struct {
Text string `xml:",chardata"`
ID string `xml:"id,attr"`
Pubkey struct {
Text string `xml:",chardata"`
Xmlns string `xml:"xmlns,attr"`
Data string `xml:"data"`
} `xml:"pubkey"`
} `xml:"item"`
} `xml:"publish"`
PublishOptions struct {
Text string `xml:",chardata"`
X struct {
Text string `xml:",chardata"`
Xmlns string `xml:"xmlns,attr"`
Type string `xml:"type,attr"`
Field [2]struct {
Text string `xml:",chardata"`
Var string `xml:"var,attr"`
Type string `xml:"type,attr"`
Value string `xml:"value"`
} `xml:"field"`
} `xml:"x"`
} `xml:"publish-options"`
}
// Created with https://github.com/miku/zek
type OxMessageElement struct {
XMLName xml.Name `xml:"message"`

@ -16,8 +16,9 @@ import (
"strings"
"time"
"github.com/mattn/go-xmpp" // BSD-3-Clause
"github.com/pborman/getopt/v2" // BSD-3-Clause
"github.com/ProtonMail/gopenpgp/v2/crypto" // MIT License
"github.com/mattn/go-xmpp" // BSD-3-Clause
"github.com/pborman/getopt/v2" // BSD-3-Clause
)
type configuration struct {
@ -100,6 +101,8 @@ func main() {
flagVersion := getopt.BoolLong("version", 0, "Show version information.")
flagMUCPassword := getopt.StringLong("muc-password", 0, "", "Password for password protected MUCs.")
flagOx := getopt.BoolLong("ox", 0, "Use \"OpenPGP for XMPP\" encryption (experimental).")
flagOxGenPrivKey := getopt.StringLong("ox-generate-private-key", 0, "",
"Generate a public Ox key for the given JID and publish the corresponding public key.")
// Parse command line flags.
getopt.Parse()
@ -134,7 +137,7 @@ func main() {
// For listening or sending raw XML it's not required to specify a recipient except
// when sending raw messages to MUCs (go-sendxmpp will join the MUC automatically).
recipients := getopt.Args()
if (len(recipients) == 0 && !*flagRaw && !*flagListen) ||
if (len(recipients) == 0 && !*flagRaw && !*flagListen && *flagOxGenPrivKey == "") ||
(len(recipients) == 0 && *flagChatroom) {
log.Fatal("No recipient specified.")
}
@ -254,6 +257,22 @@ func main() {
log.Fatal(err)
}
if *flagOxGenPrivKey != "" {
var oxPrivKey *crypto.Key
validatedOwnJid, err := MarshalJID(*flagOxGenPrivKey)
if err != nil {
// log.Fatal("Invalid JID:", *flagOxGenPrivKey)
log.Fatal(err)
}
oxPrivKey, err = oxGenPrivKey(validatedOwnJid, client)
if err != nil {
log.Fatal(err)
}
// Print oxPrivKey to be able to compile as it is not yet used.
println(oxPrivKey)
os.Exit(0)
}
if *flagHttpUpload != "" {
message = httpUpload(client, tlsConfig.ServerName,
*flagHttpUpload)

212
ox.go

@ -9,6 +9,8 @@ import (
"encoding/xml"
"errors"
"log"
"os"
"runtime"
"strings"
"time"
@ -16,50 +18,154 @@ import (
"github.com/mattn/go-xmpp" // BSD-3-Clause
)
func oxGetPublicKey(client *xmpp.Client, recipient string) (*crypto.Key, error) {
var oxPublicKeyListRequest IQPubsubRequest
var oxPublicKeyRequest IQPubsubRequest
var oxPublicKeyListXML OxPublicKeysList
var oxPublicKeyXML OxPublicKey
oxPublicKeyListRequest.Xmlns = nsPubsub
oxPublicKeyListRequest.Items.Node = nsOxPubKeys
oxPublicKeyListRequest.Items.MaxItems = "100"
opkl, err := xml.Marshal(oxPublicKeyListRequest)
func oxStorePrivKey(jid string, privKey string) error {
var err error
var homeDir, dataDir string
dataDir = os.Getenv("$XDG_DATA_HOME")
if dataDir == "" {
homeDir = os.Getenv("$XDG_HOME")
if homeDir == "" {
homeDir = os.Getenv("$HOME")
if homeDir == "" {
homeDir, err = os.UserHomeDir()
if err != nil {
return err
}
if homeDir == "" {
return errors.New("No XDG_DATA_HOME")
}
}
}
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 err
}
}
dataFile := dataDir + base64.StdEncoding.EncodeToString([]byte(jid))
var file *os.File
if _, err := os.Stat(dataFile); os.IsNotExist(err) {
file, err = os.Create(dataFile)
if err != nil {
return err
}
} else {
file, err = os.OpenFile(dataFile, os.O_RDWR, 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(privKey))
if err != nil {
log.Fatal(err)
return err
}
oxPublicKeyList, err := sendIQ(client, recipient, "get", string(opkl))
return nil
}
func oxGenPrivKey(jid string, client *xmpp.Client) (*crypto.Key, error) {
var iqOxPublishKey IQoxPublishKeyType
var iqOxPublishKeyList IQoxPublishKeyListType
xmppUri := "xmpp:" + jid
key, err := crypto.GenerateKey(xmppUri, xmppUri, "x25519", 0)
if err != nil {
return nil, err
}
keySerialized, _ := key.Serialize()
pubKey, err := key.GetPublicKey()
if err != nil {
return nil, err
}
pubKeyBase64 := base64.StdEncoding.EncodeToString(pubKey)
err = oxStorePrivKey(jid,
base64.StdEncoding.EncodeToString(keySerialized))
if err != nil {
// return nil, errors.New("Couldn't store private key:")
log.Fatal(err)
}
if oxPublicKeyList.Type != "result" {
return nil, errors.New("Error while requesting public openpgp keys for " +
recipient)
fingerprint := key.GetFingerprint()
iqOxPublishKey.Xmlns = nsPubsub
iqOxPublishKey.Publish.Node = nsOxPubKeys + ":" + fingerprint
iqOxPublishKey.Publish.Item.ID = time.Now().Format("1900-01-01T06:06:06Z")
iqOxPublishKey.Publish.Item.Pubkey.Xmlns = nsJabberData
iqOxPublishKey.Publish.Item.Pubkey.Data = pubKeyBase64
iqOxPublishKey.PublishOptions.X.Xmlns = nsJabberData
iqOxPublishKey.PublishOptions.X.Type = "submit"
iqOxPublishKey.PublishOptions.X.Field[0].Var = "FORM_TYPE"
iqOxPublishKey.PublishOptions.X.Field[0].Type = "hidden"
iqOxPublishKey.PublishOptions.X.Field[0].Value = "open"
iqOxPublishKey.PublishOptions.X.Field[1].Var = "pubsub#acces_model"
iqOxPublishKey.PublishOptions.X.Field[1].Value = "open"
opk, err := xml.Marshal(iqOxPublishKey)
if err != nil {
return nil, err
}
err = xml.Unmarshal(oxPublicKeyList.Query, &oxPublicKeyListXML)
iqReply, err := sendIQ(client, jid, "set", string(opk))
if err != nil {
return nil, err
}
if iqReply.Type != "result" {
return nil, errors.New("Error whil publishing public key.")
}
fingerprint := "none"
newestKey, err := time.Parse(time.RFC3339, "1900-01-01T06:06:06Z")
// TODO: Split GetPublicKey in GetPublicKeyList and GetPublicKey
// and use the GetPublicKey here as we haven't yet published a device list.
// The key list will only be published after successfully verifiying the
// upload.
ownPubKeyFromPubsub, err := oxRecvPublicKey(client, jid, fingerprint)
if err != nil {
return nil, errors.New("Couldn't successfully verify public key upload.")
}
ownPubKeyFromPubsubSerialized, err := ownPubKeyFromPubsub.Serialize()
if err != nil {
return nil, errors.New("Couldn't successfully verify public key upload.")
}
if pubKeyBase64 != base64.StdEncoding.EncodeToString(ownPubKeyFromPubsubSerialized) {
return nil, errors.New("Couldn't successfully verify public key upload.")
}
iqOxPublishKeyList.Xmlns = nsPubsub
iqOxPublishKeyList.Publish.Node = nsOxPubKeys
iqOxPublishKeyList.Publish.Item.PublicKeysList.Xmlns = nsOx
iqOxPublishKeyList.Publish.Item.PublicKeysList.PubkeyMetadata[0].V4Fingerprint = fingerprint
iqOxPublishKeyList.Publish.Item.PublicKeysList.PubkeyMetadata[0].Date =
time.Now().Format("1900-01-01T06:06:06Z")
iqOxPublishKeyList.PublishOptions.X.Xmlns = nsJabberData
iqOxPublishKeyList.PublishOptions.X.Field[0].Var = "FORM_TYPE"
iqOxPublishKeyList.PublishOptions.X.Field[0].Type = "hidden"
iqOxPublishKeyList.PublishOptions.X.Field[0].Value = pubsubPubOptions
iqOxPublishKeyList.PublishOptions.X.Field[1].Var = "pubsub#acces_model"
iqOxPublishKeyList.PublishOptions.X.Field[1].Value = "open"
opkl, err := xml.Marshal(iqOxPublishKeyList)
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
fingerprint = r.V4Fingerprint
}
iqReply, err = sendIQ(client, jid, "set", string(opkl))
if err != nil {
return nil, err
}
if fingerprint == "none" {
return nil, errors.New("server didn't provide public key fingerprints for " + recipient)
if iqReply.Type != "result" {
return nil, errors.New("Error while publishing public key list.")
}
return key, nil
}
func oxRecvPublicKey(client *xmpp.Client, recipient string, fingerprint string) (*crypto.Key, error) {
var oxPublicKeyRequest IQPubsubRequest
var oxPublicKeyXML OxPublicKey
oxPublicKeyRequest.Xmlns = nsPubsub
oxPublicKeyRequest.Items.Node = nsOxPubKeys + ":" + fingerprint
oxPublicKeyRequest.Items.MaxItems = "1"
@ -93,6 +199,56 @@ func oxGetPublicKey(client *xmpp.Client, recipient string) (*crypto.Key, error)
return key, nil
}
func oxGetPublicKey(client *xmpp.Client, recipient string) (*crypto.Key, error) {
var oxPublicKeyListRequest IQPubsubRequest
var oxPublicKeyListXML OxPublicKeysList
oxPublicKeyListRequest.Xmlns = nsPubsub
oxPublicKeyListRequest.Items.Node = nsOxPubKeys
oxPublicKeyListRequest.Items.MaxItems = "100"
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
}
fingerprint := "none"
newestKey, err := time.Parse(time.RFC3339, "1900-01-01T06:06:06Z")
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
fingerprint = r.V4Fingerprint
}
}
if fingerprint == "none" {
return nil, errors.New("server didn't provide public key fingerprints for " + recipient)
}
key, err := oxRecvPublicKey(client, recipient, fingerprint)
if err != nil {
return nil, errors.New("Couldn't fetch public key.")
}
return key, nil
}
func oxEncrypt(client *xmpp.Client, recipient string, message string) (string, error) {
var oxCryptMessage OxCryptElement
var oxMessage OxMessageElement

Loading…
Cancel
Save