From 50de519e6d23db065a4e5f202e8d50c159a1975a Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sun, 26 Nov 2023 02:14:19 -0700 Subject: [PATCH] Support Synapse's (and technically Dendrite's) multi-key format --- cmd/utilities/_common/signing_key_export.go | 44 ++++++ cmd/utilities/combine_signing_keys/main.go | 50 +++++++ cmd/utilities/generate_signing_key/main.go | 55 +++----- homeserver_interop/any_server/signing_key.go | 34 +++-- homeserver_interop/dendrite/signing_key.go | 48 +++++-- .../internal/signing_key_encode.go | 37 +++++ homeserver_interop/mmr/signing_key.go | 54 +++++-- homeserver_interop/signing_key.go | 10 ++ homeserver_interop/synapse/signing_key.go | 70 ++++++--- test/signing_anyserver_test.go | 18 +-- test/signing_dendrite_test.go | 133 ++++++++++++++++-- test/signing_mmr_test.go | 108 +++++++++++++- test/signing_synapse_test.go | 110 +++++++++++++-- 13 files changed, 642 insertions(+), 129 deletions(-) create mode 100644 cmd/utilities/_common/signing_key_export.go create mode 100644 cmd/utilities/combine_signing_keys/main.go create mode 100644 homeserver_interop/internal/signing_key_encode.go create mode 100644 homeserver_interop/signing_key.go diff --git a/cmd/utilities/_common/signing_key_export.go b/cmd/utilities/_common/signing_key_export.go new file mode 100644 index 00000000..9fa68685 --- /dev/null +++ b/cmd/utilities/_common/signing_key_export.go @@ -0,0 +1,44 @@ +package _common + +import ( + "os" + + "github.com/sirupsen/logrus" + "github.com/turt2live/matrix-media-repo/homeserver_interop" + "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 EncodeSigningKeys(keys []*homeserver_interop.SigningKey, format string, file string) { + var err error + var b []byte + switch format { + case "synapse": + b, err = synapse.EncodeAllSigningKeys(keys) + case "dendrite": + b, err = dendrite.EncodeAllSigningKeys(keys) + case "mmr": + b, err = mmr.EncodeAllSigningKeys(keys) + default: + logrus.Fatalf("Unknown output format '%s'. Try '%s -help' for information.", format, os.Args[0]) + } + if err != nil { + logrus.Fatal(err) + } + + f, err := os.Create(file) + 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(), format) +} diff --git a/cmd/utilities/combine_signing_keys/main.go b/cmd/utilities/combine_signing_keys/main.go new file mode 100644 index 00000000..a576f7a9 --- /dev/null +++ b/cmd/utilities/combine_signing_keys/main.go @@ -0,0 +1,50 @@ +package main + +import ( + "flag" + "os" + + "github.com/sirupsen/logrus" + "github.com/turt2live/matrix-media-repo/cmd/utilities/_common" + "github.com/turt2live/matrix-media-repo/homeserver_interop" + "github.com/turt2live/matrix-media-repo/homeserver_interop/any_server" + "github.com/turt2live/matrix-media-repo/util" +) + +func main() { + 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. Note that not all software will use multiple keys.") + flag.Parse() + + keys := make(map[string]*homeserver_interop.SigningKey) + keysArray := make([]*homeserver_interop.SigningKey, 0) + for _, file := range flag.Args() { + logrus.Infof("Reading %s", file) + + localKeys, err := decodeKeys(file) + if err != nil { + logrus.Fatal(err) + } + + for _, key := range localKeys { + if val, ok := keys[key.KeyVersion]; ok { + logrus.Fatalf("Duplicate key version '%s' detected. Known='%s', duplicate='%s'", key.KeyVersion, util.EncodeUnpaddedBase64ToString(val.PrivateKey), util.EncodeUnpaddedBase64ToString(key.PrivateKey)) + } + + keys[key.KeyVersion] = key + keysArray = append(keysArray, key) + } + } + + _common.EncodeSigningKeys(keysArray, *outputFormat, *outputFile) +} + +func decodeKeys(fileName string) ([]*homeserver_interop.SigningKey, error) { + f, err := os.Open(fileName) + if err != nil { + return nil, err + } + defer f.Close() + + return any_server.DecodeAllSigningKeys(f) +} diff --git a/cmd/utilities/generate_signing_key/main.go b/cmd/utilities/generate_signing_key/main.go index 4935a8c7..26871462 100644 --- a/cmd/utilities/generate_signing_key/main.go +++ b/cmd/utilities/generate_signing_key/main.go @@ -10,64 +10,41 @@ import ( "strings" "github.com/sirupsen/logrus" + "github.com/turt2live/matrix-media-repo/cmd/utilities/_common" + "github.com/turt2live/matrix-media-repo/homeserver_interop" "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.") + 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. If the format supports multiple keys, only the first will be converted.") 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 key *homeserver_interop.SigningKey var err error if *inputFile != "" { - priv, keyVersion, err = decodeKey(*inputFile) + key, err = decodeKey(*inputFile) } else { - keyVersion = makeKeyVersion() + keyVersion := makeKeyVersion() + + var priv ed25519.PrivateKey _, 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) - case "dendrite": - b, err = dendrite.EncodeSigningKey(keyVersion, priv) - case "mmr": - b, err = mmr.EncodeSigningKey(keyVersion, priv) - default: - logrus.Fatalf("Unknown output format '%s'. Try '%s -help' for information.", *outputFormat, flag.Arg(0)) + key = &homeserver_interop.SigningKey{ + PrivateKey: priv, + KeyVersion: keyVersion, + } } if err != nil { logrus.Fatal(err) } - 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("Key ID will be 'ed25519:%s'", key.KeyVersion) - logrus.Infof("Done! Signing key written to '%s' in %s format", f.Name(), *outputFormat) + _common.EncodeSigningKeys([]*homeserver_interop.SigningKey{key}, *outputFormat, *outputFile) } func makeKeyVersion() string { @@ -92,10 +69,10 @@ func makeKeyVersion() string { return strings.Join(chars[:6], "") } -func decodeKey(fileName string) (ed25519.PrivateKey, string, error) { +func decodeKey(fileName string) (*homeserver_interop.SigningKey, error) { f, err := os.Open(fileName) if err != nil { - return nil, "", err + return nil, err } defer f.Close() diff --git a/homeserver_interop/any_server/signing_key.go b/homeserver_interop/any_server/signing_key.go index 41e99549..45c10a3b 100644 --- a/homeserver_interop/any_server/signing_key.go +++ b/homeserver_interop/any_server/signing_key.go @@ -1,49 +1,57 @@ package any_server import ( - "crypto/ed25519" "errors" "io" + "github.com/turt2live/matrix-media-repo/homeserver_interop" "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 +func DecodeSigningKey(key io.ReadSeeker) (*homeserver_interop.SigningKey, error) { + keys, err := DecodeAllSigningKeys(key) + if err != nil { + return nil, err + } + + return keys[0], nil +} + +func DecodeAllSigningKeys(key io.ReadSeeker) ([]*homeserver_interop.SigningKey, error) { + var keys []*homeserver_interop.SigningKey var err error var errorStack error // Try Synapse first, as the most popular - priv, keyVersion, err = synapse.DecodeSigningKey(key) + keys, err = synapse.DecodeAllSigningKeys(key) if err == nil { - return priv, keyVersion, nil + return keys, 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 + return nil, err } - priv, keyVersion, err = dendrite.DecodeSigningKey(key) + keys, err = dendrite.DecodeAllSigningKeys(key) if err == nil { - return priv, keyVersion, nil + return keys, 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 + return nil, err } - priv, keyVersion, err = mmr.DecodeSigningKey(key) + keys, err = mmr.DecodeAllSigningKeys(key) if err == nil { - return priv, keyVersion, nil + return keys, 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) + 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 index e3c8ce70..f33b4616 100644 --- a/homeserver_interop/dendrite/signing_key.go +++ b/homeserver_interop/dendrite/signing_key.go @@ -7,51 +7,75 @@ import ( "fmt" "io" "strings" + + "github.com/turt2live/matrix-media-repo/homeserver_interop" + "github.com/turt2live/matrix-media-repo/homeserver_interop/internal" ) const blockType = "MATRIX PRIVATE KEY" -func EncodeSigningKey(keyVersion string, key ed25519.PrivateKey) ([]byte, error) { +func EncodeSigningKey(key *homeserver_interop.SigningKey) ([]byte, error) { block := &pem.Block{ Type: blockType, Headers: map[string]string{ - "Key-ID": fmt.Sprintf("ed25519:%s", keyVersion), + "Key-ID": fmt.Sprintf("ed25519:%s", key.KeyVersion), }, - Bytes: key.Seed(), + Bytes: key.PrivateKey.Seed(), } return pem.EncodeToMemory(block), nil } -func DecodeSigningKey(key io.Reader) (ed25519.PrivateKey, string, error) { +func EncodeAllSigningKeys(keys []*homeserver_interop.SigningKey) ([]byte, error) { + return internal.EncodeNewlineAppendFormattedSigningKeys(keys, EncodeSigningKey) +} + +func DecodeSigningKey(key io.Reader) (*homeserver_interop.SigningKey, error) { + keys, err := DecodeAllSigningKeys(key) + if err != nil { + return nil, err + } + + return keys[0], nil +} + +func DecodeAllSigningKeys(key io.Reader) ([]*homeserver_interop.SigningKey, error) { b, err := io.ReadAll(key) if err != nil { - return nil, "", err + return nil, err } + keys := make([]*homeserver_interop.SigningKey, 0) var block *pem.Block for { block, b = pem.Decode(b) if b == nil { - return nil, "", fmt.Errorf("no signing key found") + 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 == nil && (len(keys) == 0 || len(b) > 0) { + return nil, fmt.Errorf("unable to read suitable block from PEM file") + } else if block == nil && len(b) == 0 { + break } if block.Type == blockType { keyId := block.Headers["Key-ID"] if len(keyId) <= 0 { - return nil, "", fmt.Errorf("missing Key-ID header") + 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) + 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 nil, err } - return priv, keyId[len("ed25519:"):], nil + keys = append(keys, &homeserver_interop.SigningKey{ + PrivateKey: priv, + KeyVersion: keyId[len("ed25519:"):], + }) } } + + return keys, nil } diff --git a/homeserver_interop/internal/signing_key_encode.go b/homeserver_interop/internal/signing_key_encode.go new file mode 100644 index 00000000..a0497efe --- /dev/null +++ b/homeserver_interop/internal/signing_key_encode.go @@ -0,0 +1,37 @@ +package internal + +import ( + "bytes" + "fmt" + + "github.com/turt2live/matrix-media-repo/homeserver_interop" +) + +func EncodeNewlineAppendFormattedSigningKeys(keys []*homeserver_interop.SigningKey, encodeFn func(*homeserver_interop.SigningKey) ([]byte, error)) ([]byte, error) { + buf := &bytes.Buffer{} + for i, key := range keys { + b, err := encodeFn(key) + if err != nil { + return nil, err + } + + n, err := buf.Write(b) + if err != nil { + return nil, err + } + if n != len(b) { + return nil, fmt.Errorf("wrote %d bytes but expected %d bytes", n, len(b)) + } + + if b[len(b)-1] != '\n' && i != (len(keys)-1) { + n, err = buf.Write([]byte{'\n'}) + if err != nil { + return nil, err + } + if n != 1 { + return nil, fmt.Errorf("wrote %d bytes but expected %d bytes", n, 1) + } + } + } + return buf.Bytes(), nil +} diff --git a/homeserver_interop/mmr/signing_key.go b/homeserver_interop/mmr/signing_key.go index 9406286c..695739b5 100644 --- a/homeserver_interop/mmr/signing_key.go +++ b/homeserver_interop/mmr/signing_key.go @@ -7,57 +7,83 @@ import ( "fmt" "io" "strings" + + "github.com/turt2live/matrix-media-repo/homeserver_interop" + "github.com/turt2live/matrix-media-repo/homeserver_interop/internal" ) const blockType = "MMR PRIVATE KEY" -func EncodeSigningKey(keyVersion string, key ed25519.PrivateKey) ([]byte, error) { +func EncodeSigningKey(key *homeserver_interop.SigningKey) ([]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), + "Key-ID": fmt.Sprintf("ed25519:%s", key.KeyVersion), "Version": "1", }, - Bytes: key.Seed(), + Bytes: key.PrivateKey.Seed(), + } + b := pem.EncodeToMemory(block) + return b, nil +} + +func EncodeAllSigningKeys(keys []*homeserver_interop.SigningKey) ([]byte, error) { + return internal.EncodeNewlineAppendFormattedSigningKeys(keys, EncodeSigningKey) +} + +func DecodeSigningKey(key io.Reader) (*homeserver_interop.SigningKey, error) { + keys, err := DecodeAllSigningKeys(key) + if err != nil { + return nil, err } - return pem.EncodeToMemory(block), nil + + return keys[0], nil } -func DecodeSigningKey(key io.Reader) (ed25519.PrivateKey, string, error) { +func DecodeAllSigningKeys(key io.Reader) ([]*homeserver_interop.SigningKey, error) { b, err := io.ReadAll(key) if err != nil { - return nil, "", err + return nil, err } + keys := make([]*homeserver_interop.SigningKey, 0) var block *pem.Block for { block, b = pem.Decode(b) if b == nil { - return nil, "", fmt.Errorf("no signing key found") + 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 == nil && (len(keys) == 0 || len(b) > 0) { + return nil, fmt.Errorf("unable to read suitable block from PEM file") + } else if block == nil && len(b) == 0 { + break } if block.Type == blockType { version := block.Headers["Version"] if version != "1" { - return nil, "", fmt.Errorf("unsupported MMR key format version") + 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") + 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) + 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 nil, err } - return priv, keyId[len("ed25519:"):], nil + keys = append(keys, &homeserver_interop.SigningKey{ + PrivateKey: priv, + KeyVersion: keyId[len("ed25519:"):], + }) } } + + return keys, nil } diff --git a/homeserver_interop/signing_key.go b/homeserver_interop/signing_key.go new file mode 100644 index 00000000..29c0af81 --- /dev/null +++ b/homeserver_interop/signing_key.go @@ -0,0 +1,10 @@ +package homeserver_interop + +import ( + "crypto/ed25519" +) + +type SigningKey struct { + PrivateKey ed25519.PrivateKey + KeyVersion string +} diff --git a/homeserver_interop/synapse/signing_key.go b/homeserver_interop/synapse/signing_key.go index bf25d64b..9b2dcf86 100644 --- a/homeserver_interop/synapse/signing_key.go +++ b/homeserver_interop/synapse/signing_key.go @@ -8,42 +8,68 @@ import ( "io" "strings" + "github.com/turt2live/matrix-media-repo/homeserver_interop" + "github.com/turt2live/matrix-media-repo/homeserver_interop/internal" "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 EncodeSigningKey(key *homeserver_interop.SigningKey) ([]byte, error) { + b64 := util.EncodeUnpaddedBase64ToString(key.PrivateKey.Seed()) + return []byte(fmt.Sprintf("ed25519 %s %s", key.KeyVersion, b64)), nil } -func DecodeSigningKey(key io.Reader) (ed25519.PrivateKey, string, error) { +func EncodeAllSigningKeys(keys []*homeserver_interop.SigningKey) ([]byte, error) { + return internal.EncodeNewlineAppendFormattedSigningKeys(keys, EncodeSigningKey) +} + +func DecodeSigningKey(key io.Reader) (*homeserver_interop.SigningKey, error) { + keys, err := DecodeAllSigningKeys(key) + if err != nil { + return nil, err + } + + return keys[0], nil +} + +func DecodeAllSigningKeys(key io.Reader) ([]*homeserver_interop.SigningKey, error) { b, err := io.ReadAll(key) if err != nil { - return nil, "", err + 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)) + lines := strings.Split(string(b), "\n") + if len(lines) <= 0 { + return nil, fmt.Errorf("no signing keys found") } + keys := make([]*homeserver_interop.SigningKey, 0) + for i, line := range lines { + parts := strings.Split(line, " ") + if len(parts) != 3 { + return nil, fmt.Errorf("i:%d - expected 3 parts to signing key, got %d", i, len(parts)) + } - if parts[0] != "ed25519" { - return nil, "", fmt.Errorf("expected ed25519 signing key, got '%s'", parts[0]) - } + if parts[0] != "ed25519" { + return nil, fmt.Errorf("i:%d - expected ed25519 signing key, got '%s'", i, parts[0]) + } - keyVersion := parts[1] - keyB64 := parts[2] + 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) - } + keyBytes, err := util.DecodeUnpaddedBase64String(keyB64) + if err != nil { + return nil, errors.Join(fmt.Errorf("i:%d - expected base64 signing key part", i), err) + } - _, priv, err := ed25519.GenerateKey(bytes.NewReader(keyBytes)) - if err != nil { - return nil, "", err - } + _, priv, err := ed25519.GenerateKey(bytes.NewReader(keyBytes)) + if err != nil { + return nil, errors.Join(fmt.Errorf("i:%d - error generating ed25519 key", i), err) + } - return priv, keyVersion, nil + keys = append(keys, &homeserver_interop.SigningKey{ + PrivateKey: priv, + KeyVersion: keyVersion, + }) + } + return keys, nil } diff --git a/test/signing_anyserver_test.go b/test/signing_anyserver_test.go index 0f804aa1..db0783b7 100644 --- a/test/signing_anyserver_test.go +++ b/test/signing_anyserver_test.go @@ -17,19 +17,19 @@ Key-ID: ed25519:1Pu3u3 -----END MATRIX PRIVATE KEY----- ` - priv, keyVersion, err := any_server.DecodeSigningKey(bytes.NewReader([]byte(raw))) + key, 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)) + assert.Equal(t, "1Pu3u3", key.KeyVersion) + assert.Equal(t, "1Pu3u3solToI2pTdsHA4wj05bANnzPwJoxPepw2he2u4Fq1IRsE7q7tI3C83BUUIPhcZpLSKQ8jU8yA/meWHdw", util.EncodeUnpaddedBase64ToString(key.PrivateKey)) } func TestAnyServerDecodeSynapse(t *testing.T) { raw := `ed25519 a_RVfN wdSWsTNSOmMuNA1Ej6JUyeNbiBEt5jexHmVs7mHKZVc` - priv, keyVersion, err := any_server.DecodeSigningKey(bytes.NewReader([]byte(raw))) + key, 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)) + assert.Equal(t, "a_RVfN", key.KeyVersion) + assert.Equal(t, "wdSWsTNSOmMuNA1Ej6JUyeNbiBEt5jexHmVs7mHKZVc3XC3Hf2tee4KxuO3diGtvSOQ8j/MjmSmEhX1qLV6dbQ", util.EncodeUnpaddedBase64ToString(key.PrivateKey)) } func TestAnyServerDecodeMMR(t *testing.T) { @@ -41,8 +41,8 @@ PJt0OaIImDJk8P/PDb4TNQHgI/1AA1C+AaQaABxAcgc= -----END MMR PRIVATE KEY----- ` - priv, keyVersion, err := any_server.DecodeSigningKey(bytes.NewReader([]byte(raw))) + key, 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)) + assert.Equal(t, "e5d0oC", key.KeyVersion) + assert.Equal(t, "PJt0OaIImDJk8P/PDb4TNQHgI/1AA1C+AaQaABxAcgdOiF6RhfMvHtXNXwW0tCUjdexJ0+/UKOFVhjmtmYUK9Q", util.EncodeUnpaddedBase64ToString(key.PrivateKey)) } diff --git a/test/signing_dendrite_test.go b/test/signing_dendrite_test.go index 5d4a595a..82981a7b 100644 --- a/test/signing_dendrite_test.go +++ b/test/signing_dendrite_test.go @@ -37,25 +37,142 @@ Key-ID: ed25519:1Pu3u3 t.Fatal(err) } - parsedPriv, parsedKeyVer, err := dendrite.DecodeSigningKey(original) + key, err := dendrite.DecodeSigningKey(original) assert.NoError(t, err) - assert.Equal(t, keyVersion, parsedKeyVer) + assert.Equal(t, keyVersion, key.KeyVersion) - parsedSigB64 := util.EncodeUnpaddedBase64ToString(ed25519.Sign(parsedPriv, canonical)) + parsedSigB64 := util.EncodeUnpaddedBase64ToString(ed25519.Sign(key.PrivateKey, canonical)) assert.Equal(t, sigB64, parsedSigB64) // Encode and decode the key as MMR format and re-test signatures - mmrBytes, err := mmr.EncodeSigningKey(parsedKeyVer, parsedPriv) + mmrBytes, err := mmr.EncodeSigningKey(key) assert.NoError(t, err) - parsedPriv, parsedKeyVer, err = mmr.DecodeSigningKey(bytes.NewReader(mmrBytes)) + key, err = mmr.DecodeSigningKey(bytes.NewReader(mmrBytes)) assert.NoError(t, err) - assert.Equal(t, keyVersion, parsedKeyVer) + assert.Equal(t, keyVersion, key.KeyVersion) - parsedSigB64 = util.EncodeUnpaddedBase64ToString(ed25519.Sign(parsedPriv, canonical)) + parsedSigB64 = util.EncodeUnpaddedBase64ToString(ed25519.Sign(key.PrivateKey, canonical)) assert.Equal(t, sigB64, parsedSigB64) // Encode as Dendrite and compare to test value - enc, err := dendrite.EncodeSigningKey(parsedKeyVer, parsedPriv) + enc, err := dendrite.EncodeSigningKey(key) + assert.NoError(t, err) + assert.Equal(t, raw, string(enc)) +} + +func TestMultipleDecodeOneDendriteSigningKeyRoundTrip(t *testing.T) { + raw := `-----BEGIN MATRIX PRIVATE KEY----- +Key-ID: ed25519:1Pu3u3 + +1Pu3u3solToI2pTdsHA4wj05bANnzPwJoxPepw2he2s= +-----END MATRIX PRIVATE KEY----- +-----BEGIN MATRIX PRIVATE KEY----- +Key-ID: ed25519:wBZqM2 + +YOA4wuCQ+Wh7Vr+zmYSZpScnrgsrSAVtP89hn0zPO+s= +-----END MATRIX PRIVATE KEY----- +` + single := `-----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) + } + + key, err := dendrite.DecodeSigningKey(original) + assert.NoError(t, err) + assert.Equal(t, keyVersion, key.KeyVersion) + + parsedSigB64 := util.EncodeUnpaddedBase64ToString(ed25519.Sign(key.PrivateKey, canonical)) + assert.Equal(t, sigB64, parsedSigB64) + + // Encode and decode the key as MMR format and re-test signatures + mmrBytes, err := mmr.EncodeSigningKey(key) + assert.NoError(t, err) + key, err = mmr.DecodeSigningKey(bytes.NewReader(mmrBytes)) + assert.NoError(t, err) + assert.Equal(t, keyVersion, key.KeyVersion) + + parsedSigB64 = util.EncodeUnpaddedBase64ToString(ed25519.Sign(key.PrivateKey, canonical)) + assert.Equal(t, sigB64, parsedSigB64) + + // Encode as Dendrite and compare to test value + enc, err := dendrite.EncodeSigningKey(key) + assert.NoError(t, err) + assert.Equal(t, single, string(enc)) +} + +func TestMultipleDecodeDendriteSigningKeyRoundTrip(t *testing.T) { + raw := `-----BEGIN MATRIX PRIVATE KEY----- +Key-ID: ed25519:1Pu3u3 + +1Pu3u3solToI2pTdsHA4wj05bANnzPwJoxPepw2he2s= +-----END MATRIX PRIVATE KEY----- +-----BEGIN MATRIX PRIVATE KEY----- +Key-ID: ed25519:wBZqM2 + +YOA4wuCQ+Wh7Vr+zmYSZpScnrgsrSAVtP89hn0zPO+s= +-----END MATRIX PRIVATE KEY----- +` + original := bytes.NewBufferString(raw) + keyVersion1 := "1Pu3u3" + keyVersion2 := "wBZqM2" + 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) + } + + keys, err := dendrite.DecodeAllSigningKeys(original) + assert.NoError(t, err) + assert.Equal(t, 2, len(keys)) + assert.Equal(t, keyVersion1, keys[0].KeyVersion) + assert.Equal(t, keyVersion2, keys[1].KeyVersion) + + parsedSigB64 := util.EncodeUnpaddedBase64ToString(ed25519.Sign(keys[0].PrivateKey, canonical)) + assert.Equal(t, sigB64, parsedSigB64) + + // Encode and decode the key as MMR format and re-test signatures + mmrBytes, err := mmr.EncodeAllSigningKeys(keys) + assert.NoError(t, err) + keys, err = mmr.DecodeAllSigningKeys(bytes.NewReader(mmrBytes)) + assert.NoError(t, err) + assert.Equal(t, 2, len(keys)) + assert.Equal(t, keyVersion1, keys[0].KeyVersion) + assert.Equal(t, keyVersion2, keys[1].KeyVersion) + + parsedSigB64 = util.EncodeUnpaddedBase64ToString(ed25519.Sign(keys[0].PrivateKey, canonical)) + assert.Equal(t, sigB64, parsedSigB64) + + // Encode as Dendrite and compare to test value + enc, err := dendrite.EncodeAllSigningKeys(keys) assert.NoError(t, err) assert.Equal(t, raw, string(enc)) } diff --git a/test/signing_mmr_test.go b/test/signing_mmr_test.go index 614c87b4..6b5256a9 100644 --- a/test/signing_mmr_test.go +++ b/test/signing_mmr_test.go @@ -37,15 +37,115 @@ PJt0OaIImDJk8P/PDb4TNQHgI/1AA1C+AaQaABxAcgc= t.Fatal(err) } - parsedPriv, parsedKeyVer, err := mmr.DecodeSigningKey(original) + key, err := mmr.DecodeSigningKey(original) assert.NoError(t, err) - assert.Equal(t, keyVersion, parsedKeyVer) + assert.Equal(t, keyVersion, key.KeyVersion) - parsedSigB64 := util.EncodeUnpaddedBase64ToString(ed25519.Sign(parsedPriv, canonical)) + parsedSigB64 := util.EncodeUnpaddedBase64ToString(ed25519.Sign(key.PrivateKey, canonical)) assert.Equal(t, sigB64, parsedSigB64) // Encode as MMR again and compare to test value - enc, err := mmr.EncodeSigningKey(parsedKeyVer, parsedPriv) + enc, err := mmr.EncodeSigningKey(key) + assert.NoError(t, err) + assert.Equal(t, raw, string(enc)) +} + +func TestMultipleDecodeOneMMRSigningKeyRoundTrip(t *testing.T) { + raw := `-----BEGIN MMR PRIVATE KEY----- +Key-ID: ed25519:e5d0oC +Version: 1 + +PJt0OaIImDJk8P/PDb4TNQHgI/1AA1C+AaQaABxAcgc= +-----END MMR PRIVATE KEY----- +-----BEGIN MMR PRIVATE KEY----- +Key-ID: ed25519:CjtBYA +Version: 1 + +Xj/gSXAtSOTPG54sAWjm9BMVNX/Pa8ujIzwYbvLk7mM= +-----END MMR PRIVATE KEY----- +` + single := `-----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) + } + + key, err := mmr.DecodeSigningKey(original) + assert.NoError(t, err) + assert.Equal(t, keyVersion, key.KeyVersion) + + parsedSigB64 := util.EncodeUnpaddedBase64ToString(ed25519.Sign(key.PrivateKey, canonical)) + assert.Equal(t, sigB64, parsedSigB64) + + // Encode as MMR again and compare to test value + enc, err := mmr.EncodeSigningKey(key) + assert.NoError(t, err) + assert.Equal(t, single, string(enc)) +} + +func TestMultipleDecodeMMRSigningKeyRoundTrip(t *testing.T) { + raw := `-----BEGIN MMR PRIVATE KEY----- +Key-ID: ed25519:e5d0oC +Version: 1 + +PJt0OaIImDJk8P/PDb4TNQHgI/1AA1C+AaQaABxAcgc= +-----END MMR PRIVATE KEY----- +-----BEGIN MMR PRIVATE KEY----- +Key-ID: ed25519:CjtBYA +Version: 1 + +Xj/gSXAtSOTPG54sAWjm9BMVNX/Pa8ujIzwYbvLk7mM= +-----END MMR PRIVATE KEY----- +` + original := bytes.NewBufferString(raw) + keyVersion1 := "e5d0oC" + keyVersion2 := "CjtBYA" + 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) + } + + keys, err := mmr.DecodeAllSigningKeys(original) + assert.NoError(t, err) + assert.Equal(t, 2, len(keys)) + assert.Equal(t, keyVersion1, keys[0].KeyVersion) + assert.Equal(t, keyVersion2, keys[1].KeyVersion) + + parsedSigB64 := util.EncodeUnpaddedBase64ToString(ed25519.Sign(keys[0].PrivateKey, canonical)) + assert.Equal(t, sigB64, parsedSigB64) + + // Encode as MMR again and compare to test value + enc, err := mmr.EncodeAllSigningKeys(keys) assert.NoError(t, err) assert.Equal(t, raw, string(enc)) } diff --git a/test/signing_synapse_test.go b/test/signing_synapse_test.go index 4cc665b8..698ef8d6 100644 --- a/test/signing_synapse_test.go +++ b/test/signing_synapse_test.go @@ -32,25 +32,119 @@ func TestSynapseSigningKeyRoundTrip(t *testing.T) { t.Fatal(err) } - parsedPriv, parsedKeyVer, err := synapse.DecodeSigningKey(original) + key, err := synapse.DecodeSigningKey(original) assert.NoError(t, err) - assert.Equal(t, keyVersion, parsedKeyVer) + assert.Equal(t, keyVersion, key.KeyVersion) - parsedSigB64 := util.EncodeUnpaddedBase64ToString(ed25519.Sign(parsedPriv, canonical)) + parsedSigB64 := util.EncodeUnpaddedBase64ToString(ed25519.Sign(key.PrivateKey, canonical)) assert.Equal(t, sigB64, parsedSigB64) // Encode and decode the key as MMR format and re-test signatures - mmrBytes, err := mmr.EncodeSigningKey(parsedKeyVer, parsedPriv) + mmrBytes, err := mmr.EncodeSigningKey(key) assert.NoError(t, err) - parsedPriv, parsedKeyVer, err = mmr.DecodeSigningKey(bytes.NewReader(mmrBytes)) + key, err = mmr.DecodeSigningKey(bytes.NewReader(mmrBytes)) assert.NoError(t, err) - assert.Equal(t, keyVersion, parsedKeyVer) + assert.Equal(t, keyVersion, key.KeyVersion) - parsedSigB64 = util.EncodeUnpaddedBase64ToString(ed25519.Sign(parsedPriv, canonical)) + parsedSigB64 = util.EncodeUnpaddedBase64ToString(ed25519.Sign(key.PrivateKey, canonical)) assert.Equal(t, sigB64, parsedSigB64) // Encode as Synapse and compare to test value - enc, err := synapse.EncodeSigningKey(parsedKeyVer, parsedPriv) + enc, err := synapse.EncodeSigningKey(key) + assert.NoError(t, err) + assert.Equal(t, raw, string(enc)) +} + +func TestMultipleDecodeOneSynapseSigningKeyRoundTrip(t *testing.T) { + raw := `ed25519 a_RVfN wdSWsTNSOmMuNA1Ej6JUyeNbiBEt5jexHmVs7mHKZVc +ed25519 IZJY6A dvA8xfimUqog15zY6n5uRfVHb/pQ0sw9jRORJslYsoI` + single := `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) + } + + key, err := synapse.DecodeSigningKey(original) + assert.NoError(t, err) + assert.Equal(t, keyVersion, key.KeyVersion) + + parsedSigB64 := util.EncodeUnpaddedBase64ToString(ed25519.Sign(key.PrivateKey, canonical)) + assert.Equal(t, sigB64, parsedSigB64) + + // Encode and decode the key as MMR format and re-test signatures + mmrBytes, err := mmr.EncodeSigningKey(key) + assert.NoError(t, err) + key, err = mmr.DecodeSigningKey(bytes.NewReader(mmrBytes)) + assert.NoError(t, err) + assert.Equal(t, keyVersion, key.KeyVersion) + + parsedSigB64 = util.EncodeUnpaddedBase64ToString(ed25519.Sign(key.PrivateKey, canonical)) + assert.Equal(t, sigB64, parsedSigB64) + + // Encode as Synapse and compare to test value + enc, err := synapse.EncodeSigningKey(key) + assert.NoError(t, err) + assert.Equal(t, single, string(enc)) +} + +func TestMultipleDecodeSynapseSigningKeyRoundTrip(t *testing.T) { + raw := `ed25519 a_RVfN wdSWsTNSOmMuNA1Ej6JUyeNbiBEt5jexHmVs7mHKZVc +ed25519 IZJY6A dvA8xfimUqog15zY6n5uRfVHb/pQ0sw9jRORJslYsoI` + original := bytes.NewBufferString(raw) + keyVersion1 := "a_RVfN" + keyVersion2 := "IZJY6A" + 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) + } + + keys, err := synapse.DecodeAllSigningKeys(original) + assert.NoError(t, err) + assert.Equal(t, 2, len(keys)) + assert.Equal(t, keyVersion1, keys[0].KeyVersion) + assert.Equal(t, keyVersion2, keys[1].KeyVersion) + + parsedSigB64 := util.EncodeUnpaddedBase64ToString(ed25519.Sign(keys[0].PrivateKey, canonical)) + assert.Equal(t, sigB64, parsedSigB64) + + // Encode and decode the key as MMR format and re-test signatures + mmrBytes, err := mmr.EncodeAllSigningKeys(keys) + assert.NoError(t, err) + keys, err = mmr.DecodeAllSigningKeys(bytes.NewReader(mmrBytes)) + assert.NoError(t, err) + assert.Equal(t, 2, len(keys)) + assert.Equal(t, keyVersion1, keys[0].KeyVersion) + assert.Equal(t, keyVersion2, keys[1].KeyVersion) + + parsedSigB64 = util.EncodeUnpaddedBase64ToString(ed25519.Sign(keys[0].PrivateKey, canonical)) + assert.Equal(t, sigB64, parsedSigB64) + + // Encode as Synapse and compare to test value + enc, err := synapse.EncodeAllSigningKeys(keys) assert.NoError(t, err) assert.Equal(t, raw, string(enc)) }