diff --git a/base64.go b/base64.go index ef9b36a3..5d4f8021 100644 --- a/base64.go +++ b/base64.go @@ -16,8 +16,10 @@ package gomatrixserverlib import ( + "database/sql/driver" "encoding/base64" "encoding/json" + "fmt" "strings" ) @@ -26,6 +28,10 @@ import ( // // The bytes encoded using base64 when marshalled as JSON. // When the bytes are unmarshalled from JSON they are decoded from base64. +// +// When scanning directly from a database, a string column will be +// decoded from base64 automatically whereas a bytes column will be +// copied as-is. type Base64Bytes []byte // Encode encodes the bytes as base64 @@ -46,6 +52,27 @@ func (b64 *Base64Bytes) Decode(str string) error { return err } +// Implements sql.Scanner +func (b64 *Base64Bytes) Scan(src interface{}) error { + switch v := src.(type) { + case string: + return b64.Decode(v) + case []byte: + new := append(Base64Bytes{}, v...) + b64 = &new + return nil + case RawJSON: + return b64.UnmarshalJSON(v) + default: + return fmt.Errorf("unsupported source type") + } +} + +// Implements sql.Valuer +func (b64 Base64Bytes) Value() (driver.Value, error) { + return b64.Encode(), nil +} + // MarshalJSON encodes the bytes as base64 and then encodes the base64 as a JSON string. // This takes a value receiver so that maps and slices of Base64Bytes encode correctly. func (b64 Base64Bytes) MarshalJSON() ([]byte, error) { diff --git a/base64_test.go b/base64_test.go index 9b82e193..b9652e26 100644 --- a/base64_test.go +++ b/base64_test.go @@ -16,6 +16,7 @@ package gomatrixserverlib import ( + "bytes" "encoding/json" "testing" @@ -150,3 +151,39 @@ func TestUnmarshalYAMLBase64Struct(t *testing.T) { t.Fatalf("yaml.Unmarshal(%v): wanted %q got %q", input, want, result) } } + +func TestScanBase64(t *testing.T) { + expecting := Base64Bytes("This is a test string") + + inputStr := "VGhpcyBpcyBhIHRlc3Qgc3RyaW5n" + inputJSON := RawJSON(`"` + inputStr + `"`) + inputBytes := []byte(inputStr) + inputInt := 3 + + var b Base64Bytes + + if err := b.Scan(inputStr); err != nil { + t.Fatal(err) + } + if !bytes.Equal(expecting, b) { + t.Fatalf("scanning from string failed, got %v, wanted %v", b, expecting) + } + + if err := b.Scan(inputJSON); err != nil { + t.Fatal(err) + } + if !bytes.Equal(expecting, b) { + t.Fatalf("scanning from RawJSON failed, got %v, wanted %v", b, expecting) + } + + if err := b.Scan(inputBytes); err != nil { + t.Fatal(err) + } + if !bytes.Equal(expecting, b) { + t.Fatalf("scanning from []byte failed, got %v, wanted %v", b, expecting) + } + + if err := b.Scan(inputInt); err == nil { + t.Fatal("scanning from int should have failed but didn't") + } +} diff --git a/crosssigning.go b/crosssigning.go new file mode 100644 index 00000000..6c7e83d1 --- /dev/null +++ b/crosssigning.go @@ -0,0 +1,71 @@ +// Copyright 2021 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 gomatrixserverlib + +import ( + "encoding/json" + + "github.com/tidwall/gjson" +) + +type CrossSigningKeyPurpose string + +const ( + CrossSigningKeyPurposeMaster CrossSigningKeyPurpose = "master" + CrossSigningKeyPurposeSelfSigning CrossSigningKeyPurpose = "self_signing" + CrossSigningKeyPurposeUserSigning CrossSigningKeyPurpose = "user_signing" +) + +type CrossSigningKeys struct { + MasterKey CrossSigningKey `json:"master_key"` + SelfSigningKey CrossSigningKey `json:"self_signing_key"` + UserSigningKey CrossSigningKey `json:"user_signing_key"` +} + +// https://spec.matrix.org/unstable/client-server-api/#post_matrixclientr0keysdevice_signingupload +type CrossSigningKey struct { + Signatures map[string]map[KeyID]Base64Bytes `json:"signatures,omitempty"` + Keys map[KeyID]Base64Bytes `json:"keys"` + Usage []CrossSigningKeyPurpose `json:"usage"` + UserID string `json:"user_id"` +} + +func (s *CrossSigningKey) isCrossSigningBody() {} // implements CrossSigningBody + +type CrossSigningBody interface { + isCrossSigningBody() +} + +type CrossSigningForKeyOrDevice struct { + CrossSigningBody +} + +// Implements json.Unmarshaler +func (c *CrossSigningForKeyOrDevice) UnmarshalJSON(b []byte) error { + if gjson.GetBytes(b, "device_id").Exists() { + body := &DeviceKeys{} + if err := json.Unmarshal(b, body); err != nil { + return err + } + c.CrossSigningBody = body + return nil + } + body := &CrossSigningKey{} + if err := json.Unmarshal(b, body); err != nil { + return err + } + c.CrossSigningBody = body + return nil +} diff --git a/federationtypes.go b/federationtypes.go index f943d325..40d754f0 100644 --- a/federationtypes.go +++ b/federationtypes.go @@ -2,6 +2,7 @@ package gomatrixserverlib import ( "context" + "database/sql/driver" "encoding/json" "fmt" "io" @@ -243,11 +244,11 @@ type RespUserDeviceKeys struct { DeviceID string `json:"device_id"` Algorithms []string `json:"algorithms"` // E.g "curve25519:JLAFKJWSCS": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI" - Keys map[string]string `json:"keys"` + Keys map[KeyID]Base64Bytes `json:"keys"` // E.g "@alice:example.com": { // "ed25519:JLAFKJWSCS": "dSO80A01XiigH3uBiDVx/EjzaoycHcjq9lfQX0uWsqxl2giMIiSPR8a4d291W1ihKJL/a+myXS367WT6NAIcBA" // } - Signatures map[string]map[string]string `json:"signatures"` + Signatures map[string]map[KeyID]Base64Bytes `json:"signatures"` } // UnmarshalJSON implements json.Unmarshaller @@ -874,7 +875,9 @@ type RespClaimKeys struct { // RespQueryKeys is the response for https://matrix.org/docs/spec/server_server/latest#post-matrix-federation-v1-user-keys-query type RespQueryKeys struct { - DeviceKeys map[string]map[string]DeviceKeys `json:"device_keys"` + DeviceKeys map[string]map[string]DeviceKeys `json:"device_keys"` + MasterKeys map[string]CrossSigningForKeyOrDevice `json:"master_keys"` + SelfSigningKeys map[string]CrossSigningForKeyOrDevice `json:"self_signing_keys"` } // DeviceKeys as per https://matrix.org/docs/spec/server_server/latest#post-matrix-federation-v1-user-keys-query @@ -885,6 +888,22 @@ type DeviceKeys struct { Unsigned map[string]interface{} `json:"unsigned"` } +func (s *DeviceKeys) isCrossSigningBody() {} // implements CrossSigningBody + +func (s *DeviceKeys) Scan(src interface{}) error { + switch v := src.(type) { + case string: + return json.Unmarshal([]byte(v), s) + case []byte: + return json.Unmarshal(v, s) + } + return fmt.Errorf("unsupported source type") +} + +func (s DeviceKeys) Value() (driver.Value, error) { + return json.Marshal(s) +} + // MSC2836EventRelationshipsRequest is a request to /event_relationships from // https://github.com/matrix-org/matrix-doc/blob/kegan/msc/threading/proposals/2836-threading.md // nolint:maligned