From 3b4be900000cc79bba4a0d98295b4afe9afea116 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 10 Jun 2020 16:29:02 +0100 Subject: [PATCH] Improvements to Yggdrasil demo (#1114) * Improvements to Yggdrasil demo * Fix missing copyright * Fix tie-break --- cmd/dendrite-demo-yggdrasil/convert/25519.go | 53 ++++++++++++++ .../convert/25519_test.go | 51 ++++++++++++++ cmd/dendrite-demo-yggdrasil/main.go | 25 ++++--- .../signing/fetcher.go | 69 +++++++++++++++++++ cmd/dendrite-demo-yggdrasil/yggconn/node.go | 28 ++++++-- .../yggconn/session.go | 8 +-- 6 files changed, 217 insertions(+), 17 deletions(-) create mode 100644 cmd/dendrite-demo-yggdrasil/convert/25519.go create mode 100644 cmd/dendrite-demo-yggdrasil/convert/25519_test.go create mode 100644 cmd/dendrite-demo-yggdrasil/signing/fetcher.go diff --git a/cmd/dendrite-demo-yggdrasil/convert/25519.go b/cmd/dendrite-demo-yggdrasil/convert/25519.go new file mode 100644 index 000000000..97f053ec0 --- /dev/null +++ b/cmd/dendrite-demo-yggdrasil/convert/25519.go @@ -0,0 +1,53 @@ +// Copyright 2019 Google LLC +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd +// +// Original code from https://github.com/FiloSottile/age/blob/bbab440e198a4d67ba78591176c7853e62d29e04/internal/age/ssh.go + +package convert + +import ( + "crypto/ed25519" + "crypto/sha512" + "math/big" + + "golang.org/x/crypto/curve25519" +) + +var curve25519P, _ = new(big.Int).SetString("57896044618658097711785492504343953926634992332820282019728792003956564819949", 10) + +func Ed25519PrivateKeyToCurve25519(pk ed25519.PrivateKey) []byte { + h := sha512.New() + _, _ = h.Write(pk.Seed()) + out := h.Sum(nil) + return out[:curve25519.ScalarSize] +} + +func Ed25519PublicKeyToCurve25519(pk ed25519.PublicKey) []byte { + // ed25519.PublicKey is a little endian representation of the y-coordinate, + // with the most significant bit set based on the sign of the x-coordinate. + bigEndianY := make([]byte, ed25519.PublicKeySize) + for i, b := range pk { + bigEndianY[ed25519.PublicKeySize-i-1] = b + } + bigEndianY[0] &= 0b0111_1111 + + // The Montgomery u-coordinate is derived through the bilinear map + // u = (1 + y) / (1 - y) + // See https://blog.filippo.io/using-ed25519-keys-for-encryption. + y := new(big.Int).SetBytes(bigEndianY) + denom := big.NewInt(1) + denom.ModInverse(denom.Sub(denom, y), curve25519P) // 1 / (1 - y) + u := y.Mul(y.Add(y, big.NewInt(1)), denom) + u.Mod(u, curve25519P) + + out := make([]byte, curve25519.PointSize) + uBytes := u.Bytes() + for i, b := range uBytes { + out[len(uBytes)-i-1] = b + } + + return out +} diff --git a/cmd/dendrite-demo-yggdrasil/convert/25519_test.go b/cmd/dendrite-demo-yggdrasil/convert/25519_test.go new file mode 100644 index 000000000..22177b8b4 --- /dev/null +++ b/cmd/dendrite-demo-yggdrasil/convert/25519_test.go @@ -0,0 +1,51 @@ +// Copyright 2020 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package convert + +import ( + "bytes" + "crypto/ed25519" + "encoding/hex" + "testing" + + "golang.org/x/crypto/curve25519" +) + +func TestKeyConversion(t *testing.T) { + edPub, edPriv, err := ed25519.GenerateKey(nil) + if err != nil { + t.Fatal(err) + } + t.Log("Signing public:", hex.EncodeToString(edPub)) + t.Log("Signing private:", hex.EncodeToString(edPriv)) + + cuPriv := Ed25519PrivateKeyToCurve25519(edPriv) + t.Log("Encryption private:", hex.EncodeToString(cuPriv)) + + cuPub := Ed25519PublicKeyToCurve25519(edPub) + t.Log("Converted encryption public:", hex.EncodeToString(cuPub)) + + var realPub, realPriv [32]byte + copy(realPriv[:32], cuPriv[:32]) + curve25519.ScalarBaseMult(&realPub, &realPriv) + t.Log("Scalar-multed encryption public:", hex.EncodeToString(realPub[:])) + + if !bytes.Equal(realPriv[:], cuPriv[:]) { + t.Fatal("Private keys should be equal (this means the test is broken)") + } + if !bytes.Equal(realPub[:], cuPub[:]) { + t.Fatal("Public keys should be equal") + } +} diff --git a/cmd/dendrite-demo-yggdrasil/main.go b/cmd/dendrite-demo-yggdrasil/main.go index 62c561290..94e94d8d3 100644 --- a/cmd/dendrite-demo-yggdrasil/main.go +++ b/cmd/dendrite-demo-yggdrasil/main.go @@ -16,7 +16,9 @@ package main import ( "context" + "crypto/ed25519" "crypto/tls" + "encoding/hex" "flag" "fmt" "net" @@ -25,6 +27,8 @@ import ( "time" "github.com/matrix-org/dendrite/appservice" + "github.com/matrix-org/dendrite/cmd/dendrite-demo-yggdrasil/convert" + "github.com/matrix-org/dendrite/cmd/dendrite-demo-yggdrasil/signing" "github.com/matrix-org/dendrite/cmd/dendrite-demo-yggdrasil/yggconn" "github.com/matrix-org/dendrite/eduserver" "github.com/matrix-org/dendrite/eduserver/cache" @@ -35,7 +39,6 @@ import ( "github.com/matrix-org/dendrite/internal/setup" "github.com/matrix-org/dendrite/publicroomsapi/storage" "github.com/matrix-org/dendrite/roomserver" - "github.com/matrix-org/dendrite/serverkeyapi" "github.com/matrix-org/gomatrixserverlib" "github.com/sirupsen/logrus" @@ -61,7 +64,13 @@ func createFederationClient( ) *gomatrixserverlib.FederationClient { yggdialer := func(_, address string) (net.Conn, error) { tokens := strings.Split(address, ":") - return n.Dial("curve25519", tokens[0]) + raw, err := hex.DecodeString(tokens[0]) + if err != nil { + return nil, fmt.Errorf("hex.DecodeString: %w", err) + } + converted := convert.Ed25519PublicKeyToCurve25519(ed25519.PublicKey(raw)) + convhex := hex.EncodeToString(converted) + return n.Dial("curve25519", convhex) } yggdialerctx := func(ctx context.Context, network, address string) (net.Conn, error) { return yggdialer(network, address) @@ -102,9 +111,9 @@ func main() { cfg := &config.Dendrite{} cfg.SetDefaults() - cfg.Matrix.ServerName = gomatrixserverlib.ServerName(ygg.EncryptionPublicKey()) + cfg.Matrix.ServerName = gomatrixserverlib.ServerName(ygg.DerivedServerName()) cfg.Matrix.PrivateKey = ygg.SigningPrivateKey() - cfg.Matrix.KeyID = gomatrixserverlib.KeyID("ed25519:auto") + cfg.Matrix.KeyID = gomatrixserverlib.KeyID(signing.KeyID) cfg.Kafka.UseNaffka = true cfg.Kafka.Topics.OutputRoomEvent = "roomserverOutput" cfg.Kafka.Topics.OutputClientData = "clientapiOutput" @@ -130,9 +139,7 @@ func main() { deviceDB := base.CreateDeviceDB() federation := createFederationClient(base, ygg) - serverKeyAPI := serverkeyapi.NewInternalAPI( - base.Cfg, federation, base.Caches, - ) + serverKeyAPI := &signing.YggdrasilKeys{} keyRing := serverKeyAPI.KeyRing() rsComponent := roomserver.NewInternalAPI( @@ -170,7 +177,7 @@ func main() { EDUInternalAPI: eduInputAPI, FederationSenderAPI: fsAPI, RoomserverAPI: rsAPI, - ServerKeyAPI: serverKeyAPI, + //ServerKeyAPI: serverKeyAPI, PublicRoomsDB: publicRoomsDB, } @@ -185,7 +192,7 @@ func main() { ) go func() { - logrus.Info("Listening on ", ygg.EncryptionPublicKey()) + logrus.Info("Listening on ", ygg.DerivedServerName()) logrus.Fatal(httpServer.Serve(ygg)) }() go func() { diff --git a/cmd/dendrite-demo-yggdrasil/signing/fetcher.go b/cmd/dendrite-demo-yggdrasil/signing/fetcher.go new file mode 100644 index 000000000..bcec0cbec --- /dev/null +++ b/cmd/dendrite-demo-yggdrasil/signing/fetcher.go @@ -0,0 +1,69 @@ +// Copyright 2020 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package signing + +import ( + "context" + "encoding/hex" + "fmt" + "time" + + "github.com/matrix-org/gomatrixserverlib" +) + +const KeyID = "ed25519:dendrite-demo-yggdrasil" + +type YggdrasilKeys struct { +} + +func (f *YggdrasilKeys) KeyRing() *gomatrixserverlib.KeyRing { + return &gomatrixserverlib.KeyRing{ + KeyDatabase: f, + } +} + +func (f *YggdrasilKeys) FetchKeys( + ctx context.Context, + requests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp, +) (map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, error) { + res := make(map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult) + for req := range requests { + if req.KeyID != KeyID { + return nil, fmt.Errorf("FetchKeys: cannot fetch key with ID %s, should be %s", req.KeyID, KeyID) + } + + hexkey, err := hex.DecodeString(string(req.ServerName)) + if err != nil { + return nil, fmt.Errorf("FetchKeys: can't decode server name %q: %w", req.ServerName, err) + } + + res[req] = gomatrixserverlib.PublicKeyLookupResult{ + VerifyKey: gomatrixserverlib.VerifyKey{ + Key: hexkey, + }, + ExpiredTS: gomatrixserverlib.PublicKeyNotExpired, + ValidUntilTS: gomatrixserverlib.AsTimestamp(time.Now().Add(24 * time.Hour * 365)), + } + } + return res, nil +} + +func (f *YggdrasilKeys) FetcherName() string { + return "YggdrasilKeys" +} + +func (f *YggdrasilKeys) StoreKeys(ctx context.Context, results map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult) error { + return nil +} diff --git a/cmd/dendrite-demo-yggdrasil/yggconn/node.go b/cmd/dendrite-demo-yggdrasil/yggconn/node.go index 517f7f794..14bbf84d7 100644 --- a/cmd/dendrite-demo-yggdrasil/yggconn/node.go +++ b/cmd/dendrite-demo-yggdrasil/yggconn/node.go @@ -24,6 +24,8 @@ import ( "os" "sync" + "github.com/matrix-org/dendrite/cmd/dendrite-demo-yggdrasil/convert" + "github.com/libp2p/go-yamux" yggdrasiladmin "github.com/yggdrasil-network/yggdrasil-go/src/admin" yggdrasilconfig "github.com/yggdrasil-network/yggdrasil-go/src/config" @@ -56,8 +58,6 @@ func Setup(instanceName, instancePeer string) (*Node, error) { log: gologme.New(os.Stdout, "YGG ", log.Flags()), incoming: make(chan *yamux.Stream), } - n.config.AdminListen = fmt.Sprintf("unix://./%s-yggdrasil.sock", instanceName) - n.config.MulticastInterfaces = []string{".*"} yggfile := fmt.Sprintf("%s-yggdrasil.conf", instanceName) if _, err := os.Stat(yggfile); !os.IsNotExist(err) { @@ -69,6 +69,11 @@ func Setup(instanceName, instancePeer string) (*Node, error) { panic(err) } } else { + n.config.AdminListen = fmt.Sprintf("unix://./%s-yggdrasil.sock", instanceName) + n.config.MulticastInterfaces = []string{".*"} + n.config.EncryptionPrivateKey = hex.EncodeToString(n.EncryptionPrivateKey()) + n.config.EncryptionPublicKey = hex.EncodeToString(n.EncryptionPublicKey()) + j, err := json.MarshalIndent(n.config, "", " ") if err != nil { panic(err) @@ -119,8 +124,23 @@ func Setup(instanceName, instancePeer string) (*Node, error) { return n, nil } -func (n *Node) EncryptionPublicKey() string { - return n.core.EncryptionPublicKey() +func (n *Node) DerivedServerName() string { + return hex.EncodeToString(n.SigningPublicKey()) +} + +func (n *Node) EncryptionPublicKey() []byte { + edkey := n.SigningPublicKey() + return convert.Ed25519PublicKeyToCurve25519(edkey) +} + +func (n *Node) EncryptionPrivateKey() []byte { + edkey := n.SigningPrivateKey() + return convert.Ed25519PrivateKeyToCurve25519(edkey) +} + +func (n *Node) SigningPublicKey() ed25519.PublicKey { + pubBytes, _ := hex.DecodeString(n.config.SigningPublicKey) + return ed25519.PublicKey(pubBytes) } func (n *Node) SigningPrivateKey() ed25519.PrivateKey { diff --git a/cmd/dendrite-demo-yggdrasil/yggconn/session.go b/cmd/dendrite-demo-yggdrasil/yggconn/session.go index 4e9a9f043..a9bb15f7e 100644 --- a/cmd/dendrite-demo-yggdrasil/yggconn/session.go +++ b/cmd/dendrite-demo-yggdrasil/yggconn/session.go @@ -40,10 +40,10 @@ func (n *Node) listenFromYgg() { return } var session *yamux.Session - if strings.Compare(n.EncryptionPublicKey(), conn.RemoteAddr().String()) < 0 { - session, err = yamux.Client(conn, n.yamuxConfig()) - } else { + if strings.Compare(conn.RemoteAddr().String(), n.DerivedServerName()) < 0 { session, err = yamux.Server(conn, n.yamuxConfig()) + } else { + session, err = yamux.Client(conn, n.yamuxConfig()) } if err != nil { return @@ -96,7 +96,7 @@ func (n *Node) DialContext(ctx context.Context, network, address string) (net.Co n.log.Println("n.dialer.DialContext:", err) return nil, err } - if strings.Compare(n.EncryptionPublicKey(), address) < 0 { + if strings.Compare(address, n.DerivedServerName()) > 0 { session, err = yamux.Client(conn, n.yamuxConfig()) } else { session, err = yamux.Server(conn, n.yamuxConfig())