From 757c88caa6dd374b021d3075f3eea77a766d2759 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sat, 25 Nov 2023 23:41:03 -0700 Subject: [PATCH 1/5] Add signing key generation and conversion tooling Will be used by MSC3916 implementation. --- .dockerignore | 1 + .gitignore | 1 + cmd/utilities/generate_signing_key/main.go | 103 +++++++++++++++++++ homeserver_interop/any_server/signing_key.go | 49 +++++++++ homeserver_interop/dendrite/signing_key.go | 57 ++++++++++ homeserver_interop/mmr/signing_key.go | 63 ++++++++++++ homeserver_interop/synapse/signing_key.go | 49 +++++++++ test/signing_anyserver_test.go | 48 +++++++++ test/signing_dendrite_test.go | 61 +++++++++++ test/signing_mmr_test.go | 51 +++++++++ test/signing_synapse_test.go | 56 ++++++++++ 11 files changed, 539 insertions(+) create mode 100644 cmd/utilities/generate_signing_key/main.go create mode 100644 homeserver_interop/any_server/signing_key.go create mode 100644 homeserver_interop/dendrite/signing_key.go create mode 100644 homeserver_interop/mmr/signing_key.go create mode 100644 homeserver_interop/synapse/signing_key.go create mode 100644 test/signing_anyserver_test.go create mode 100644 test/signing_dendrite_test.go create mode 100644 test/signing_mmr_test.go create mode 100644 test/signing_synapse_test.go diff --git a/.dockerignore b/.dockerignore index 20fa8b8c..33e5598b 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,4 @@ +*.key /webui /.idea /bin diff --git a/.gitignore b/.gitignore index 20fa8b8c..33e5598b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +*.key /webui /.idea /bin diff --git a/cmd/utilities/generate_signing_key/main.go b/cmd/utilities/generate_signing_key/main.go new file mode 100644 index 00000000..90d3c979 --- /dev/null +++ b/cmd/utilities/generate_signing_key/main.go @@ -0,0 +1,103 @@ +package main + +import ( + "crypto/ed25519" + "crypto/rand" + "flag" + "fmt" + "os" + "sort" + "strings" + + "github.com/sirupsen/logrus" + "github.com/turt2live/matrix-media-repo/homeserver_interop/any_server" + "github.com/turt2live/matrix-media-repo/homeserver_interop/dendrite" + "github.com/turt2live/matrix-media-repo/homeserver_interop/mmr" + "github.com/turt2live/matrix-media-repo/homeserver_interop/synapse" +) + +func main() { + inputFile := flag.String("input", "", "When set to a file path, the signing key to convert to the output format. The key must have been generated in a format supported by -format.") + outputFormat := flag.String("format", "mmr", "The output format for the key. May be 'mmr', 'synapse', or 'dendrite'.") + outputFile := flag.String("output", "./signing.key", "The output file for the key.") + flag.Parse() + + var keyVersion string + var priv ed25519.PrivateKey + var err error + + if *inputFile != "" { + priv, keyVersion, err = decodeKey(*inputFile) + } else { + keyVersion = makeKeyVersion() + _, priv, err = ed25519.GenerateKey(nil) + priv = priv[len(priv)-32:] + } + if err != nil { + logrus.Fatal(err) + } + + logrus.Infof("Key ID will be 'ed25519:%s'", keyVersion) + + var b []byte + switch *outputFormat { + case "synapse": + b, err = synapse.EncodeSigningKey(keyVersion, priv) + break + case "dendrite": + b, err = dendrite.EncodeSigningKey(keyVersion, priv) + break + case "mmr": + b, err = mmr.EncodeSigningKey(keyVersion, priv) + break + default: + logrus.Fatalf("Unknown output format '%s'. Try '%s -help' for information.", *outputFormat, flag.Arg(0)) + } + + f, err := os.Create(*outputFile) + if err != nil { + logrus.Fatal(err) + } + defer func(f *os.File) { + _ = f.Close() + }(f) + + _, err = f.Write(b) + if err != nil { + logrus.Fatal(err) + } + + logrus.Infof("Done! Signing key written to '%s' in %s format", f.Name(), *outputFormat) +} + +func makeKeyVersion() string { + buf := make([]byte, 2) + chars := strings.Split("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", "") + for i := 0; i < len(chars); i++ { + sort.Slice(chars, func(i int, j int) bool { + c, err := rand.Read(buf) + + // "should never happen" clauses + if err != nil { + panic(err) + } + if c != len(buf) || c != 2 { + panic(fmt.Sprintf("crypto rand read %d bytes, expected %d", c, len(buf))) + } + + return buf[0] < buf[1] + }) + } + + return strings.Join(chars[:6], "") +} + +func decodeKey(fileName string) (ed25519.PrivateKey, string, error) { + f, err := os.Open(fileName) + if err != nil { + return nil, "", err + } + defer f.Close() + + return any_server.DecodeSigningKey(f) +} diff --git a/homeserver_interop/any_server/signing_key.go b/homeserver_interop/any_server/signing_key.go new file mode 100644 index 00000000..41e99549 --- /dev/null +++ b/homeserver_interop/any_server/signing_key.go @@ -0,0 +1,49 @@ +package any_server + +import ( + "crypto/ed25519" + "errors" + "io" + + "github.com/turt2live/matrix-media-repo/homeserver_interop/dendrite" + "github.com/turt2live/matrix-media-repo/homeserver_interop/mmr" + "github.com/turt2live/matrix-media-repo/homeserver_interop/synapse" +) + +func DecodeSigningKey(key io.ReadSeeker) (ed25519.PrivateKey, string, error) { + var keyVersion string + var priv ed25519.PrivateKey + var err error + + var errorStack error + + // Try Synapse first, as the most popular + priv, keyVersion, err = synapse.DecodeSigningKey(key) + if err == nil { + return priv, keyVersion, nil + } + errorStack = errors.Join(errors.New("synapse: unable to decode"), err, errorStack) + + // Rewind & try Dendrite + if _, err = key.Seek(0, io.SeekStart); err != nil { + return nil, "", err + } + priv, keyVersion, err = dendrite.DecodeSigningKey(key) + if err == nil { + return priv, keyVersion, nil + } + errorStack = errors.Join(errors.New("dendrite: unable to decode"), err, errorStack) + + // Rewind & try MMR + if _, err = key.Seek(0, io.SeekStart); err != nil { + return nil, "", err + } + priv, keyVersion, err = mmr.DecodeSigningKey(key) + if err == nil { + return priv, keyVersion, nil + } + errorStack = errors.Join(errors.New("mmr: unable to decode"), err, errorStack) + + // Fail case + return nil, "", errors.Join(errors.New("unable to detect signing key format"), errorStack) +} diff --git a/homeserver_interop/dendrite/signing_key.go b/homeserver_interop/dendrite/signing_key.go new file mode 100644 index 00000000..e3c8ce70 --- /dev/null +++ b/homeserver_interop/dendrite/signing_key.go @@ -0,0 +1,57 @@ +package dendrite + +import ( + "bytes" + "crypto/ed25519" + "encoding/pem" + "fmt" + "io" + "strings" +) + +const blockType = "MATRIX PRIVATE KEY" + +func EncodeSigningKey(keyVersion string, key ed25519.PrivateKey) ([]byte, error) { + block := &pem.Block{ + Type: blockType, + Headers: map[string]string{ + "Key-ID": fmt.Sprintf("ed25519:%s", keyVersion), + }, + Bytes: key.Seed(), + } + return pem.EncodeToMemory(block), nil +} + +func DecodeSigningKey(key io.Reader) (ed25519.PrivateKey, string, error) { + b, err := io.ReadAll(key) + if err != nil { + return nil, "", err + } + + var block *pem.Block + for { + block, b = pem.Decode(b) + if b == nil { + return nil, "", fmt.Errorf("no signing key found") + } + if block == nil { + return nil, "", fmt.Errorf("unable to read suitable block from PEM file") + } + if block.Type == blockType { + keyId := block.Headers["Key-ID"] + if len(keyId) <= 0 { + return nil, "", fmt.Errorf("missing Key-ID header") + } + if !strings.HasPrefix(keyId, "ed25519:") { + return nil, "", fmt.Errorf("key ID '%s' does not denote an ed25519 private key", keyId) + } + + _, priv, err := ed25519.GenerateKey(bytes.NewReader(block.Bytes)) + if err != nil { + return nil, "", err + } + + return priv, keyId[len("ed25519:"):], nil + } + } +} diff --git a/homeserver_interop/mmr/signing_key.go b/homeserver_interop/mmr/signing_key.go new file mode 100644 index 00000000..9406286c --- /dev/null +++ b/homeserver_interop/mmr/signing_key.go @@ -0,0 +1,63 @@ +package mmr + +import ( + "bytes" + "crypto/ed25519" + "encoding/pem" + "fmt" + "io" + "strings" +) + +const blockType = "MMR PRIVATE KEY" + +func EncodeSigningKey(keyVersion string, key ed25519.PrivateKey) ([]byte, error) { + // Similar to Dendrite, but using a different block type and added Version header for future expansion + block := &pem.Block{ + Type: blockType, + Headers: map[string]string{ + "Key-ID": fmt.Sprintf("ed25519:%s", keyVersion), + "Version": "1", + }, + Bytes: key.Seed(), + } + return pem.EncodeToMemory(block), nil +} + +func DecodeSigningKey(key io.Reader) (ed25519.PrivateKey, string, error) { + b, err := io.ReadAll(key) + if err != nil { + return nil, "", err + } + + var block *pem.Block + for { + block, b = pem.Decode(b) + if b == nil { + return nil, "", fmt.Errorf("no signing key found") + } + if block == nil { + return nil, "", fmt.Errorf("unable to read suitable block from PEM file") + } + if block.Type == blockType { + version := block.Headers["Version"] + if version != "1" { + return nil, "", fmt.Errorf("unsupported MMR key format version") + } + + keyId := block.Headers["Key-ID"] + if len(keyId) <= 0 { + return nil, "", fmt.Errorf("missing Key-ID header") + } + if !strings.HasPrefix(keyId, "ed25519:") { + return nil, "", fmt.Errorf("key ID '%s' does not denote an ed25519 private key", keyId) + } + _, priv, err := ed25519.GenerateKey(bytes.NewReader(block.Bytes)) + if err != nil { + return nil, "", err + } + + return priv, keyId[len("ed25519:"):], nil + } + } +} diff --git a/homeserver_interop/synapse/signing_key.go b/homeserver_interop/synapse/signing_key.go new file mode 100644 index 00000000..bf25d64b --- /dev/null +++ b/homeserver_interop/synapse/signing_key.go @@ -0,0 +1,49 @@ +package synapse + +import ( + "bytes" + "crypto/ed25519" + "errors" + "fmt" + "io" + "strings" + + "github.com/turt2live/matrix-media-repo/util" +) + +func EncodeSigningKey(keyVersion string, key ed25519.PrivateKey) ([]byte, error) { + b64 := util.EncodeUnpaddedBase64ToString(key.Seed()) + return []byte(fmt.Sprintf("ed25519 %s %s", keyVersion, b64)), nil +} + +func DecodeSigningKey(key io.Reader) (ed25519.PrivateKey, string, error) { + b, err := io.ReadAll(key) + if err != nil { + return nil, "", err + } + + // See https://github.com/matrix-org/python-signedjson/blob/067ae81616573e8ceb627cc046d91b5b489bcc96/signedjson/key.py#L137-L150 + parts := strings.Split(string(b), " ") + if len(parts) != 3 { + return nil, "", fmt.Errorf("expected 3 parts to signing key, got %d", len(parts)) + } + + if parts[0] != "ed25519" { + return nil, "", fmt.Errorf("expected ed25519 signing key, got '%s'", parts[0]) + } + + keyVersion := parts[1] + keyB64 := parts[2] + + keyBytes, err := util.DecodeUnpaddedBase64String(keyB64) + if err != nil { + return nil, "", errors.Join(errors.New("expected base64 signing key part"), err) + } + + _, priv, err := ed25519.GenerateKey(bytes.NewReader(keyBytes)) + if err != nil { + return nil, "", err + } + + return priv, keyVersion, nil +} diff --git a/test/signing_anyserver_test.go b/test/signing_anyserver_test.go new file mode 100644 index 00000000..0f804aa1 --- /dev/null +++ b/test/signing_anyserver_test.go @@ -0,0 +1,48 @@ +package test + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/turt2live/matrix-media-repo/homeserver_interop/any_server" + "github.com/turt2live/matrix-media-repo/util" +) + +func TestAnyServerDecodeDendrite(t *testing.T) { + raw := `-----BEGIN MATRIX PRIVATE KEY----- +Key-ID: ed25519:1Pu3u3 + +1Pu3u3solToI2pTdsHA4wj05bANnzPwJoxPepw2he2s= +-----END MATRIX PRIVATE KEY----- +` + + priv, keyVersion, err := any_server.DecodeSigningKey(bytes.NewReader([]byte(raw))) + assert.NoError(t, err) + assert.Equal(t, "1Pu3u3", keyVersion) + assert.Equal(t, "1Pu3u3solToI2pTdsHA4wj05bANnzPwJoxPepw2he2u4Fq1IRsE7q7tI3C83BUUIPhcZpLSKQ8jU8yA/meWHdw", util.EncodeUnpaddedBase64ToString(priv)) +} + +func TestAnyServerDecodeSynapse(t *testing.T) { + raw := `ed25519 a_RVfN wdSWsTNSOmMuNA1Ej6JUyeNbiBEt5jexHmVs7mHKZVc` + + priv, keyVersion, err := any_server.DecodeSigningKey(bytes.NewReader([]byte(raw))) + assert.NoError(t, err) + assert.Equal(t, "a_RVfN", keyVersion) + assert.Equal(t, "wdSWsTNSOmMuNA1Ej6JUyeNbiBEt5jexHmVs7mHKZVc3XC3Hf2tee4KxuO3diGtvSOQ8j/MjmSmEhX1qLV6dbQ", util.EncodeUnpaddedBase64ToString(priv)) +} + +func TestAnyServerDecodeMMR(t *testing.T) { + raw := `-----BEGIN MMR PRIVATE KEY----- +Key-ID: ed25519:e5d0oC +Version: 1 + +PJt0OaIImDJk8P/PDb4TNQHgI/1AA1C+AaQaABxAcgc= +-----END MMR PRIVATE KEY----- +` + + priv, keyVersion, err := any_server.DecodeSigningKey(bytes.NewReader([]byte(raw))) + assert.NoError(t, err) + assert.Equal(t, "e5d0oC", keyVersion) + assert.Equal(t, "PJt0OaIImDJk8P/PDb4TNQHgI/1AA1C+AaQaABxAcgdOiF6RhfMvHtXNXwW0tCUjdexJ0+/UKOFVhjmtmYUK9Q", util.EncodeUnpaddedBase64ToString(priv)) +} diff --git a/test/signing_dendrite_test.go b/test/signing_dendrite_test.go new file mode 100644 index 00000000..5d4a595a --- /dev/null +++ b/test/signing_dendrite_test.go @@ -0,0 +1,61 @@ +package test + +import ( + "bytes" + "crypto/ed25519" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/turt2live/matrix-media-repo/database" + "github.com/turt2live/matrix-media-repo/homeserver_interop/dendrite" + "github.com/turt2live/matrix-media-repo/homeserver_interop/mmr" + "github.com/turt2live/matrix-media-repo/util" +) + +func TestDendriteSigningKeyRoundTrip(t *testing.T) { + raw := `-----BEGIN MATRIX PRIVATE KEY----- +Key-ID: ed25519:1Pu3u3 + +1Pu3u3solToI2pTdsHA4wj05bANnzPwJoxPepw2he2s= +-----END MATRIX PRIVATE KEY----- +` + original := bytes.NewBufferString(raw) + keyVersion := "1Pu3u3" + canonical, err := util.EncodeCanonicalJson(database.AnonymousJson{ + "old_verify_keys": database.AnonymousJson{}, + "server_name": "localhost", + "valid_until_ts": 1701584534175, + "verify_keys": database.AnonymousJson{ + "ed25519:1Pu3u3": database.AnonymousJson{ + "key": "uBatSEbBO6u7SNwvNwVFCD4XGaS0ikPI1PMgP5nlh3c", + }, + }, + }) + sigB64 := "ya8NhdqVGZp8vhEgmtfIdm7gIEiLpcbp/0H2m+36nto/mXLDaGulkaQB/p7iftksiboTg/yK4BAzjWO0zFz7DQ" + + if err != nil { + t.Fatal(err) + } + + parsedPriv, parsedKeyVer, err := dendrite.DecodeSigningKey(original) + assert.NoError(t, err) + assert.Equal(t, keyVersion, parsedKeyVer) + + parsedSigB64 := util.EncodeUnpaddedBase64ToString(ed25519.Sign(parsedPriv, canonical)) + assert.Equal(t, sigB64, parsedSigB64) + + // Encode and decode the key as MMR format and re-test signatures + mmrBytes, err := mmr.EncodeSigningKey(parsedKeyVer, parsedPriv) + assert.NoError(t, err) + parsedPriv, parsedKeyVer, err = mmr.DecodeSigningKey(bytes.NewReader(mmrBytes)) + assert.NoError(t, err) + assert.Equal(t, keyVersion, parsedKeyVer) + + parsedSigB64 = util.EncodeUnpaddedBase64ToString(ed25519.Sign(parsedPriv, canonical)) + assert.Equal(t, sigB64, parsedSigB64) + + // Encode as Dendrite and compare to test value + enc, err := dendrite.EncodeSigningKey(parsedKeyVer, parsedPriv) + assert.NoError(t, err) + assert.Equal(t, raw, string(enc)) +} diff --git a/test/signing_mmr_test.go b/test/signing_mmr_test.go new file mode 100644 index 00000000..614c87b4 --- /dev/null +++ b/test/signing_mmr_test.go @@ -0,0 +1,51 @@ +package test + +import ( + "bytes" + "crypto/ed25519" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/turt2live/matrix-media-repo/database" + "github.com/turt2live/matrix-media-repo/homeserver_interop/mmr" + "github.com/turt2live/matrix-media-repo/util" +) + +func TestMMRSigningKeyRoundTrip(t *testing.T) { + raw := `-----BEGIN MMR PRIVATE KEY----- +Key-ID: ed25519:e5d0oC +Version: 1 + +PJt0OaIImDJk8P/PDb4TNQHgI/1AA1C+AaQaABxAcgc= +-----END MMR PRIVATE KEY----- +` + original := bytes.NewBufferString(raw) + keyVersion := "e5d0oC" + canonical, err := util.EncodeCanonicalJson(database.AnonymousJson{ + "old_verify_keys": database.AnonymousJson{}, + "server_name": "localhost", + "valid_until_ts": 1700979986627, + "verify_keys": database.AnonymousJson{ + "ed25519:e5d0oC": database.AnonymousJson{ + "key": "TohekYXzLx7VzV8FtLQlI3XsSdPv1CjhVYY5rZmFCvU", + }, + }, + }) + sigB64 := "FRIe4KJ5kdnBXJgQCgC057YcHafHZmidNqYtSSWLU7QMgDu8uMHWcuPPack8zys1GeLdgS9d5YolmyOVQT9WDA" + + if err != nil { + t.Fatal(err) + } + + parsedPriv, parsedKeyVer, err := mmr.DecodeSigningKey(original) + assert.NoError(t, err) + assert.Equal(t, keyVersion, parsedKeyVer) + + parsedSigB64 := util.EncodeUnpaddedBase64ToString(ed25519.Sign(parsedPriv, canonical)) + assert.Equal(t, sigB64, parsedSigB64) + + // Encode as MMR again and compare to test value + enc, err := mmr.EncodeSigningKey(parsedKeyVer, parsedPriv) + assert.NoError(t, err) + assert.Equal(t, raw, string(enc)) +} diff --git a/test/signing_synapse_test.go b/test/signing_synapse_test.go new file mode 100644 index 00000000..4cc665b8 --- /dev/null +++ b/test/signing_synapse_test.go @@ -0,0 +1,56 @@ +package test + +import ( + "bytes" + "crypto/ed25519" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/turt2live/matrix-media-repo/database" + "github.com/turt2live/matrix-media-repo/homeserver_interop/mmr" + "github.com/turt2live/matrix-media-repo/homeserver_interop/synapse" + "github.com/turt2live/matrix-media-repo/util" +) + +func TestSynapseSigningKeyRoundTrip(t *testing.T) { + raw := "ed25519 a_RVfN wdSWsTNSOmMuNA1Ej6JUyeNbiBEt5jexHmVs7mHKZVc" + original := bytes.NewBufferString(raw) + keyVersion := "a_RVfN" + canonical, err := util.EncodeCanonicalJson(database.AnonymousJson{ + "old_verify_keys": database.AnonymousJson{}, + "server_name": "localhost", + "valid_until_ts": 1701065483311, + "verify_keys": database.AnonymousJson{ + "ed25519:a_RVfN": database.AnonymousJson{ + "key": "N1wtx39rXnuCsbjt3Yhrb0jkPI/zI5kphIV9ai1enW0", + }, + }, + }) + sigB64 := "hCcSfyiyMPZU93ysk+r62aC0nkbUKRgzwzRpPO85VUshILT64fg5mPykMUb/XU0G3Tr7/Qn8uTpdPkoZ3B+QDw" + + if err != nil { + t.Fatal(err) + } + + parsedPriv, parsedKeyVer, err := synapse.DecodeSigningKey(original) + assert.NoError(t, err) + assert.Equal(t, keyVersion, parsedKeyVer) + + parsedSigB64 := util.EncodeUnpaddedBase64ToString(ed25519.Sign(parsedPriv, canonical)) + assert.Equal(t, sigB64, parsedSigB64) + + // Encode and decode the key as MMR format and re-test signatures + mmrBytes, err := mmr.EncodeSigningKey(parsedKeyVer, parsedPriv) + assert.NoError(t, err) + parsedPriv, parsedKeyVer, err = mmr.DecodeSigningKey(bytes.NewReader(mmrBytes)) + assert.NoError(t, err) + assert.Equal(t, keyVersion, parsedKeyVer) + + parsedSigB64 = util.EncodeUnpaddedBase64ToString(ed25519.Sign(parsedPriv, canonical)) + assert.Equal(t, sigB64, parsedSigB64) + + // Encode as Synapse and compare to test value + enc, err := synapse.EncodeSigningKey(parsedKeyVer, parsedPriv) + assert.NoError(t, err) + assert.Equal(t, raw, string(enc)) +} From 50bf78041f4a6627a2cdaa9eacfeee790ee20147 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sat, 25 Nov 2023 23:43:36 -0700 Subject: [PATCH 2/5] Relicense my own utility as MIT --- util/unpadded_base64.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 util/unpadded_base64.go diff --git a/util/unpadded_base64.go b/util/unpadded_base64.go new file mode 100644 index 00000000..1ef1a576 --- /dev/null +++ b/util/unpadded_base64.go @@ -0,0 +1,13 @@ +package util + +import ( + "encoding/base64" +) + +func DecodeUnpaddedBase64String(val string) ([]byte, error) { + return base64.RawStdEncoding.DecodeString(val) +} + +func EncodeUnpaddedBase64ToString(val []byte) string { + return base64.RawStdEncoding.EncodeToString(val) +} From 3ba713704f281711039375828f86fd0f30e0b93b Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sat, 25 Nov 2023 23:44:54 -0700 Subject: [PATCH 3/5] also relicense canonical_json util --- util/canonical_json.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 util/canonical_json.go diff --git a/util/canonical_json.go b/util/canonical_json.go new file mode 100644 index 00000000..c514f311 --- /dev/null +++ b/util/canonical_json.go @@ -0,0 +1,20 @@ +package util + +import ( + "bytes" + "encoding/json" +) + +func EncodeCanonicalJson(obj map[string]interface{}) ([]byte, error) { + b, err := json.Marshal(obj) + if err != nil { + return nil, err + } + + // De-encode values + b = bytes.Replace(b, []byte("\\u003c"), []byte("<"), -1) + b = bytes.Replace(b, []byte("\\u003e"), []byte(">"), -1) + b = bytes.Replace(b, []byte("\\u0026"), []byte("&"), -1) + + return b, nil +} From 361d8cdcef2fd6a8585b687740ee7616688a0c0e Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sat, 25 Nov 2023 23:48:07 -0700 Subject: [PATCH 4/5] appease the linter --- cmd/utilities/generate_signing_key/main.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/utilities/generate_signing_key/main.go b/cmd/utilities/generate_signing_key/main.go index 90d3c979..4935a8c7 100644 --- a/cmd/utilities/generate_signing_key/main.go +++ b/cmd/utilities/generate_signing_key/main.go @@ -43,16 +43,16 @@ func main() { switch *outputFormat { case "synapse": b, err = synapse.EncodeSigningKey(keyVersion, priv) - break case "dendrite": b, err = dendrite.EncodeSigningKey(keyVersion, priv) - break case "mmr": b, err = mmr.EncodeSigningKey(keyVersion, priv) - break default: logrus.Fatalf("Unknown output format '%s'. Try '%s -help' for information.", *outputFormat, flag.Arg(0)) } + if err != nil { + logrus.Fatal(err) + } f, err := os.Create(*outputFile) if err != nil { From 7912bfb775e0c49868dc4e82ffa27cac64e0a4e7 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sat, 25 Nov 2023 23:53:42 -0700 Subject: [PATCH 5/5] don't forget the relicensed test too --- test/canonical_json_test.go | 114 ++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 test/canonical_json_test.go diff --git a/test/canonical_json_test.go b/test/canonical_json_test.go new file mode 100644 index 00000000..918137ab --- /dev/null +++ b/test/canonical_json_test.go @@ -0,0 +1,114 @@ +package test + +import ( + "testing" + + "github.com/turt2live/matrix-media-repo/util" +) + +func TestEncodeCanonicalJson_CaseA(t *testing.T) { + input := map[string]interface{}{} + expectedOutput := []byte("{}") + actualOutput, _ := util.EncodeCanonicalJson(input) + compareBytes(expectedOutput, actualOutput, t) +} + +func TestEncodeCanonicalJson_CaseB(t *testing.T) { + input := map[string]interface{}{ + "one": 1, + "two": "Two", + } + expectedOutput := []byte("{\"one\":1,\"two\":\"Two\"}") + actualOutput, _ := util.EncodeCanonicalJson(input) + compareBytes(expectedOutput, actualOutput, t) +} + +func TestEncodeCanonicalJson_CaseC(t *testing.T) { + input := map[string]interface{}{ + "b": "2", + "a": "1", + } + expectedOutput := []byte("{\"a\":\"1\",\"b\":\"2\"}") + actualOutput, _ := util.EncodeCanonicalJson(input) + compareBytes(expectedOutput, actualOutput, t) +} + +func TestEncodeCanonicalJson_CaseD(t *testing.T) { + input := map[string]interface{}{ + "auth": map[string]interface{}{ + "success": true, + "mxid": "@john.doe:example.com", + "profile": map[string]interface{}{ + "display_name": "John Doe", + "three_pids": []map[string]interface{}{ + { + "medium": "email", + "address": "john.doe@example.org", + }, + { + "medium": "msisdn", + "address": "123456789", + }, + }, + }, + }, + } + expectedOutput := []byte("{\"auth\":{\"mxid\":\"@john.doe:example.com\",\"profile\":{\"display_name\":\"John Doe\",\"three_pids\":[{\"address\":\"john.doe@example.org\",\"medium\":\"email\"},{\"address\":\"123456789\",\"medium\":\"msisdn\"}]},\"success\":true}}") + actualOutput, _ := util.EncodeCanonicalJson(input) + compareBytes(expectedOutput, actualOutput, t) +} + +func TestEncodeCanonicalJson_CaseE(t *testing.T) { + input := map[string]interface{}{ + "a": "日本語", + } + expectedOutput := []byte("{\"a\":\"日本語\"}") + actualOutput, _ := util.EncodeCanonicalJson(input) + compareBytes(expectedOutput, actualOutput, t) +} + +func TestEncodeCanonicalJson_CaseF(t *testing.T) { + input := map[string]interface{}{ + "本": 2, + "日": 1, + } + expectedOutput := []byte("{\"日\":1,\"本\":2}") + actualOutput, _ := util.EncodeCanonicalJson(input) + compareBytes(expectedOutput, actualOutput, t) +} + +func TestEncodeCanonicalJson_CaseG(t *testing.T) { + input := map[string]interface{}{ + "a": "\u65E5", + } + expectedOutput := []byte("{\"a\":\"日\"}") + actualOutput, _ := util.EncodeCanonicalJson(input) + compareBytes(expectedOutput, actualOutput, t) +} + +func TestEncodeCanonicalJson_CaseH(t *testing.T) { + input := map[string]interface{}{ + "a": nil, + } + expectedOutput := []byte("{\"a\":null}") + actualOutput, _ := util.EncodeCanonicalJson(input) + compareBytes(expectedOutput, actualOutput, t) +} + +func compareBytes(expected []byte, actual []byte, t *testing.T) { + if len(expected) != len(actual) { + t.Errorf("Mismatched length: %d != %d", len(actual), len(expected)) + t.Fail() + return + } + + for i := range expected { + e := expected[i] + a := actual[i] + if e != a { + t.Errorf("Expected %b but got %b at index %d", e, a, i) + t.Fail() + return + } + } +}