diff --git a/src/github.com/matrix-org/dendrite/common/config/config.go b/src/github.com/matrix-org/dendrite/common/config/config.go index 8a52dd52e..767cd9780 100644 --- a/src/github.com/matrix-org/dendrite/common/config/config.go +++ b/src/github.com/matrix-org/dendrite/common/config/config.go @@ -17,13 +17,11 @@ package config import ( "bytes" "crypto/sha256" - "encoding/base64" "encoding/pem" "fmt" "github.com/matrix-org/gomatrixserverlib" "golang.org/x/crypto/ed25519" "gopkg.in/yaml.v2" - "io" "io/ioutil" "path/filepath" "strings" @@ -32,7 +30,7 @@ import ( // Version is the current version of the config format. // This will change whenever we make breaking changes to the config format. -const Version = "0" +const Version = "v0" // Dendrite contains all the config used by a dendrite process. type Dendrite struct { @@ -104,11 +102,11 @@ type Dendrite struct { // Postgres Config Database struct { - Media DataSource `yaml:"media"` - Account DataSource `yaml:"account"` - ServerKeys DataSource `yaml:"server_keys"` - Sync DataSource `yaml:"sync"` - Roomserver DataSource `yaml:"roomserver"` + MediaServer DataSource `yaml:"media_server"` + Account DataSource `yaml:"account"` + ServerKeys DataSource `yaml:"server_keys"` + SyncServer DataSource `yaml:"sync_server"` + RoomServer DataSource `yaml:"room_server"` } `yaml:"database"` // The internal addresses the components will listen on. @@ -118,7 +116,7 @@ type Dendrite struct { ClientAPI Address `yaml:"client_api"` FederationAPI Address `yaml:"federation_api"` SyncAPI Address `yaml:"sync_api"` - Roomserver Address `yaml:"roomserver"` + RoomServer Address `yaml:"room_server"` } `yaml:"listen"` } @@ -189,18 +187,19 @@ func loadConfig( return nil, err } - if config.Matrix.KeyID, config.Matrix.PrivateKey, err = readKey(string(privateKeyData)); err != nil { + if config.Matrix.KeyID, config.Matrix.PrivateKey, err = readKeyPEM(privateKeyPath, privateKeyData); err != nil { return nil, err } for _, certPath := range config.Matrix.FederationCertificatePaths { - pemData, err := readFile(absPath(configDirPath, certPath)) + absCertPath := absPath(configDirPath, certPath) + pemData, err := readFile(absCertPath) if err != nil { return nil, err } fingerprint := fingerprintPEM(pemData) if fingerprint == nil { - return nil, fmt.Errorf("no certificate PEM data in %q", certPath) + return nil, fmt.Errorf("no certificate PEM data in %q", absCertPath) } config.Matrix.TLSFingerPrints = append(config.Matrix.TLSFingerPrints, *fingerprint) } @@ -261,16 +260,16 @@ func (config *Dendrite) check() error { checkNotEmpty("media.base_path", string(config.Media.BasePath)) checkNotEmpty("kafka.topics.input_room_event", string(config.Kafka.Topics.InputRoomEvent)) checkNotEmpty("kafka.topics.output_room_event", string(config.Kafka.Topics.InputRoomEvent)) - checkNotEmpty("database.media", string(config.Database.Media)) + checkNotEmpty("database.media_server", string(config.Database.MediaServer)) checkNotEmpty("database.account", string(config.Database.Account)) checkNotEmpty("database.server_keys", string(config.Database.ServerKeys)) - checkNotEmpty("database.sync", string(config.Database.Sync)) - checkNotEmpty("database.roomserver", string(config.Database.Roomserver)) + checkNotEmpty("database.sync_server", string(config.Database.SyncServer)) + checkNotEmpty("database.room_server", string(config.Database.RoomServer)) checkNotEmpty("listen.media_api", string(config.Listen.MediaAPI)) checkNotEmpty("listen.client_api", string(config.Listen.ClientAPI)) checkNotEmpty("listen.federation_api", string(config.Listen.FederationAPI)) checkNotEmpty("listen.sync_api", string(config.Listen.SyncAPI)) - checkNotEmpty("listen.roomserver", string(config.Listen.Roomserver)) + checkNotEmpty("listen.room_server", string(config.Listen.RoomServer)) if problems != nil { return Error{problems} @@ -286,38 +285,28 @@ func absPath(dir string, path Path) string { return filepath.Join(dir, string(path)) } -func loadPrivateKey(path string) (gomatrixserverlib.KeyID, ed25519.PrivateKey, error) { - keyData, err := ioutil.ReadFile(path) - if err != nil { - return "", nil, err +func readKeyPEM(path string, data []byte) (gomatrixserverlib.KeyID, ed25519.PrivateKey, error) { + for { + var keyBlock *pem.Block + keyBlock, data = pem.Decode(data) + if data == nil { + return "", nil, fmt.Errorf("no matrix private key PEM data in %q", path) + } + if keyBlock.Type == "MATRIX PRIVATE KEY" { + keyID := keyBlock.Headers["Key-ID"] + if keyID == "" { + return "", nil, fmt.Errorf("missing key ID in PEM data in %q", path) + } + if !strings.HasPrefix(keyID, "ed25519:") { + return "", nil, fmt.Errorf("key ID %q doesn't start with \"ed25519:\" in %q", keyID, path) + } + _, privKey, err := ed25519.GenerateKey(bytes.NewReader(keyBlock.Bytes)) + if err != nil { + return "", nil, err + } + return gomatrixserverlib.KeyID(keyID), privKey, nil + } } - return readKey(string(keyData)) -} - -// readKey reads a server's private ed25519 key. -// Otherwise the key is the key ID and the base64 encoded private key -// separated by a single space character. -// E.g "ed25519:abcd ABCDEFGHIJKLMNOPabcdefghijklmnop01234567890" -func readKey(key string) (gomatrixserverlib.KeyID, ed25519.PrivateKey, error) { - var keyID gomatrixserverlib.KeyID - var seed io.Reader - // TODO: We should be reading this from a PEM formatted file instead of - // our own custom format. - parts := strings.SplitN(strings.TrimRight(key, "\n"), " ", 2) - keyID = gomatrixserverlib.KeyID(parts[0]) - if len(parts) != 2 { - return "", nil, fmt.Errorf("Invalid server key: %q", key) - } - seedBytes, err := base64.RawStdEncoding.DecodeString(parts[1]) - if err != nil { - return "", nil, err - } - seed = bytes.NewReader(seedBytes) - _, privKey, err := ed25519.GenerateKey(seed) - if err != nil { - return "", nil, err - } - return keyID, privKey, nil } func fingerprintPEM(data []byte) *gomatrixserverlib.TLSFingerprint { diff --git a/src/github.com/matrix-org/dendrite/common/config/config_test.go b/src/github.com/matrix-org/dendrite/common/config/config_test.go new file mode 100644 index 000000000..18a16ea01 --- /dev/null +++ b/src/github.com/matrix-org/dendrite/common/config/config_test.go @@ -0,0 +1,131 @@ +// Copyright 2017 Vector Creations Ltd +// +// 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 config + +import ( + "fmt" + "testing" +) + +func TestLoadConfigRelative(t *testing.T) { + _, err := loadConfig("/my/config/dir", []byte(testConfig), + mockReadFile{ + "/my/config/dir/matrix_key.pem": testKey, + "/my/config/dir/tls_cert.pem": testCert, + }.readFile, + ) + if err != nil { + t.Error("failed to load config:", err) + } +} + +const testConfig = ` +version: v0 +matrix: + server_name: localhost + private_key: matrix_key.pem + federation_certificates: [tls_cert.pem] +media: + base_path: media_store +kafka: + addresses: ["localhost:9092"] + topics: + input_room_event: input.room + output_room_event: output.room +database: + media_server: "postgresql:///media_server" + account: "postgresql:///account" + server_keys: "postgresql:///server_keys" + sync_server: "postgresql:///sync_server" + room_server: "postgresql:///room_server" +listen: + room_server: "localhost:7770" + client_api: "localhost:7771" + federation_api: "localhost:7772" + sync_api: "localhost:7773" + media_api: "localhost:7774" +` + +type mockReadFile map[string]string + +func (m mockReadFile) readFile(path string) ([]byte, error) { + data, ok := m[path] + if !ok { + return nil, fmt.Errorf("no such file %q", path) + } + return []byte(data), nil +} + +func TestReadKey(t *testing.T) { + keyID, _, err := readKeyPEM("path/to/key", []byte(testKey)) + if err != nil { + t.Error("failed to load private key:", err) + } + wantKeyID := "ed25519:c8NsuQ" + if wantKeyID != string(keyID) { + t.Errorf("wanted key ID to be %q, got %q", wantKeyID, keyID) + } +} + +const testKey = ` +-----BEGIN MATRIX PRIVATE KEY----- +Key-ID: ed25519:c8NsuQ +7KRZiZ2sTyRR8uqqUjRwczuwRXXkUMYIUHq4Mc3t4bE= +-----END MATRIX PRIVATE KEY----- +` + +func TestFingerprintPEM(t *testing.T) { + got := fingerprintPEM([]byte(testCert)) + if got == nil { + t.Error("failed to calculate fingerprint") + } + if string(got.SHA256) != testCertFingerprint { + t.Errorf("bad fingerprint: wanted %q got %q", got, testCertFingerprint) + } + +} + +const testCertFingerprint = "56.\\SPQxE\xd4\x95\xfb\xf6\xd5\x04\x91\xcb/\x07\xb1^\x88\x08\xe3\xc1p\xdfY\x04\x19w\xcb" + +const testCert = ` +-----BEGIN CERTIFICATE----- +MIIE0zCCArugAwIBAgIJAPype3u24LJeMA0GCSqGSIb3DQEBCwUAMAAwHhcNMTcw +NjEzMTQyODU4WhcNMTgwNjEzMTQyODU4WjAAMIICIjANBgkqhkiG9w0BAQEFAAOC +Ag8AMIICCgKCAgEA3vNSr7lCh/alxPFqairp/PYohwdsqPvOD7zf7dJCNhy0gbdC +9/APwIbPAPL9nU+o9ud1ACNCKBCQin/9LnI5vd5pa/Ne+mmRADDLB/BBBoywSJWG +NSfKJ9n3XY1bjgtqi53uUh+RDdQ7sXudDqCUxiiJZmS7oqK/mp88XXAgCbuXUY29 +GmzbbDz37vntuSxDgUOnJ8uPSvRp5YPKogA3JwW1SyrlLt4Z30CQ6nH3Y2Q5SVfJ +NIQyMrnwyjA9bCdXezv1cLXoTYn7U9BRyzXTZeXs3y3ldnRfISXN35CU04Az1F8j +lfj7nXMEqI/qAj/qhxZ8nVBB+rpNOZy9RJko3O+G5Qa/EvzkQYV1rW4TM2Yme88A +QyJspoV/0bXk6gG987PonK2Uk5djxSULhnGVIqswydyH0Nzb+slRp2bSoWbaNlee ++6TIeiyTQYc055pCHOp22gtLrC5LQGchksi02St2ZzRHdnlfqCJ8S9sS7x3trzds +cYueg1sGI+O8szpQ3eUM7OhJOBrx6OlR7+QYnQg1wr/V+JAz1qcyTC1URcwfeqtg +QjxFdBD9LfCtfK+AO51H9ugtsPJqOh33PmvfvUBEM05OHCA0lNaWJHROGpm4T4cc +YQI9JQk/0lB7itF1qK5RG74qgKdjkBkfZxi0OqkUgHk6YHtJlKfET8zfrtcCAwEA +AaNQME4wHQYDVR0OBBYEFGwb0NgH0Zr7Ga23njEJ85Ozf8M9MB8GA1UdIwQYMBaA +FGwb0NgH0Zr7Ga23njEJ85Ozf8M9MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggIBAKU3RHXggbq/pLhGinU5q/9QT0TB/0bBnF1wNFkKQC0FrNJ+ZnBNmusy +oqOn7DEohBCCDxT0kgOC05gLEsGLkSXlVyqCsPFfycCFhtu1QzSRtQNRxB3pW3Wq +4/RFVYv0PGBjVBKxImQlEmXJWEDwemGKqDQZPtqR/FTHTbJcaT0xQr5+1oG6lawt +I/2cW6GQ0kYW/Szps8FgNdSNgVqCjjNIzBYbWhRWMx/63qD1ReUbY7/Yw9KKT8nK +zXERpbTM9k+Pnm0g9Gep+9HJ1dBFJeuTPugKeSeyqg2OJbENw1hxGs/HjBXw7580 +ioiMn/kMj6Tg/f3HCfKrdHHBFQw0/fJW6o17QImYIpPOPzc5RjXBrCJWb34kxqEd +NQdKgejWiV/LlVsguIF8hVZH2kRzvoyypkVUtSUYGmjvA5UXoORQZfJ+b41llq1B +GcSF6iaVbAFKnsUyyr1i9uHz/6Muqflphv/SfZxGheIn5u3PnhXrzDagvItjw0NS +n0Xq64k7fc42HXJpF8CGBkSaIhtlzcruO+vqR80B9r62+D0V7VmHOnP135MT6noU +8F0JQfEtP+I8NII5jHSF/khzSgP5g80LS9tEc2ILnIHK1StkInAoRQQ+/HsQsgbz +ANAf5kxmMsM0zlN2hkxl0H6o7wKlBSw3RI3cjfilXiMWRPJrzlc4 +-----END CERTIFICATE----- +`