diff --git a/go.mod b/go.mod index e2da18e..9475d72 100644 --- a/go.mod +++ b/go.mod @@ -18,11 +18,11 @@ require ( github.com/zyedidia/clipboard v1.0.3 go.etcd.io/bbolt v1.3.5 golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 - golang.org/x/net v0.0.0-20201022231255-08b38378de70 + golang.org/x/net v0.0.0-20201026091529-146b70c837a4 gopkg.in/toast.v1 v1.0.0-20180812000517-0a84660828b2 gopkg.in/vansante/go-ffprobe.v2 v2.0.2 gopkg.in/yaml.v2 v2.3.0 - maunium.net/go/mautrix v0.7.13 + maunium.net/go/mautrix v0.8.0-rc.2 maunium.net/go/mauview v0.1.2 maunium.net/go/tcell v0.2.0 ) diff --git a/go.sum b/go.sum index ec29e65..5e4601a 100644 --- a/go.sum +++ b/go.sum @@ -1,57 +1,63 @@ github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= -github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI= -github.com/alecthomas/chroma v0.8.0 h1:HS+HE97sgcqjQGu5uVr8jIE55Mmh5UeQ7kckAhHg2pY= -github.com/alecthomas/chroma v0.8.0/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM= github.com/alecthomas/chroma v0.8.1 h1:ym20sbvyC6RXz45u4qDglcgr8E313oPROshcuCHqiEE= github.com/alecthomas/chroma v0.8.1/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM= -github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo= github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0= github.com/alecthomas/kong v0.2.4/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE= -github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY= github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts= +github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ= github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk= github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/gabriel-vasile/mimetype v1.1.1 h1:qbN9MPuRf3bstHu9zkI9jDWNfH//9+9kHxr9oRBBBOA= github.com/gabriel-vasile/mimetype v1.1.1/go.mod h1:6CDPel/o/3/s4+bp6kIbsWATq8pmgOisOPG40CJa6To= github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/kyokomi/emoji v2.2.2+incompatible h1:gaQFbK2+uSxOR4iGZprJAbpmtqTrHhSdgOyIMD6Oidc= -github.com/kyokomi/emoji v2.2.2+incompatible/go.mod h1:mZ6aGCD7yk8j6QY6KICwnZ2pxoszVseX1DNoGtU2tBA= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/kyokomi/emoji/v2 v2.2.5 h1:sxOmQKMB3ICTDWiJbtMHUnKn1HFHjGk9av0+IYWVovI= github.com/kyokomi/emoji/v2 v2.2.5/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE= github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY= github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lithammer/fuzzysearch v1.1.0 h1:go9v8tLCrNTTlH42OAaq4eHFe81TDHEnlrMEb6R4f+A= -github.com/lithammer/fuzzysearch v1.1.0/go.mod h1:Bqx4wo8lTOFcJr3ckpY6HA9lEIOO0H5HrkJ5CsN56HQ= github.com/lithammer/fuzzysearch v1.1.1 h1:8F9OAV2xPuYblToVohjanztdnPjbtA0MLgMvDKQ0Z08= github.com/lithammer/fuzzysearch v1.1.1/go.mod h1:H2bng+w5gsR7NlfIJM8ElGZI0sX6C/9uzGqicVXGU6c= github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac= github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA= github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= -github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ= github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -59,15 +65,12 @@ github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sasha-s/go-deadlock v0.2.0 h1:lMqc+fUb7RrFS3gQLtoQsJ7/6TV/pAIFvBsqX73DK8Y= github.com/sasha-s/go-deadlock v0.2.0/go.mod h1:StQn567HiB1fF2yJ44N9au7wOhrPS3iZqiDbRupzT10= -github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/tidwall/gjson v1.6.0 h1:9VEQWz6LLMUsUl6PueE49ir4Ka6CzLymOAZDxpFsTDc= github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= @@ -78,35 +81,31 @@ github.com/tidwall/pretty v1.0.1 h1:WE4RBSZ1x6McVVC8S/Md+Qse8YUv6HRObAx6ke00NY8= github.com/tidwall/pretty v1.0.1/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/sjson v1.1.1 h1:7h1vk049Jnd5EH9NyzNiEuwYW4b5qgreBbqRC19AS3U= github.com/tidwall/sjson v1.1.1/go.mod h1:yvVuSnpEQv5cYIrO+AT6kw4QVfd5SDZoGIS7/5+fZFs= -github.com/zyedidia/clipboard v0.0.0-20200421031010-7c45b8673834 h1:0nOfq3JwYRiY3+nwfWVQYEaXDmGCQgj3RKoqTifLzP4= github.com/zyedidia/clipboard v0.0.0-20200421031010-7c45b8673834/go.mod h1:zykFnZUXX0ErxqvYLUFEq7QDJKId8rmh2FgD0/Y8cjA= github.com/zyedidia/clipboard v1.0.3 h1:F/nCDVYMdbDWTmY8s8cJl0tnwX32q96IF09JHM14bUI= github.com/zyedidia/clipboard v1.0.3/go.mod h1:zykFnZUXX0ErxqvYLUFEq7QDJKId8rmh2FgD0/Y8cjA= -github.com/zyedidia/poller v1.0.1 h1:Tt9S3AxAjXwWGNiC2TUdRJkQDZSzCBNVQ4xXiQ7440s= github.com/zyedidia/poller v1.0.1/go.mod h1:vZXJOHGDcuK08GXhF6IAY0ZFd2WcgOR5DOTp84Uk5eE= -go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg= -go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E= +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20200430140353-33d19683fad8 h1:6WW6V3x1P/jokJBpRQYUJnMHRP6isStQwCozxnU7XQw= -golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 h1:QelT11PB4FXiDEXucrfNckHoFxwt8USGY1ajP1ZF5lM= golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM= -golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201022231255-08b38378de70 h1:Z6x4N9mAi4oF0TbHweCsH618MO6OI6UFgV0FP5n0wBY= golang.org/x/net v0.0.0-20201022231255-08b38378de70/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201026091529-146b70c837a4 h1:awiuzyrRjJDb+OXi9ceHO3SDxVoN3JER57mhtqkdQBs= +golang.org/x/net v0.0.0-20201026091529-146b70c837a4/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -114,33 +113,27 @@ golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3 h1:5B6i6EAiSYyejWfvc5Rc9BbI3rzIsrrXfAQBWnYfn+w= golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/toast.v1 v1.0.0-20180812000517-0a84660828b2 h1:MZF6J7CV6s/h0HBkfqebrYfKCVEo5iN+wzE4QhV3Evo= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/toast.v1 v1.0.0-20180812000517-0a84660828b2/go.mod h1:s1Sn2yZos05Qfs7NKt867Xe18emOmtsO3eAKbDaon0o= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/vansante/go-ffprobe.v2 v2.0.2 h1:DdxSfFnlqeawPIVbIQEI6LR6OQHQNR7tNgWb2mWuC4w= gopkg.in/vansante/go-ffprobe.v2 v2.0.2/go.mod h1:qF0AlAjk7Nqzqf3y333Ly+KxN3cKF2JqA3JT5ZheUGE= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= maunium.net/go/maulogger/v2 v2.1.1/go.mod h1:TYWy7wKwz/tIXTpsx8G3mZseIRiC5DoMxSZazOHy68A= -maunium.net/go/mautrix v0.7.6 h1:jB9oCimPq0mVyolwQBC/9N1fu21AU+Ryq837cLf4gOo= -maunium.net/go/mautrix v0.7.6/go.mod h1:Va/74MijqaS0DQ3aUqxmFO54/PMfr1LVsCOcGRHbYmo= -maunium.net/go/mautrix v0.7.13 h1:qfnvLxvQafvLgHbdZF/+9qs9gyArYf8fUnzfQbjgQaU= -maunium.net/go/mautrix v0.7.13/go.mod h1:Jn0ijwXwMFvJFIN9IljirIVKpZQbZP/Dk7pdX2qDmXk= -maunium.net/go/mauview v0.1.1 h1:wfTXyPx3LGAGpTskh+UbBv/QItUWnEpaneHmywoYnfY= -maunium.net/go/mauview v0.1.1/go.mod h1:3QBUiuLct9moP1LgDhCGIg0Ovxn38Bd2sGndnUOuj4o= +maunium.net/go/mautrix v0.8.0-rc.2 h1:H1OieKdfTr4+i3lGgvydl+3z21CM4XgHzTrW8ozmsb8= +maunium.net/go/mautrix v0.8.0-rc.2/go.mod h1:TtVePxoEaw6+RZDKVajw66Yaj1lqLjH8l4FF3krsqWY= maunium.net/go/mauview v0.1.2 h1:6Y3GpyckIlzCNkry6k025YhWg8oh5XJFj3RAMf4VwWo= maunium.net/go/mauview v0.1.2/go.mod h1:3QBUiuLct9moP1LgDhCGIg0Ovxn38Bd2sGndnUOuj4o= maunium.net/go/tcell v0.2.0 h1:1Q0kN3wCOGAIGu1r3QHADsjSUOPDylKREvCv3EzJpVg= diff --git a/interface/matrix.go b/interface/matrix.go index 870b80d..9430621 100644 --- a/interface/matrix.go +++ b/interface/matrix.go @@ -51,6 +51,7 @@ type MatrixContainer interface { Login(user, password string) error Logout() + UIAFallback(authType mautrix.AuthType, sessionID string) error SendPreferencesToMatrix() PrepareMarkdownMessage(roomID id.RoomID, msgtype event.MessageType, text, html string, relation *Relation) *muksevt.Event @@ -82,6 +83,7 @@ type Crypto interface { Load() error FlushStore() error ProcessSyncResponse(resp *mautrix.RespSync, since string) + ProcessInRoomVerification(evt *event.Event) error HandleMemberEvent(*event.Event) DecryptMegolmEvent(*event.Event) (*event.Event, error) EncryptMegolmEvent(id.RoomID, event.Type, interface{}) (*event.EncryptedEventContent, error) diff --git a/matrix/matrix.go b/matrix/matrix.go index 19ba419..e89b523 100644 --- a/matrix/matrix.go +++ b/matrix/matrix.go @@ -585,7 +585,16 @@ func (c *Container) HandleEncrypted(source mautrix.EventSource, mxEvent *event.E c.HandleMessage(source, mxEvent) return } - c.HandleMessage(source, evt) + if evt.Type.IsInRoomVerification() { + err := c.crypto.ProcessInRoomVerification(evt) + if err != nil { + debug.Printf("[Crypto/Error] Failed to process in-room verification event %s of type %s: %v", evt.ID, evt.Type.String(), err) + } else { + debug.Printf("[Crypto/Debug] Processed in-room verification event %s of type %s", evt.ID, evt.Type.String()) + } + } else { + c.HandleMessage(source, evt) + } } // HandleMessage is the event handler for the m.room.message timeline event. @@ -744,14 +753,14 @@ func (c *Container) HandleReadReceipt(source mautrix.EventSource, evt *event.Eve } } -func (c *Container) parseDirectChatInfo(evt *event.Event) map[*rooms.Room]bool { - directChats := make(map[*rooms.Room]bool) - for _, roomIDList := range *evt.Content.AsDirectChats() { +func (c *Container) parseDirectChatInfo(evt *event.Event) map[*rooms.Room]id.UserID { + directChats := make(map[*rooms.Room]id.UserID) + for userID, roomIDList := range *evt.Content.AsDirectChats() { for _, roomID := range roomIDList { // TODO we shouldn't create direct chat rooms that we aren't in room := c.GetOrCreateRoom(roomID) if room != nil && !room.HasLeft { - directChats[room] = true + directChats[room] = userID } } } @@ -761,9 +770,10 @@ func (c *Container) parseDirectChatInfo(evt *event.Event) map[*rooms.Room]bool { func (c *Container) HandleDirectChatInfo(_ mautrix.EventSource, evt *event.Event) { directChats := c.parseDirectChatInfo(evt) for _, room := range c.config.Rooms.Map { - shouldBeDirect := directChats[room] - if shouldBeDirect != room.IsDirect { - room.IsDirect = shouldBeDirect + userID, isDirect := directChats[room] + if isDirect != room.IsDirect { + room.IsDirect = isDirect + room.OtherUser = userID if c.config.AuthCache.InitialSyncDone { c.ui.MainView().UpdateTags(room) } @@ -879,7 +889,7 @@ func (c *Container) prepareEvent(roomID id.RoomID, content *event.MessageEventCo Type: event.RelReplace, EventID: rel.Event.ID, } - } else if rel != nil && rel.Type == event.RelReference { + } else if rel != nil && rel.Type == event.RelReply { content.SetReply(rel.Event.Event) } diff --git a/matrix/rooms/room.go b/matrix/rooms/room.go index d5d1d8f..40913cf 100644 --- a/matrix/rooms/room.go +++ b/matrix/rooms/room.go @@ -93,7 +93,8 @@ type Room struct { highlightCache *bool lastMarkedRead id.EventID // Whether or not this room is marked as a direct chat. - IsDirect bool + IsDirect bool + OtherUser id.UserID // List of tags given to this room. RawTags []RoomTag diff --git a/matrix/uia-fallback.go b/matrix/uia-fallback.go new file mode 100644 index 0000000..6b2e42a --- /dev/null +++ b/matrix/uia-fallback.go @@ -0,0 +1,115 @@ +// gomuks - A terminal Matrix client written in Go. +// Copyright (C) 2020 Tulir Asokan +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package matrix + +import ( + "context" + "errors" + "net/http" + "net/url" + "time" + + "maunium.net/go/mautrix" + + "maunium.net/go/gomuks/debug" + "maunium.net/go/gomuks/lib/open" +) + +const uiaFallbackPage = ` + + + gomuks user-interactive auth + + + + +

Please complete the login in the popup window

+

Keep this page open while logging in, it will close automatically after the login finishes.

+ + + + + +` + +func (c *Container) UIAFallback(loginType mautrix.AuthType, sessionID string) error { + errChan := make(chan error, 1) + server := &http.Server{Addr: ":29325"} + server.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == "GET" { + w.Header().Add("Content-Type", "text/html") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(uiaFallbackPage)) + } else if r.Method == "POST" || r.Method == "DELETE" { + w.Header().Add("Content-Type", "text/html") + w.WriteHeader(http.StatusOK) + + go func() { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + err := server.Shutdown(ctx) + if err != nil { + debug.Printf("Failed to shut down SSO server: %v\n", err) + } + if r.Method == "DELETE" { + errChan <- errors.New("login cancelled") + } else { + errChan <- nil + } + }() + } else { + w.WriteHeader(http.StatusMethodNotAllowed) + } + }) + go server.ListenAndServe() + defer server.Close() + authURL := c.client.BuildURLWithQuery(mautrix.URLPath{"auth", loginType, "fallback", "web"}, map[string]string{ + "session": sessionID, + }) + link := url.URL{ + Scheme: "http", + Host: "localhost:29325", + Path: "/", + Fragment: authURL, + } + err := open.Open(link.String()) + if err != nil { + return err + } + err = <-errChan + return err +} diff --git a/ui/command-processor.go b/ui/command-processor.go index fcec03e..88dfafd 100644 --- a/ui/command-processor.go +++ b/ui/command-processor.go @@ -104,19 +104,23 @@ func NewCommandProcessor(parent *MainView) *CommandProcessor { "e": {"edit"}, "dl": {"download"}, "o": {"open"}, + "4s": {"ssss"}, + "s4": {"ssss"}, + "cs": {"cross-signing"}, }, autocompleters: map[string]CommandAutocompleter{ - "devices": autocompleteDevice, - "device": autocompleteDevice, - "verify": autocompleteDevice, - "unverify": autocompleteDevice, - "blacklist": autocompleteDevice, - "upload": autocompleteFile, - "download": autocompleteFile, - "open": autocompleteFile, - "import": autocompleteFile, - "export": autocompleteFile, - "export-room": autocompleteFile, + "devices": autocompleteUser, + "device": autocompleteDevice, + "verify": autocompleteUser, + "verify-device": autocompleteDevice, + "unverify": autocompleteDevice, + "blacklist": autocompleteDevice, + "upload": autocompleteFile, + "download": autocompleteFile, + "open": autocompleteFile, + "import": autocompleteFile, + "export": autocompleteFile, + "export-room": autocompleteFile, }, commands: map[string]CommandHandler{ "unknown-command": cmdUnknownCommand, @@ -164,6 +168,7 @@ func NewCommandProcessor(parent *MainView) *CommandProcessor { "fingerprint": cmdFingerprint, "devices": cmdDevices, + "verify-device": cmdVerifyDevice, "verify": cmdVerify, "device": cmdDevice, "unverify": cmdUnverify, @@ -172,6 +177,8 @@ func NewCommandProcessor(parent *MainView) *CommandProcessor { "import": cmdImportKeys, "export": cmdExportKeys, "export-room": cmdExportRoomKeys, + "ssss": cmdSSSS, + "cross-signing": cmdCrossSigning, }, } } diff --git a/ui/crypto-commands.go b/ui/crypto-commands.go index 6c8896f..8968392 100644 --- a/ui/crypto-commands.go +++ b/ui/crypto-commands.go @@ -19,6 +19,7 @@ package ui import ( + "errors" "fmt" "io/ioutil" "path/filepath" @@ -26,7 +27,10 @@ import ( "time" "unicode" + ifc "maunium.net/go/gomuks/interface" + "maunium.net/go/mautrix" "maunium.net/go/mautrix/crypto" + "maunium.net/go/mautrix/crypto/ssss" "maunium.net/go/mautrix/id" ) @@ -77,15 +81,20 @@ func autocompleteDeviceDeviceID(cmd *CommandAutocomplete) (completions []string, return } +func autocompleteUser(cmd *CommandAutocomplete) ([]string, string) { + if len(cmd.Args) == 1 && !unicode.IsSpace(rune(cmd.RawArgs[len(cmd.RawArgs)-1])) { + return autocompleteDeviceUserID(cmd) + } + return []string{}, "" +} + func autocompleteDevice(cmd *CommandAutocomplete) ([]string, string) { if len(cmd.Args) == 0 { return []string{}, "" } else if len(cmd.Args) == 1 && !unicode.IsSpace(rune(cmd.RawArgs[len(cmd.RawArgs)-1])) { return autocompleteDeviceUserID(cmd) - } else if cmd.Command != "devices" { - return autocompleteDeviceDeviceID(cmd) } - return []string{}, "" + return autocompleteDeviceDeviceID(cmd) } func getDevice(cmd *Command) *crypto.DeviceIdentity { @@ -134,7 +143,11 @@ func cmdDevices(cmd *Command) { } var buf strings.Builder for _, device := range devices { - _, _ = fmt.Fprintf(&buf, "%s (%s) - %s\n Fingerprint: %s\n", device.DeviceID, device.Name, device.Trust.String(), device.Fingerprint()) + trust := device.Trust.String() + if device.Trust == crypto.TrustStateUnset && mach.IsDeviceTrusted(device) { + trust = "verified (transitive)" + } + _, _ = fmt.Fprintf(&buf, "%s (%s) - %s\n Fingerprint: %s\n", device.DeviceID, device.Name, trust, device.Fingerprint()) } resp := buf.String() cmd.Reply("%s", resp[:len(resp)-1]) @@ -149,13 +162,28 @@ func cmdDevice(cmd *Command) { if device.Deleted { deviceType = "Deleted device" } + mach := cmd.Matrix.Crypto().(*crypto.OlmMachine) + trustState := device.Trust.String() + if device.Trust == crypto.TrustStateUnset && mach.IsDeviceTrusted(device) { + trustState = "verified (transitive)" + } cmd.Reply("%s %s of %s\nFingerprint: %s\nIdentity key: %s\nDevice name: %s\nTrust state: %s", deviceType, device.DeviceID, device.UserID, device.Fingerprint(), device.IdentityKey, - device.Name, device.Trust.String()) + device.Name, trustState) } -func cmdVerify(cmd *Command) { +func crossSignDevice(cmd *Command, device *crypto.DeviceIdentity) { + mach := cmd.Matrix.Crypto().(*crypto.OlmMachine) + err := mach.SignOwnDevice(device) + if err != nil { + cmd.Reply("Failed to upload cross-signing signature: %v", err) + } else { + cmd.Reply("Successfully cross-signed %s (%s)", device.DeviceID, device.Name) + } +} + +func cmdVerifyDevice(cmd *Command) { device := getDevice(cmd) if device == nil { return @@ -184,11 +212,49 @@ func cmdVerify(cmd *Command) { if device.Trust == crypto.TrustStateBlacklisted { action = "unblacklisted and verified" } - device.Trust = crypto.TrustStateVerified - putDevice(cmd, device, action) + if device.UserID == cmd.Matrix.Client().UserID { + crossSignDevice(cmd, device) + device.Trust = crypto.TrustStateVerified + putDevice(cmd, device, action) + } else { + putDevice(cmd, device, action) + cmd.Reply("Warning: verifying individual devices of other users is not synced with cross-signing") + } } } +func cmdVerify(cmd *Command) { + if len(cmd.Args) < 1 { + cmd.Reply("Usage: /%s [--force]", cmd.OrigCommand) + return + } + force := len(cmd.Args) >= 2 && strings.ToLower(cmd.Args[1]) == "--force" + userID := id.UserID(cmd.Args[0]) + room := cmd.Room.Room + if !room.Encrypted { + cmd.Reply("In-room verification is only supported in encrypted rooms") + return + } + if (!room.IsDirect || room.OtherUser != userID) && !force { + cmd.Reply("This doesn't seem to be a direct chat. Either switch to a direct chat with %s, "+ + "or use `--force` to start the verification anyway.", userID) + return + } + mach := cmd.Matrix.Crypto().(*crypto.OlmMachine) + if mach.CrossSigningKeys == nil && !force { + cmd.Reply("Cross-signing private keys not cached. Generate or fetch cross-signing keys with `/cross-signing`, " + + "or use `--force` to start the verification anyway") + return + } + modal := NewVerificationModal(cmd.MainView, &crypto.DeviceIdentity{UserID: userID}, mach.DefaultSASTimeout) + _, err := mach.NewInRoomSASVerificationWith(cmd.Room.Room.ID, userID, modal, 120*time.Second) + if err != nil { + cmd.Reply("Failed to start in-room verification: %v", err) + return + } + cmd.MainView.ShowModal(modal) +} + func cmdUnverify(cmd *Command) { device := getDevice(cmd) if device == nil { @@ -243,7 +309,7 @@ func cmdImportKeys(cmd *Command) { cmd.Reply("Failed to read %s: %v", path, err) return } - passphrase, ok := cmd.MainView.AskPassword("Key import", false) + passphrase, ok := cmd.MainView.AskPassword("Key import", "passphrase", "", false) if !ok { cmd.Reply("Passphrase entry cancelled") return @@ -263,7 +329,7 @@ func exportKeys(cmd *Command, sessions []*crypto.InboundGroupSession) { cmd.Reply("Failed to get absolute path: %v", err) return } - passphrase, ok := cmd.MainView.AskPassword("Key export", true) + passphrase, ok := cmd.MainView.AskPassword("Key export", "passphrase", "", true) if !ok { cmd.Reply("Passphrase entry cancelled") return @@ -299,3 +365,333 @@ func cmdExportRoomKeys(cmd *Command) { } exportKeys(cmd, sessions) } + +const ssssHelp = `Usage: /%s [...] + +Subcommands: +* status [key ID] - Check the status of your SSSS. +* generate [--set-default] - Generate a SSSS key and optionally set it as the default. +* set-default - Set a SSSS key as the default.` + +func cmdSSSS(cmd *Command) { + if len(cmd.Args) == 0 { + cmd.Reply(ssssHelp, cmd.OrigCommand) + return + } + + mach := cmd.Matrix.Crypto().(*crypto.OlmMachine) + + switch strings.ToLower(cmd.Args[0]) { + case "status": + keyID := "" + if len(cmd.Args) > 1 { + keyID = cmd.Args[1] + } + cmdS4Status(cmd, mach, keyID) + case "generate": + setDefault := len(cmd.Args) > 1 && strings.ToLower(cmd.Args[1]) == "--set-default" + cmdS4Generate(cmd, mach, setDefault) + case "set-default": + if len(cmd.Args) < 2 { + cmd.Reply("Usage: /%s set-default ", cmd.OrigCommand) + return + } + cmdS4SetDefault(cmd, mach, cmd.Args[1]) + default: + cmd.Reply(ssssHelp, cmd.OrigCommand) + } +} + +func cmdS4Status(cmd *Command, mach *crypto.OlmMachine, keyID string) { + var keyData *ssss.KeyMetadata + var err error + if len(keyID) == 0 { + keyID, keyData, err = mach.SSSS.GetDefaultKeyData() + } else { + keyData, err = mach.SSSS.GetKeyData(keyID) + } + if errors.Is(err, ssss.ErrNoDefaultKeyAccountDataEvent) { + cmd.Reply("SSSS is not set up: no default key set") + return + } else if err != nil { + cmd.Reply("Failed to get key data: %v", err) + return + } + hasPassphrase := "no" + if keyData.Passphrase != nil { + hasPassphrase = fmt.Sprintf("yes (alg=%s,bits=%d,iter=%d)", keyData.Passphrase.Algorithm, keyData.Passphrase.Bits, keyData.Passphrase.Iterations) + } + algorithm := keyData.Algorithm + if algorithm != ssss.AlgorithmAESHMACSHA2 { + algorithm += " (not supported!)" + } + cmd.Reply("Default key is set.\n Key ID: %s\n Has passphrase: %s\n Algorithm: %s", keyID, hasPassphrase, algorithm) +} + +func cmdS4Generate(cmd *Command, mach *crypto.OlmMachine, setDefault bool) { + passphrase, ok := cmd.MainView.AskPassword("Passphrase", "", "", true) + if !ok { + return + } + + key, err := ssss.NewKey(passphrase) + if err != nil { + cmd.Reply("Failed to generate new key: %v", err) + return + } + + err = mach.SSSS.SetKeyData(key.ID, key.Metadata) + if err != nil { + cmd.Reply("Failed to upload key metadata: %v", err) + return + } + + // TODO if we start persisting command replies, the recovery key needs to be moved into a popup + cmd.Reply("Successfully generated key %s\nRecovery key: %s", key.ID, key.RecoveryKey()) + + if setDefault { + err = mach.SSSS.SetDefaultKeyID(key.ID) + if err != nil { + cmd.Reply("Failed to set key as default: %v", err) + } + } else { + cmd.Reply("You can use `/%s set-default %s` to set it as the default", cmd.OrigCommand, key.ID) + } +} + +func cmdS4SetDefault(cmd *Command, mach *crypto.OlmMachine, keyID string) { + _, err := mach.SSSS.GetKeyData(keyID) + if err != nil { + if errors.Is(err, mautrix.MNotFound) { + cmd.Reply("Couldn't find key data on server") + } else { + cmd.Reply("Failed to fetch key data: %v", err) + } + return + } + + err = mach.SSSS.SetDefaultKeyID(keyID) + if err != nil { + cmd.Reply("Failed to set key as default: %v", err) + } else { + cmd.Reply("Successfully set key %s as default", keyID) + } +} + +const crossSigningHelp = `Usage: /%s [...] + +Subcommands: +* status + Check the status of your own cross-signing keys. +* generate [--force] + Generate and upload new cross-signing keys. + This will prompt you to enter your account password. + If you already have existing keys, --force is required. +* self-sign + Sign the current device with cached cross-signing keys. +* fetch [--save-to-disk] + Fetch your cross-signing keys from SSSS and decrypt them. + If --save-to-disk is specified, the keys are saved to disk. +* upload + Upload your cross-signing keys to SSSS.` + +func cmdCrossSigning(cmd *Command) { + if len(cmd.Args) == 0 { + cmd.Reply(crossSigningHelp, cmd.OrigCommand) + return + } + + client := cmd.Matrix.Client() + mach := cmd.Matrix.Crypto().(*crypto.OlmMachine) + + switch strings.ToLower(cmd.Args[0]) { + case "status": + cmdCrossSigningStatus(cmd, mach) + case "generate": + force := len(cmd.Args) > 1 && strings.ToLower(cmd.Args[1]) == "--force" + cmdCrossSigningGenerate(cmd, cmd.Matrix, mach, client, force) + case "fetch": + saveToDisk := len(cmd.Args) > 1 && strings.ToLower(cmd.Args[1]) == "--save-to-disk" + cmdCrossSigningFetch(cmd, mach, saveToDisk) + case "upload": + cmdCrossSigningUpload(cmd, mach) + case "self-sign": + cmdCrossSigningSelfSign(cmd, mach) + default: + cmd.Reply(crossSigningHelp, cmd.OrigCommand) + } +} + +func cmdCrossSigningStatus(cmd *Command, mach *crypto.OlmMachine) { + keys := mach.GetOwnCrossSigningPublicKeys() + if keys == nil { + if mach.CrossSigningKeys != nil { + cmd.Reply("Cross-signing keys are cached, but not published") + } else { + cmd.Reply("Didn't find published cross-signing keys") + } + return + } + if mach.CrossSigningKeys != nil { + cmd.Reply("Cross-signing keys are published and private keys are cached") + } else { + cmd.Reply("Cross-signing keys are published, but private keys are not cached") + } + cmd.Reply("Master key: %s", keys.MasterKey) + cmd.Reply("User signing key: %s", keys.UserSigningKey) + cmd.Reply("Self-signing key: %s", keys.SelfSigningKey) +} + +func cmdCrossSigningFetch(cmd *Command, mach *crypto.OlmMachine, saveToDisk bool) { + key := getSSSS(cmd, mach) + if key == nil { + return + } + + err := mach.FetchCrossSigningKeysFromSSSS(key) + if err != nil { + cmd.Reply("Error fetching cross-signing keys: %v", err) + return + } + if saveToDisk { + cmd.Reply("Saving keys to disk is not yet implemented") + } + cmd.Reply("Successfully unlocked cross-signing keys") +} + +func cmdCrossSigningGenerate(cmd *Command, container ifc.MatrixContainer, mach *crypto.OlmMachine, client *mautrix.Client, force bool) { + if !force { + existingKeys := mach.GetOwnCrossSigningPublicKeys() + if existingKeys != nil { + cmd.Reply("Found existing cross-signing keys. Use `--force` if you want to overwrite them.") + return + } + } + + keys, err := mach.GenerateCrossSigningKeys() + if err != nil { + cmd.Reply("Failed to generate cross-signing keys: %v", err) + return + } + + err = mach.PublishCrossSigningKeys(keys, func(uia *mautrix.RespUserInteractive) interface{} { + if !uia.HasSingleStageFlow(mautrix.AuthTypePassword) { + for _, flow := range uia.Flows { + if len(flow.Stages) != 1 { + return nil + } + cmd.Reply("Opening browser for authentication") + err := container.UIAFallback(flow.Stages[0], uia.Session) + if err != nil { + cmd.Reply("Authentication failed: %v", err) + return nil + } + return &mautrix.ReqUIAuthFallback{ + Session: uia.Session, + User: mach.Client.UserID.String(), + } + } + cmd.Reply("No supported authentication mechanisms found") + return nil + } + password, ok := cmd.MainView.AskPassword("Account password", "", "correct horse battery staple", false) + if !ok { + return nil + } + return &mautrix.ReqUIAuthLogin{ + BaseAuthData: mautrix.BaseAuthData{ + Type: mautrix.AuthTypePassword, + Session: uia.Session, + }, + User: mach.Client.UserID.String(), + Password: password, + } + }) + if err != nil { + cmd.Reply("Failed to publish cross-signing keys: %v", err) + return + } + cmd.Reply("Successfully generated and published cross-signing keys") + + err = mach.SignOwnMasterKey() + if err != nil { + cmd.Reply("Failed to sign master key with device key: %v", err) + } +} + +func getSSSS(cmd *Command, mach *crypto.OlmMachine) *ssss.Key { + _, keyData, err := mach.SSSS.GetDefaultKeyData() + if err != nil { + if errors.Is(err, mautrix.MNotFound) { + cmd.Reply("SSSS not set up, use `!ssss generate --set-default` first") + } else { + cmd.Reply("Failed to fetch default SSSS key data: %v", err) + } + return nil + } + + var key *ssss.Key + if keyData.Passphrase != nil && keyData.Passphrase.Algorithm == ssss.PassphraseAlgorithmPBKDF2 { + passphrase, ok := cmd.MainView.AskPassword("Passphrase", "", "correct horse battery staple", false) + if !ok { + return nil + } + key, err = keyData.VerifyPassphrase(passphrase) + if errors.Is(err, ssss.ErrIncorrectSSSSKey) { + cmd.Reply("Incorrect passphrase") + return nil + } + } else { + recoveryKey, ok := cmd.MainView.AskPassword("Recovery key", "", "tDAK LMRH PiYE bdzi maCe xLX5 wV6P Nmfd c5mC wLef 15Fs VVSc", false) + if !ok { + return nil + } + key, err = keyData.VerifyRecoveryKey(recoveryKey) + if errors.Is(err, ssss.ErrInvalidRecoveryKey) { + cmd.Reply("Malformed recovery key") + return nil + } else if errors.Is(err, ssss.ErrIncorrectSSSSKey) { + cmd.Reply("Incorrect recovery key") + return nil + } + } + // All the errors should already be handled above, this is just for backup + if err != nil { + cmd.Reply("Failed to get SSSS key: %v", err) + return nil + } + return key +} + +func cmdCrossSigningUpload(cmd *Command, mach *crypto.OlmMachine) { + if mach.CrossSigningKeys == nil { + cmd.Reply("Cross-signing keys not cached, use `!%s generate` first", cmd.OrigCommand) + return + } + + key := getSSSS(cmd, mach) + if key == nil { + return + } + + err := mach.UploadCrossSigningKeysToSSSS(key, mach.CrossSigningKeys) + if err != nil { + cmd.Reply("Failed to upload keys to SSSS: %v", err) + } else { + cmd.Reply("Successfully uploaded cross-signing keys to SSSS") + } +} + +func cmdCrossSigningSelfSign(cmd *Command, mach *crypto.OlmMachine) { + if mach.CrossSigningKeys == nil { + cmd.Reply("Cross-signing keys not cached") + return + } + + err := mach.SignOwnDevice(mach.OwnIdentity()) + if err != nil { + cmd.Reply("Failed to self-sign: %v", err) + } else { + cmd.Reply("Successfully self-signed. This device is now trusted by other devices") + } +} diff --git a/ui/no-crypto-commands.go b/ui/no-crypto-commands.go index 2d1529d..3669d5a 100644 --- a/ui/no-crypto-commands.go +++ b/ui/no-crypto-commands.go @@ -29,6 +29,7 @@ func cmdNoCrypto(cmd *Command) { var ( cmdDevices = cmdNoCrypto cmdDevice = cmdNoCrypto + cmdVerifyDevice = cmdNoCrypto cmdVerify = cmdNoCrypto cmdUnverify = cmdNoCrypto cmdBlacklist = cmdNoCrypto diff --git a/ui/password-modal.go b/ui/password-modal.go index afda7b5..4a9f25f 100644 --- a/ui/password-modal.go +++ b/ui/password-modal.go @@ -17,6 +17,9 @@ package ui import ( + "fmt" + "strings" + "maunium.net/go/mauview" "maunium.net/go/tcell" ) @@ -41,13 +44,20 @@ type PasswordModal struct { parent *MainView } -func (view *MainView) AskPassword(title string, isNew bool) (string, bool) { - pwm := NewPasswordModal(view, title, isNew) +func (view *MainView) AskPassword(title, thing, placeholder string, isNew bool) (string, bool) { + pwm := NewPasswordModal(view, title, thing, placeholder, isNew) view.ShowModal(pwm) + view.parent.Render() return pwm.Wait() } -func NewPasswordModal(parent *MainView, title string, isNew bool) *PasswordModal { +func NewPasswordModal(parent *MainView, title, thing, placeholder string, isNew bool) *PasswordModal { + if placeholder == "" { + placeholder = "correct horse battery staple" + } + if thing == "" { + thing = strings.ToLower(title) + } pwm := &PasswordModal{ parent: parent, form: mauview.NewForm(), @@ -64,13 +74,13 @@ func NewPasswordModal(parent *MainView, title string, isNew bool) *PasswordModal pwm.text = mauview.NewTextField() if isNew { - pwm.text.SetText("Create a passphrase") + pwm.text.SetText(fmt.Sprintf("Create a %s", thing)) } else { - pwm.text.SetText("Enter the passphrase") + pwm.text.SetText(fmt.Sprintf("Enter the %s", thing)) } pwm.input = mauview.NewInputField(). SetMaskCharacter('*'). - SetPlaceholder("correct horse battery staple") + SetPlaceholder(placeholder) pwm.form.AddComponent(pwm.text, 1, 1, 3, 1) pwm.form.AddFormItem(pwm.input, 1, 2, 3, 1) @@ -78,10 +88,10 @@ func NewPasswordModal(parent *MainView, title string, isNew bool) *PasswordModal height += 3 pwm.confirmInput = mauview.NewInputField(). SetMaskCharacter('*'). - SetPlaceholder("correct horse battery staple"). + SetPlaceholder(placeholder). SetChangedFunc(pwm.HandleChange) pwm.input.SetChangedFunc(pwm.HandleChange) - pwm.confirmText = mauview.NewTextField().SetText("Confirm passphrase") + pwm.confirmText = mauview.NewTextField().SetText(fmt.Sprintf("Confirm %s", thing)) pwm.form.SetRow(3, 1).SetRow(4, 1).SetRow(5, 1) pwm.form.AddComponent(pwm.confirmText, 1, 4, 3, 1) @@ -125,9 +135,9 @@ func (pwm *PasswordModal) ClickSubmit() { func (pwm *PasswordModal) Wait() (string, bool) { select { - case result := <- pwm.outputChan: + case result := <-pwm.outputChan: return result, true - case <- pwm.cancelChan: + case <-pwm.cancelChan: return "", false } } diff --git a/ui/room-view.go b/ui/room-view.go index 26daa93..c3d14f9 100644 --- a/ui/room-view.go +++ b/ui/room-view.go @@ -771,7 +771,7 @@ func (view *RoomView) getRelationForNewEvent() *ifc.Relation { } } else if view.replying != nil { return &ifc.Relation{ - Type: event.RelReference, + Type: event.RelReply, Event: view.replying, } } diff --git a/ui/verification-modal.go b/ui/verification-modal.go index bc529c9..aabc36a 100644 --- a/ui/verification-modal.go +++ b/ui/verification-modal.go @@ -148,7 +148,8 @@ func (vm *VerificationModal) VerificationMethods() []crypto.VerificationMethod { return []crypto.VerificationMethod{crypto.VerificationMethodEmoji{}, crypto.VerificationMethodDecimal{}} } -func (vm *VerificationModal) VerifySASMatch(_ *crypto.DeviceIdentity, data crypto.SASData) bool { +func (vm *VerificationModal) VerifySASMatch(device *crypto.DeviceIdentity, data crypto.SASData) bool { + vm.device = device var typeName string if data.Type() == event.SASDecimal { typeName = "numbers"