Update gomatrixserverlib (#89)
This commit is contained in:
parent
a1ce351d36
commit
0309035aad
|
@ -14,7 +14,10 @@
|
||||||
|
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import "golang.org/x/crypto/ed25519"
|
import (
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
|
)
|
||||||
|
|
||||||
// ClientAPI contains the config information necessary to spin up a clientapi process.
|
// ClientAPI contains the config information necessary to spin up a clientapi process.
|
||||||
type ClientAPI struct {
|
type ClientAPI struct {
|
||||||
|
@ -24,7 +27,7 @@ type ClientAPI struct {
|
||||||
PrivateKey ed25519.PrivateKey
|
PrivateKey ed25519.PrivateKey
|
||||||
// An arbitrary string used to uniquely identify the PrivateKey. Must start with the
|
// An arbitrary string used to uniquely identify the PrivateKey. Must start with the
|
||||||
// prefix "ed25519:".
|
// prefix "ed25519:".
|
||||||
KeyID string
|
KeyID gomatrixserverlib.KeyID
|
||||||
// A list of URIs to send events to. These kafka logs should be consumed by a Room Server.
|
// A list of URIs to send events to. These kafka logs should be consumed by a Room Server.
|
||||||
KafkaProducerURIs []string
|
KafkaProducerURIs []string
|
||||||
// The topic for events which are written to the logs.
|
// The topic for events which are written to the logs.
|
||||||
|
|
|
@ -111,7 +111,7 @@ func buildAndOutput() gomatrixserverlib.EventReference {
|
||||||
eventID++
|
eventID++
|
||||||
id := fmt.Sprintf("$%d:%s", eventID, *serverName)
|
id := fmt.Sprintf("$%d:%s", eventID, *serverName)
|
||||||
now = time.Unix(0, 0)
|
now = time.Unix(0, 0)
|
||||||
event, err := b.Build(id, now, *serverName, *keyID, privateKey)
|
event, err := b.Build(id, now, *serverName, gomatrixserverlib.KeyID(*keyID), privateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
2
vendor/manifest
vendored
2
vendor/manifest
vendored
|
@ -92,7 +92,7 @@
|
||||||
{
|
{
|
||||||
"importpath": "github.com/matrix-org/gomatrixserverlib",
|
"importpath": "github.com/matrix-org/gomatrixserverlib",
|
||||||
"repository": "https://github.com/matrix-org/gomatrixserverlib",
|
"repository": "https://github.com/matrix-org/gomatrixserverlib",
|
||||||
"revision": "8e847d5bdb5cc0dd11e0846188eb403b70ae6bb3",
|
"revision": "fd3b9faf8492989e8f782592d37dcbdc01c44fea",
|
||||||
"branch": "master"
|
"branch": "master"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
177
vendor/src/github.com/matrix-org/gomatrixserverlib/LICENSE
vendored
Normal file
177
vendor/src/github.com/matrix-org/gomatrixserverlib/LICENSE
vendored
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
|
@ -16,6 +16,7 @@
|
||||||
package gomatrixserverlib
|
package gomatrixserverlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -141,3 +142,81 @@ func (fc *Client) LookupUserInfo(matrixServer, token string) (u UserInfo, err er
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LookupServerKeys lookups up the keys for a matrix server from a matrix server.
|
||||||
|
// The first argument is the name of the matrix server to download the keys from.
|
||||||
|
// The second argument is a map from (server name, key ID) pairs to timestamps.
|
||||||
|
// The (server name, key ID) pair identifies the key to download.
|
||||||
|
// The timestamps tell the server when the keys need to be valid until.
|
||||||
|
// Perspective servers can use that timestamp to determine whether they can
|
||||||
|
// return a cached copy of the keys or whether they will need to retrieve a fresh
|
||||||
|
// copy of the keys.
|
||||||
|
// Returns the keys or an error if there was a problem talking to the server.
|
||||||
|
func (fc *Client) LookupServerKeys(
|
||||||
|
matrixServer string, keyRequests map[PublicKeyRequest]Timestamp,
|
||||||
|
) (map[PublicKeyRequest]ServerKeys, error) {
|
||||||
|
url := url.URL{
|
||||||
|
Scheme: "matrix",
|
||||||
|
Host: matrixServer,
|
||||||
|
Path: "/_matrix/key/v2/query",
|
||||||
|
}
|
||||||
|
|
||||||
|
// The request format is:
|
||||||
|
// { "server_keys": { "<server_name>": { "<key_id>": { "minimum_valid_until_ts": <ts> }}}
|
||||||
|
type keyreq struct {
|
||||||
|
MinimumValidUntilTS Timestamp `json:"minimum_valid_until_ts"`
|
||||||
|
}
|
||||||
|
request := struct {
|
||||||
|
ServerKeyMap map[string]map[KeyID]keyreq `json:"server_keys"`
|
||||||
|
}{map[string]map[KeyID]keyreq{}}
|
||||||
|
for k, ts := range keyRequests {
|
||||||
|
server := request.ServerKeyMap[k.ServerName]
|
||||||
|
if server == nil {
|
||||||
|
server = map[KeyID]keyreq{}
|
||||||
|
request.ServerKeyMap[k.ServerName] = server
|
||||||
|
}
|
||||||
|
server[k.KeyID] = keyreq{ts}
|
||||||
|
}
|
||||||
|
|
||||||
|
requestBytes, err := json.Marshal(request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := fc.client.Post(url.String(), "application/json", bytes.NewBuffer(requestBytes))
|
||||||
|
if response != nil {
|
||||||
|
defer response.Body.Close()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.StatusCode != 200 {
|
||||||
|
var errorOutput []byte
|
||||||
|
if errorOutput, err = ioutil.ReadAll(response.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("HTTP %d : %s", response.StatusCode, errorOutput)
|
||||||
|
}
|
||||||
|
|
||||||
|
var body struct {
|
||||||
|
ServerKeyList []ServerKeys `json:"server_keys"`
|
||||||
|
}
|
||||||
|
if err = json.NewDecoder(response.Body).Decode(&body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := map[PublicKeyRequest]ServerKeys{}
|
||||||
|
for _, keys := range body.ServerKeyList {
|
||||||
|
keys.FromServer = matrixServer
|
||||||
|
// TODO: What happens if the same key ID appears in multiple responses?
|
||||||
|
// We should probably take the response with the highest valid_until_ts.
|
||||||
|
for keyID := range keys.VerifyKeys {
|
||||||
|
result[PublicKeyRequest{keys.ServerName, keyID}] = keys
|
||||||
|
}
|
||||||
|
for keyID := range keys.OldVerifyKeys {
|
||||||
|
result[PublicKeyRequest{keys.ServerName, keyID}] = keys
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
|
@ -109,14 +109,14 @@ var emptyEventReferenceList = []EventReference{}
|
||||||
// Call this after filling out the necessary fields.
|
// Call this after filling out the necessary fields.
|
||||||
// This can be called mutliple times on the same builder.
|
// This can be called mutliple times on the same builder.
|
||||||
// A different event ID must be supplied each time this is called.
|
// A different event ID must be supplied each time this is called.
|
||||||
func (eb *EventBuilder) Build(eventID string, now time.Time, origin, keyID string, privateKey ed25519.PrivateKey) (result Event, err error) {
|
func (eb *EventBuilder) Build(eventID string, now time.Time, origin string, keyID KeyID, privateKey ed25519.PrivateKey) (result Event, err error) {
|
||||||
var event struct {
|
var event struct {
|
||||||
EventBuilder
|
EventBuilder
|
||||||
EventID string `json:"event_id"`
|
EventID string `json:"event_id"`
|
||||||
RawContent rawJSON `json:"content"`
|
RawContent rawJSON `json:"content"`
|
||||||
RawUnsigned rawJSON `json:"unsigned,omitempty"`
|
RawUnsigned rawJSON `json:"unsigned,omitempty"`
|
||||||
OriginServerTS int64 `json:"origin_server_ts"`
|
OriginServerTS Timestamp `json:"origin_server_ts"`
|
||||||
Origin string `json:"origin"`
|
Origin string `json:"origin"`
|
||||||
}
|
}
|
||||||
event.EventBuilder = *eb
|
event.EventBuilder = *eb
|
||||||
if event.PrevEvents == nil {
|
if event.PrevEvents == nil {
|
||||||
|
@ -127,7 +127,7 @@ func (eb *EventBuilder) Build(eventID string, now time.Time, origin, keyID strin
|
||||||
}
|
}
|
||||||
event.RawContent = rawJSON(event.content)
|
event.RawContent = rawJSON(event.content)
|
||||||
event.RawUnsigned = rawJSON(event.unsigned)
|
event.RawUnsigned = rawJSON(event.unsigned)
|
||||||
event.OriginServerTS = now.UnixNano() / 1000000
|
event.OriginServerTS = AsTimestamp(now)
|
||||||
event.Origin = origin
|
event.Origin = origin
|
||||||
event.EventID = eventID
|
event.EventID = eventID
|
||||||
|
|
||||||
|
@ -245,7 +245,7 @@ func (e Event) EventReference() EventReference {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sign returns a copy of the event with an additional signature.
|
// Sign returns a copy of the event with an additional signature.
|
||||||
func (e Event) Sign(signingName, keyID string, privateKey ed25519.PrivateKey) Event {
|
func (e Event) Sign(signingName string, keyID KeyID, privateKey ed25519.PrivateKey) Event {
|
||||||
eventJSON, err := signEvent(signingName, keyID, privateKey, e.eventJSON)
|
eventJSON, err := signEvent(signingName, keyID, privateKey, e.eventJSON)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// This is unreachable for events created with EventBuilder.Build or NewEventFromUntrustedJSON
|
// This is unreachable for events created with EventBuilder.Build or NewEventFromUntrustedJSON
|
||||||
|
@ -262,23 +262,17 @@ func (e Event) Sign(signingName, keyID string, privateKey ed25519.PrivateKey) Ev
|
||||||
}
|
}
|
||||||
|
|
||||||
// KeyIDs returns a list of key IDs that the named entity has signed the event with.
|
// KeyIDs returns a list of key IDs that the named entity has signed the event with.
|
||||||
func (e Event) KeyIDs(signingName string) []string {
|
func (e Event) KeyIDs(signingName string) []KeyID {
|
||||||
var event struct {
|
keyIDs, err := ListKeyIDs(signingName, e.eventJSON)
|
||||||
Signatures map[string]map[string]rawJSON `json:"signatures"`
|
if err != nil {
|
||||||
}
|
|
||||||
if err := json.Unmarshal(e.eventJSON, &event); err != nil {
|
|
||||||
// This should unreachable for events created with EventBuilder.Build or NewEventFromUntrustedJSON
|
// This should unreachable for events created with EventBuilder.Build or NewEventFromUntrustedJSON
|
||||||
panic(fmt.Errorf("gomatrixserverlib: invalid event %v", err))
|
panic(fmt.Errorf("gomatrixserverlib: invalid event %v", err))
|
||||||
}
|
}
|
||||||
var keyIDs []string
|
|
||||||
for keyID := range event.Signatures[signingName] {
|
|
||||||
keyIDs = append(keyIDs, keyID)
|
|
||||||
}
|
|
||||||
return keyIDs
|
return keyIDs
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify checks a ed25519 signature
|
// Verify checks a ed25519 signature
|
||||||
func (e Event) Verify(signingName, keyID string, publicKey ed25519.PublicKey) error {
|
func (e Event) Verify(signingName string, keyID KeyID, publicKey ed25519.PublicKey) error {
|
||||||
return verifyEventSignature(signingName, keyID, publicKey, e.eventJSON)
|
return verifyEventSignature(signingName, keyID, publicKey, e.eventJSON)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -142,7 +142,7 @@ func referenceOfEvent(eventJSON []byte) (EventReference, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignEvent adds a ED25519 signature to the event for the given key.
|
// SignEvent adds a ED25519 signature to the event for the given key.
|
||||||
func signEvent(signingName, keyID string, privateKey ed25519.PrivateKey, eventJSON []byte) ([]byte, error) {
|
func signEvent(signingName string, keyID KeyID, privateKey ed25519.PrivateKey, eventJSON []byte) ([]byte, error) {
|
||||||
|
|
||||||
// Redact the event before signing so signature that will remain valid even if the event is redacted.
|
// Redact the event before signing so signature that will remain valid even if the event is redacted.
|
||||||
redactedJSON, err := redactEvent(eventJSON)
|
redactedJSON, err := redactEvent(eventJSON)
|
||||||
|
@ -176,7 +176,7 @@ func signEvent(signingName, keyID string, privateKey ed25519.PrivateKey, eventJS
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerifyEventSignature checks if the event has been signed by the given ED25519 key.
|
// VerifyEventSignature checks if the event has been signed by the given ED25519 key.
|
||||||
func verifyEventSignature(signingName, keyID string, publicKey ed25519.PublicKey, eventJSON []byte) error {
|
func verifyEventSignature(signingName string, keyID KeyID, publicKey ed25519.PublicKey, eventJSON []byte) error {
|
||||||
redactedJSON, err := redactEvent(eventJSON)
|
redactedJSON, err := redactEvent(eventJSON)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -30,7 +30,7 @@ func TestVerifyEventSignatureTestVectors(t *testing.T) {
|
||||||
}
|
}
|
||||||
random := bytes.NewBuffer(seed)
|
random := bytes.NewBuffer(seed)
|
||||||
entityName := "domain"
|
entityName := "domain"
|
||||||
keyID := "ed25519:1"
|
keyID := KeyID("ed25519:1")
|
||||||
|
|
||||||
publicKey, _, err := ed25519.GenerateKey(random)
|
publicKey, _, err := ed25519.GenerateKey(random)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -173,7 +173,7 @@ func TestSignEventTestVectors(t *testing.T) {
|
||||||
}
|
}
|
||||||
random := bytes.NewBuffer(seed)
|
random := bytes.NewBuffer(seed)
|
||||||
entityName := "domain"
|
entityName := "domain"
|
||||||
keyID := "ed25519:1"
|
keyID := KeyID("ed25519:1")
|
||||||
|
|
||||||
_, privateKey, err := ed25519.GenerateKey(random)
|
_, privateKey, err := ed25519.GenerateKey(random)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
308
vendor/src/github.com/matrix-org/gomatrixserverlib/keyring.go
vendored
Normal file
308
vendor/src/github.com/matrix-org/gomatrixserverlib/keyring.go
vendored
Normal file
|
@ -0,0 +1,308 @@
|
||||||
|
package gomatrixserverlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A PublicKeyRequest is a request for a public key with a particular key ID.
|
||||||
|
type PublicKeyRequest struct {
|
||||||
|
// The server to fetch a key for.
|
||||||
|
ServerName string
|
||||||
|
// The ID of the key to fetch.
|
||||||
|
KeyID KeyID
|
||||||
|
}
|
||||||
|
|
||||||
|
// A KeyFetcher is a way of fetching public keys in bulk.
|
||||||
|
type KeyFetcher interface {
|
||||||
|
// Lookup a batch of public keys.
|
||||||
|
// Takes a map from (server name, key ID) pairs to timestamp.
|
||||||
|
// The timestamp is when the keys need to be vaild up to.
|
||||||
|
// Returns a map from (server name, key ID) pairs to server key objects for
|
||||||
|
// that server name containing that key ID
|
||||||
|
// The result may have fewer (server name, key ID) pairs than were in the request.
|
||||||
|
// The result may have more (server name, key ID) pairs than were in the request.
|
||||||
|
// Returns an error if there was a problem fetching the keys.
|
||||||
|
FetchKeys(requests map[PublicKeyRequest]Timestamp) (map[PublicKeyRequest]ServerKeys, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A KeyDatabase is a store for caching public keys.
|
||||||
|
type KeyDatabase interface {
|
||||||
|
KeyFetcher
|
||||||
|
// Add a block of public keys to the database.
|
||||||
|
StoreKeys(map[PublicKeyRequest]ServerKeys) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// A KeyRing stores keys for matrix servers and provides methods for verifying JSON messages.
|
||||||
|
type KeyRing struct {
|
||||||
|
KeyFetchers []KeyFetcher
|
||||||
|
KeyDatabase KeyDatabase
|
||||||
|
}
|
||||||
|
|
||||||
|
// A VerifyJSONRequest is a request to check for a signature on a JSON message.
|
||||||
|
// A JSON message is valid for a server if the message has at least one valid
|
||||||
|
// signature from that server.
|
||||||
|
type VerifyJSONRequest struct {
|
||||||
|
// The name of the matrix server to check for a signature for.
|
||||||
|
ServerName string
|
||||||
|
// The millisecond posix timestamp the message needs to be valid at.
|
||||||
|
AtTS Timestamp
|
||||||
|
// The JSON bytes.
|
||||||
|
Message []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// A VerifyJSONResult is the result of checking the signature of a JSON message.
|
||||||
|
type VerifyJSONResult struct {
|
||||||
|
// Whether the message passed the signature checks.
|
||||||
|
// This will be nil if the message passed the checks.
|
||||||
|
// This will have an error if the message did not pass the checks.
|
||||||
|
Result error
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyJSONs performs bulk JSON signature verification for a list of VerifyJSONRequests.
|
||||||
|
// Returns a list of VerifyJSONResults with the same length and order as the request list.
|
||||||
|
// The caller should check the Result field for each entry to see if it was valid.
|
||||||
|
// Returns an error if there was a problem talking to the database or one of the other methods
|
||||||
|
// of fetching the public keys.
|
||||||
|
func (k *KeyRing) VerifyJSONs(requests []VerifyJSONRequest) ([]VerifyJSONResult, error) {
|
||||||
|
results := make([]VerifyJSONResult, len(requests))
|
||||||
|
keyIDs := make([][]KeyID, len(requests))
|
||||||
|
|
||||||
|
for i := range requests {
|
||||||
|
ids, err := ListKeyIDs(requests[i].ServerName, requests[i].Message)
|
||||||
|
if err != nil {
|
||||||
|
results[i].Result = fmt.Errorf("gomatrixserverlib: error extracting key IDs")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, keyID := range ids {
|
||||||
|
if k.isAlgorithmSupported(keyID) {
|
||||||
|
keyIDs[i] = append(keyIDs[i], keyID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(keyIDs[i]) == 0 {
|
||||||
|
results[i].Result = fmt.Errorf(
|
||||||
|
"gomatrixserverlib: not signed by %q with a supported algorithm", requests[i].ServerName,
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Set a place holder error in the result field.
|
||||||
|
// This will be unset if one of the signature checks passes.
|
||||||
|
// This will be overwritten if one of the signature checks fails.
|
||||||
|
// Therefore this will only remain in place if the keys couldn't be downloaded.
|
||||||
|
results[i].Result = fmt.Errorf(
|
||||||
|
"gomatrixserverlib: could not download key for %q", requests[i].ServerName,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
keyRequests := k.publicKeyRequests(requests, results, keyIDs)
|
||||||
|
if len(keyRequests) == 0 {
|
||||||
|
// There aren't any keys to fetch so we can stop here.
|
||||||
|
// This will happen if all the objects are missing supported signatures.
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
keysFromDatabase, err := k.KeyDatabase.FetchKeys(keyRequests)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
k.checkUsingKeys(requests, results, keyIDs, keysFromDatabase)
|
||||||
|
|
||||||
|
for i := range k.KeyFetchers {
|
||||||
|
keyRequests := k.publicKeyRequests(requests, results, keyIDs)
|
||||||
|
if len(keyRequests) == 0 {
|
||||||
|
// There aren't any keys to fetch so we can stop here.
|
||||||
|
// This means that we've checked every JSON object we can check.
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
// TODO: Coalesce in-flight requests for the same keys.
|
||||||
|
// Otherwise we risk spamming the servers we query the keys from.
|
||||||
|
keysFetched, err := k.KeyFetchers[i].FetchKeys(keyRequests)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
k.checkUsingKeys(requests, results, keyIDs, keysFetched)
|
||||||
|
|
||||||
|
// Add the keys to the database so that we won't need to fetch them again.
|
||||||
|
if err := k.KeyDatabase.StoreKeys(keysFetched); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KeyRing) isAlgorithmSupported(keyID KeyID) bool {
|
||||||
|
return strings.HasPrefix(string(keyID), "ed25519:")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KeyRing) publicKeyRequests(requests []VerifyJSONRequest, results []VerifyJSONResult, keyIDs [][]KeyID) map[PublicKeyRequest]Timestamp {
|
||||||
|
keyRequests := map[PublicKeyRequest]Timestamp{}
|
||||||
|
for i := range requests {
|
||||||
|
if results[i].Result == nil {
|
||||||
|
// We've already verified this message, we don't need to refetch the keys for it.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, keyID := range keyIDs[i] {
|
||||||
|
k := PublicKeyRequest{requests[i].ServerName, keyID}
|
||||||
|
// Grab the maximum neeeded TS for this server and key ID.
|
||||||
|
// This will default to 0 if the server and keyID weren't in the map.
|
||||||
|
maxTS := keyRequests[k]
|
||||||
|
if maxTS <= requests[i].AtTS {
|
||||||
|
// We clobber on equality since that means that if the server and keyID
|
||||||
|
// weren't already in the map and since AtTS is unsigned and since the
|
||||||
|
// default value for maxTS is 0 we will always insert an entry for the
|
||||||
|
// server and keyID.
|
||||||
|
keyRequests[k] = requests[i].AtTS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return keyRequests
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KeyRing) checkUsingKeys(
|
||||||
|
requests []VerifyJSONRequest, results []VerifyJSONResult, keyIDs [][]KeyID,
|
||||||
|
keys map[PublicKeyRequest]ServerKeys,
|
||||||
|
) {
|
||||||
|
for i := range requests {
|
||||||
|
if results[i].Result == nil {
|
||||||
|
// We've already checked this message and it passed the signature checks.
|
||||||
|
// So we can skip to the next message.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, keyID := range keyIDs[i] {
|
||||||
|
serverKeys, ok := keys[PublicKeyRequest{requests[i].ServerName, keyID}]
|
||||||
|
if !ok {
|
||||||
|
// No key for this key ID so we continue onto the next key ID.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
publicKey := serverKeys.PublicKey(keyID, requests[i].AtTS)
|
||||||
|
if publicKey == nil {
|
||||||
|
// The key wasn't valid at the timestamp we needed it to be valid at.
|
||||||
|
// So skip onto the next key.
|
||||||
|
results[i].Result = fmt.Errorf(
|
||||||
|
"gomatrixserverlib: key with ID %q for %q not valid at %d",
|
||||||
|
keyID, requests[i].ServerName, requests[i].AtTS,
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := VerifyJSON(
|
||||||
|
requests[i].ServerName, keyID, ed25519.PublicKey(publicKey), requests[i].Message,
|
||||||
|
); err != nil {
|
||||||
|
// The signature wasn't valid, record the error and try the next key ID.
|
||||||
|
results[i].Result = err
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// The signature is valid, set the result to nil.
|
||||||
|
results[i].Result = nil
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A PerspectiveKeyFetcher fetches server keys from a single perspective server.
|
||||||
|
type PerspectiveKeyFetcher struct {
|
||||||
|
// The name of the perspective server to fetch keys from.
|
||||||
|
PerspectiveServerName string
|
||||||
|
// The ed25519 public keys the perspective server must sign responses with.
|
||||||
|
PerspectiveServerKeys map[KeyID]ed25519.PublicKey
|
||||||
|
// The federation client to use to fetch keys with.
|
||||||
|
Client Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// FetchKeys implements KeyFetcher
|
||||||
|
func (p *PerspectiveKeyFetcher) FetchKeys(requests map[PublicKeyRequest]Timestamp) (map[PublicKeyRequest]ServerKeys, error) {
|
||||||
|
results, err := p.Client.LookupServerKeys(p.PerspectiveServerName, requests)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for req, keys := range results {
|
||||||
|
var valid bool
|
||||||
|
keyIDs, err := ListKeyIDs(p.PerspectiveServerName, keys.Raw)
|
||||||
|
if err != nil {
|
||||||
|
// The response from the perspective server was corrupted.
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, keyID := range keyIDs {
|
||||||
|
perspectiveKey, ok := p.PerspectiveServerKeys[keyID]
|
||||||
|
if !ok {
|
||||||
|
// We don't have a key for that keyID, skip to the next keyID.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := VerifyJSON(p.PerspectiveServerName, keyID, perspectiveKey, keys.Raw); err != nil {
|
||||||
|
// An invalid signature is very bad since it means we have a
|
||||||
|
// problem talking to the perspective server.
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
valid = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !valid {
|
||||||
|
// This means we don't have a known signature from the perspective server.
|
||||||
|
return nil, fmt.Errorf("gomatrixserverlib: not signed with a known key for the perspective server")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the keys are valid for the server.
|
||||||
|
checks, _, _ := CheckKeys(req.ServerName, time.Unix(0, 0), keys, nil)
|
||||||
|
if !checks.AllChecksOK {
|
||||||
|
// This is bad because it means that the perspective server was trying to feed us an invalid response.
|
||||||
|
return nil, fmt.Errorf("gomatrixserverlib: key response from perspective server failed checks")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A DirectKeyFetcher fetches keys directly from a server.
|
||||||
|
// This may be suitable for local deployments that are firewalled from the public internet where DNS can be trusted.
|
||||||
|
type DirectKeyFetcher struct {
|
||||||
|
// The federation client to use to fetch keys with.
|
||||||
|
Client Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// FetchKeys implements KeyFetcher
|
||||||
|
func (d *DirectKeyFetcher) FetchKeys(requests map[PublicKeyRequest]Timestamp) (map[PublicKeyRequest]ServerKeys, error) {
|
||||||
|
byServer := map[string]map[PublicKeyRequest]Timestamp{}
|
||||||
|
for req, ts := range requests {
|
||||||
|
server := byServer[req.ServerName]
|
||||||
|
if server == nil {
|
||||||
|
server = map[PublicKeyRequest]Timestamp{}
|
||||||
|
byServer[req.ServerName] = server
|
||||||
|
}
|
||||||
|
server[req] = ts
|
||||||
|
}
|
||||||
|
|
||||||
|
results := map[PublicKeyRequest]ServerKeys{}
|
||||||
|
for server, reqs := range byServer {
|
||||||
|
// TODO: make these requests in parallel
|
||||||
|
serverResults, err := d.fetchKeysForServer(server, reqs)
|
||||||
|
if err != nil {
|
||||||
|
// TODO: Should we actually be erroring here? or should we just drop those keys from the result map?
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for req, keys := range serverResults {
|
||||||
|
results[req] = keys
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DirectKeyFetcher) fetchKeysForServer(
|
||||||
|
serverName string, requests map[PublicKeyRequest]Timestamp,
|
||||||
|
) (map[PublicKeyRequest]ServerKeys, error) {
|
||||||
|
results, err := d.Client.LookupServerKeys(serverName, requests)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for req, keys := range results {
|
||||||
|
// Check that the keys are valid for the server.
|
||||||
|
checks, _, _ := CheckKeys(req.ServerName, time.Unix(0, 0), keys, nil)
|
||||||
|
if !checks.AllChecksOK {
|
||||||
|
return nil, fmt.Errorf("gomatrixserverlib: key response direct from %q failed checks", serverName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
139
vendor/src/github.com/matrix-org/gomatrixserverlib/keyring_test.go
vendored
Normal file
139
vendor/src/github.com/matrix-org/gomatrixserverlib/keyring_test.go
vendored
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
package gomatrixserverlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var privateKeySeed1 = `QJvXAPj0D9MUb1exkD8pIWmCvT1xajlsB8jRYz/G5HE`
|
||||||
|
var privateKeySeed2 = `AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`
|
||||||
|
|
||||||
|
// testKeys taken from a copy of synapse.
|
||||||
|
var testKeys = `{
|
||||||
|
"old_verify_keys": {
|
||||||
|
"ed25519:old": {
|
||||||
|
"expired_ts": 929059200,
|
||||||
|
"key": "O2onvM62pC1io6jQKm8Nc2UyFXcd4kOmOsBIoYtZ2ik"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"server_name": "localhost:8800",
|
||||||
|
"signatures": {
|
||||||
|
"localhost:8800": {
|
||||||
|
"ed25519:a_Obwu": "xkr4Z49ODoQnRi//ePfXlt8Q68vzd+DkzBNCt60NcwnLjNREx0qVQrw1iTFSoxkgGtz30NDkmyffDrCrmX5KBw"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tls_fingerprints": [
|
||||||
|
{
|
||||||
|
"sha256": "I2ohBnqpb5m3HldWFwyA10WdjqDksukiKVUdZ690WzM"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"valid_until_ts": 1493142432964,
|
||||||
|
"verify_keys": {
|
||||||
|
"ed25519:a_Obwu": {
|
||||||
|
"key": "2UwTWD4+tgTgENV7znGGNqhAOGY+BW1mRAnC6W6FBQg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
type testKeyDatabase struct{}
|
||||||
|
|
||||||
|
func (db *testKeyDatabase) FetchKeys(requests map[PublicKeyRequest]Timestamp) (map[PublicKeyRequest]ServerKeys, error) {
|
||||||
|
results := map[PublicKeyRequest]ServerKeys{}
|
||||||
|
var keys ServerKeys
|
||||||
|
if err := json.Unmarshal([]byte(testKeys), &keys); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req1 := PublicKeyRequest{"localhost:8800", "ed25519:old"}
|
||||||
|
req2 := PublicKeyRequest{"localhost:8800", "ed25519:a_Obwu"}
|
||||||
|
|
||||||
|
for req := range requests {
|
||||||
|
if req == req1 || req == req2 {
|
||||||
|
results[req] = keys
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *testKeyDatabase) StoreKeys(requests map[PublicKeyRequest]ServerKeys) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifyJSONsSuccess(t *testing.T) {
|
||||||
|
// Check that trying to verify the server key JSON works.
|
||||||
|
k := KeyRing{nil, &testKeyDatabase{}}
|
||||||
|
results, err := k.VerifyJSONs([]VerifyJSONRequest{{
|
||||||
|
ServerName: "localhost:8800",
|
||||||
|
Message: []byte(testKeys),
|
||||||
|
AtTS: 1493142432964,
|
||||||
|
}})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(results) != 1 || results[0].Result != nil {
|
||||||
|
t.Fatalf("VerifyJSON(): Wanted [{Result: nil}] got %#v", results)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifyJSONsUnknownServerFails(t *testing.T) {
|
||||||
|
// Check that trying to verify JSON for an unknown server fails.
|
||||||
|
k := KeyRing{nil, &testKeyDatabase{}}
|
||||||
|
results, err := k.VerifyJSONs([]VerifyJSONRequest{{
|
||||||
|
ServerName: "unknown:8800",
|
||||||
|
Message: []byte(testKeys),
|
||||||
|
AtTS: 1493142432964,
|
||||||
|
}})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(results) != 1 || results[0].Result == nil {
|
||||||
|
t.Fatalf("VerifyJSON(): Wanted [{Result: <some error>}] got %#v", results)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifyJSONsDistantFutureFails(t *testing.T) {
|
||||||
|
// Check that trying to verify JSON from the distant future fails.
|
||||||
|
distantFuture := Timestamp(2000000000000)
|
||||||
|
k := KeyRing{nil, &testKeyDatabase{}}
|
||||||
|
results, err := k.VerifyJSONs([]VerifyJSONRequest{{
|
||||||
|
ServerName: "unknown:8800",
|
||||||
|
Message: []byte(testKeys),
|
||||||
|
AtTS: distantFuture,
|
||||||
|
}})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(results) != 1 || results[0].Result == nil {
|
||||||
|
t.Fatalf("VerifyJSON(): Wanted [{Result: <some error>}] got %#v", results)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifyJSONsFetcherError(t *testing.T) {
|
||||||
|
// Check that if the database errors then the attempt to verify JSON fails.
|
||||||
|
k := KeyRing{nil, &erroringKeyDatabase{}}
|
||||||
|
results, err := k.VerifyJSONs([]VerifyJSONRequest{{
|
||||||
|
ServerName: "localhost:8800",
|
||||||
|
Message: []byte(testKeys),
|
||||||
|
AtTS: 1493142432964,
|
||||||
|
}})
|
||||||
|
if err != error(&testErrorFetch) || results != nil {
|
||||||
|
t.Fatalf("VerifyJSONs(): Wanted (nil, <some error>) got (%#v, %q)", results, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type erroringKeyDatabase struct{}
|
||||||
|
|
||||||
|
type erroringKeyDatabaseError int
|
||||||
|
|
||||||
|
func (e *erroringKeyDatabaseError) Error() string { return "An error with the key database" }
|
||||||
|
|
||||||
|
var testErrorFetch = erroringKeyDatabaseError(1)
|
||||||
|
var testErrorStore = erroringKeyDatabaseError(2)
|
||||||
|
|
||||||
|
func (e *erroringKeyDatabase) FetchKeys(requests map[PublicKeyRequest]Timestamp) (map[PublicKeyRequest]ServerKeys, error) {
|
||||||
|
return nil, &testErrorFetch
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *erroringKeyDatabase) StoreKeys(keys map[PublicKeyRequest]ServerKeys) error {
|
||||||
|
return &testErrorStore
|
||||||
|
}
|
|
@ -21,7 +21,6 @@ import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -31,19 +30,70 @@ import (
|
||||||
// ServerKeys are the ed25519 signing keys published by a matrix server.
|
// ServerKeys are the ed25519 signing keys published by a matrix server.
|
||||||
// Contains SHA256 fingerprints of the TLS X509 certificates used by the server.
|
// Contains SHA256 fingerprints of the TLS X509 certificates used by the server.
|
||||||
type ServerKeys struct {
|
type ServerKeys struct {
|
||||||
Raw []byte `json:"-"` // Copy of the raw JSON for signature checking.
|
// Copy of the raw JSON for signature checking.
|
||||||
ServerName string `json:"server_name"` // The name of the server.
|
Raw []byte
|
||||||
TLSFingerprints []struct { // List of SHA256 fingerprints of X509 certificates.
|
// The server the raw JSON was downloaded from.
|
||||||
SHA256 Base64String `json:"sha256"`
|
FromServer string
|
||||||
} `json:"tls_fingerprints"`
|
// The decoded JSON fields.
|
||||||
VerifyKeys map[string]struct { // The current signing keys in use on this server.
|
ServerKeyFields
|
||||||
Key Base64String `json:"key"` // The public key.
|
}
|
||||||
} `json:"verify_keys"`
|
|
||||||
ValidUntilTS int64 `json:"valid_until_ts"` // When this result is valid until in milliseconds.
|
// A TLSFingerprint is a SHA256 hash of an X509 certificate.
|
||||||
OldVerifyKeys map[string]struct { // Old keys that are now only valid for checking historic events.
|
type TLSFingerprint struct {
|
||||||
Key Base64String `json:"key"` // The public key.
|
SHA256 Base64String `json:"sha256"`
|
||||||
ExpiredTS uint64 `json:"expired_ts"` // When this key stopped being valid for event signing.
|
}
|
||||||
} `json:"old_verify_keys"`
|
|
||||||
|
// A VerifyKey is a ed25519 public key for a server.
|
||||||
|
type VerifyKey struct {
|
||||||
|
// The public key.
|
||||||
|
Key Base64String `json:"key"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// An OldVerifyKey is an old ed25519 public key that is no longer valid.
|
||||||
|
type OldVerifyKey struct {
|
||||||
|
VerifyKey
|
||||||
|
// When this key stopped being valid for event signing in milliseconds.
|
||||||
|
ExpiredTS Timestamp `json:"expired_ts"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerKeyFields are the parsed JSON contents of the ed25519 signing keys published by a matrix server.
|
||||||
|
type ServerKeyFields struct {
|
||||||
|
// The name of the server
|
||||||
|
ServerName string `json:"server_name"`
|
||||||
|
// List of SHA256 fingerprints of X509 certificates used by this server.
|
||||||
|
TLSFingerprints []TLSFingerprint `json:"tls_fingerprints"`
|
||||||
|
// The current signing keys in use on this server.
|
||||||
|
// The keys of the map are the IDs of the keys.
|
||||||
|
// These are valid while this response is valid.
|
||||||
|
VerifyKeys map[KeyID]VerifyKey `json:"verify_keys"`
|
||||||
|
// When this result is valid until in milliseconds.
|
||||||
|
ValidUntilTS Timestamp `json:"valid_until_ts"`
|
||||||
|
// Old keys that are now only valid for checking historic events.
|
||||||
|
// The keys of the map are the IDs of the keys.
|
||||||
|
OldVerifyKeys map[KeyID]OldVerifyKey `json:"old_verify_keys"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements json.Unmarshaler
|
||||||
|
func (keys *ServerKeys) UnmarshalJSON(data []byte) error {
|
||||||
|
keys.Raw = data
|
||||||
|
return json.Unmarshal(data, &keys.ServerKeyFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements json.Marshaler
|
||||||
|
func (keys ServerKeys) MarshalJSON() ([]byte, error) {
|
||||||
|
// We already have a copy of the serialised JSON for the keys so we can return that directly.
|
||||||
|
return keys.Raw, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PublicKey returns a public key with the given ID valid at the given TS or nil if no such key exists.
|
||||||
|
func (keys ServerKeys) PublicKey(keyID KeyID, atTS Timestamp) []byte {
|
||||||
|
if currentKey, ok := keys.VerifyKeys[keyID]; ok && (atTS <= keys.ValidUntilTS) {
|
||||||
|
return currentKey.Key
|
||||||
|
}
|
||||||
|
if oldKey, ok := keys.OldVerifyKeys[keyID]; ok && (atTS <= oldKey.ExpiredTS) {
|
||||||
|
return oldKey.Key
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FetchKeysDirect fetches the matrix keys directly from the given address.
|
// FetchKeysDirect fetches the matrix keys directly from the given address.
|
||||||
|
@ -85,10 +135,8 @@ func FetchKeysDirect(serverName, addr, sni string) (*ServerKeys, *tls.Connection
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
var keys ServerKeys
|
var keys ServerKeys
|
||||||
if keys.Raw, err = ioutil.ReadAll(response.Body); err != nil {
|
keys.FromServer = serverName
|
||||||
return nil, nil, err
|
if err = json.NewDecoder(response.Body).Decode(&keys); err != nil {
|
||||||
}
|
|
||||||
if err = json.Unmarshal(keys.Raw, &keys); err != nil {
|
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
return &keys, &connectionState, nil
|
return &keys, &connectionState, nil
|
||||||
|
@ -107,25 +155,25 @@ type TLSFingerprintChecks struct {
|
||||||
|
|
||||||
// KeyChecks are the checks that should be applied to ServerKey responses.
|
// KeyChecks are the checks that should be applied to ServerKey responses.
|
||||||
type KeyChecks struct {
|
type KeyChecks struct {
|
||||||
AllChecksOK bool // Did all the checks pass?
|
AllChecksOK bool // Did all the checks pass?
|
||||||
MatchingServerName bool // Does the server name match what was requested.
|
MatchingServerName bool // Does the server name match what was requested.
|
||||||
FutureValidUntilTS bool // The valid until TS is in the future.
|
FutureValidUntilTS bool // The valid until TS is in the future.
|
||||||
HasEd25519Key bool // The server has at least one ed25519 key.
|
HasEd25519Key bool // The server has at least one ed25519 key.
|
||||||
AllEd25519ChecksOK *bool // All the Ed25519 checks are ok. or null if there weren't any to check.
|
AllEd25519ChecksOK *bool // All the Ed25519 checks are ok. or null if there weren't any to check.
|
||||||
Ed25519Checks map[string]Ed25519Checks // Checks for Ed25519 keys.
|
Ed25519Checks map[KeyID]Ed25519Checks // Checks for Ed25519 keys.
|
||||||
HasTLSFingerprint bool // The server has at least one fingerprint.
|
HasTLSFingerprint bool // The server has at least one fingerprint.
|
||||||
AllTLSFingerprintChecksOK *bool // All the fingerpint checks are ok.
|
AllTLSFingerprintChecksOK *bool // All the fingerpint checks are ok.
|
||||||
TLSFingerprintChecks []TLSFingerprintChecks // Checks for TLS fingerprints.
|
TLSFingerprintChecks []TLSFingerprintChecks // Checks for TLS fingerprints.
|
||||||
MatchingTLSFingerprint *bool // The TLS fingerprint for the connection matches one of the listed fingerprints.
|
MatchingTLSFingerprint *bool // The TLS fingerprint for the connection matches one of the listed fingerprints.
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckKeys checks the keys returned from a server to make sure they are valid.
|
// CheckKeys checks the keys returned from a server to make sure they are valid.
|
||||||
// If the checks pass then also return a map of key_id to Ed25519 public key and a list of SHA256 TLS fingerprints.
|
// If the checks pass then also return a map of key_id to Ed25519 public key and a list of SHA256 TLS fingerprints.
|
||||||
func CheckKeys(serverName string, now time.Time, keys ServerKeys, connState *tls.ConnectionState) (
|
func CheckKeys(serverName string, now time.Time, keys ServerKeys, connState *tls.ConnectionState) (
|
||||||
checks KeyChecks, ed25519Keys map[string]Base64String, sha256Fingerprints []Base64String,
|
checks KeyChecks, ed25519Keys map[KeyID]Base64String, sha256Fingerprints []Base64String,
|
||||||
) {
|
) {
|
||||||
checks.MatchingServerName = serverName == keys.ServerName
|
checks.MatchingServerName = serverName == keys.ServerName
|
||||||
checks.FutureValidUntilTS = now.UnixNano() < keys.ValidUntilTS*1000000
|
checks.FutureValidUntilTS = keys.ValidUntilTS.Time().After(now)
|
||||||
checks.AllChecksOK = checks.MatchingServerName && checks.FutureValidUntilTS
|
checks.AllChecksOK = checks.MatchingServerName && checks.FutureValidUntilTS
|
||||||
|
|
||||||
ed25519Keys = checkVerifyKeys(keys, &checks)
|
ed25519Keys = checkVerifyKeys(keys, &checks)
|
||||||
|
@ -160,12 +208,12 @@ func checkFingerprint(connState *tls.ConnectionState, sha256Fingerprints []Base6
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkVerifyKeys(keys ServerKeys, checks *KeyChecks) map[string]Base64String {
|
func checkVerifyKeys(keys ServerKeys, checks *KeyChecks) map[KeyID]Base64String {
|
||||||
allEd25519ChecksOK := true
|
allEd25519ChecksOK := true
|
||||||
checks.Ed25519Checks = map[string]Ed25519Checks{}
|
checks.Ed25519Checks = map[KeyID]Ed25519Checks{}
|
||||||
verifyKeys := map[string]Base64String{}
|
verifyKeys := map[KeyID]Base64String{}
|
||||||
for keyID, keyData := range keys.VerifyKeys {
|
for keyID, keyData := range keys.VerifyKeys {
|
||||||
algorithm := strings.SplitN(keyID, ":", 2)[0]
|
algorithm := strings.SplitN(string(keyID), ":", 2)[0]
|
||||||
publicKey := keyData.Key
|
publicKey := keyData.Key
|
||||||
if algorithm == "ed25519" {
|
if algorithm == "ed25519" {
|
||||||
checks.HasEd25519Key = true
|
checks.HasEd25519Key = true
|
||||||
|
|
307
vendor/src/github.com/matrix-org/gomatrixserverlib/request.go
vendored
Normal file
307
vendor/src/github.com/matrix-org/gomatrixserverlib/request.go
vendored
Normal file
|
@ -0,0 +1,307 @@
|
||||||
|
package gomatrixserverlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/matrix-org/util"
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A FederationRequest is a request to send to a remote server or a request
|
||||||
|
// received from a remote server.
|
||||||
|
// Federation requests are signed by building a JSON object and signing it
|
||||||
|
type FederationRequest struct {
|
||||||
|
// fields implement the JSON format needed for signing
|
||||||
|
// specified in https://matrix.org/docs/spec/server_server/unstable.html#request-authentication
|
||||||
|
fields struct {
|
||||||
|
Content rawJSON `json:"content,omitempty"`
|
||||||
|
Destination string `json:"destination"`
|
||||||
|
Method string `json:"method"`
|
||||||
|
Origin string `json:"origin"`
|
||||||
|
RequestURI string `json:"uri"`
|
||||||
|
Signatures map[string]map[string]string `json:"signatures,omitempty"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFederationRequest creates a matrix request. Takes an HTTP method, a
|
||||||
|
// destination homeserver and a request path which can have a query string.
|
||||||
|
// The destination is the name of a matrix homeserver.
|
||||||
|
// The request path must begin with a slash.
|
||||||
|
// Eg. NewFederationRequest("GET", "matrix.org", "/_matrix/federation/v1/send/123")
|
||||||
|
func NewFederationRequest(method, destination, requestURI string) FederationRequest {
|
||||||
|
var r FederationRequest
|
||||||
|
r.fields.Destination = destination
|
||||||
|
r.fields.Method = strings.ToUpper(method)
|
||||||
|
r.fields.RequestURI = requestURI
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetContent sets the JSON content for the request.
|
||||||
|
// Returns an error if there already is JSON content present on the request.
|
||||||
|
func (r *FederationRequest) SetContent(content interface{}) error {
|
||||||
|
if r.fields.Content != nil {
|
||||||
|
return fmt.Errorf("gomatrixserverlib: content already set on the request")
|
||||||
|
}
|
||||||
|
if r.fields.Signatures != nil {
|
||||||
|
return fmt.Errorf("gomatrixserverlib: the request is signed and cannot be modified")
|
||||||
|
}
|
||||||
|
data, err := json.Marshal(content)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.fields.Content = rawJSON(data)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method returns the JSON method for the request.
|
||||||
|
func (r *FederationRequest) Method() string {
|
||||||
|
return r.fields.Method
|
||||||
|
}
|
||||||
|
|
||||||
|
// Content returns the JSON content for the request.
|
||||||
|
func (r *FederationRequest) Content() []byte {
|
||||||
|
return []byte(r.fields.Content)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Origin returns the server that the request originated on.
|
||||||
|
func (r *FederationRequest) Origin() string {
|
||||||
|
return r.fields.Origin
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestURI returns the path and query sections of the HTTP request URL.
|
||||||
|
func (r *FederationRequest) RequestURI() string {
|
||||||
|
return r.fields.RequestURI
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign the matrix request with an ed25519 key.
|
||||||
|
// Uses the algorithm specified https://matrix.org/docs/spec/server_server/unstable.html#request-authentication
|
||||||
|
// Updates the request with the signature in place.
|
||||||
|
// Returns an error if there was a problem signing the request.
|
||||||
|
func (r *FederationRequest) Sign(serverName string, keyID KeyID, privateKey ed25519.PrivateKey) error {
|
||||||
|
if r.fields.Origin != "" && r.fields.Origin != serverName {
|
||||||
|
return fmt.Errorf("gomatrixserverlib: the request is already signed by a different server")
|
||||||
|
}
|
||||||
|
r.fields.Origin = serverName
|
||||||
|
// The request fields are already in the form required by the specification
|
||||||
|
// So we can just serialise the request fields using the default marshaller
|
||||||
|
data, err := json.Marshal(r.fields)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
signedData, err := SignJSON(serverName, keyID, privateKey, data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Now we can deserialise the signed request back into the request structure
|
||||||
|
// to set the Signatures field, (This will clobber the other fields but they
|
||||||
|
// will all round-trip through an encode/decode.)
|
||||||
|
return json.Unmarshal(signedData, &r.fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPRequest constructs an net/http.Request for this matrix request.
|
||||||
|
// The request can be passed to net/http.Client.Do().
|
||||||
|
func (r *FederationRequest) HTTPRequest() (*http.Request, error) {
|
||||||
|
urlStr := fmt.Sprintf("matrix://%s%s", r.fields.Destination, r.fields.RequestURI)
|
||||||
|
|
||||||
|
var content io.Reader
|
||||||
|
if r.fields.Content != nil {
|
||||||
|
content = bytes.NewReader([]byte(r.fields.Content))
|
||||||
|
}
|
||||||
|
|
||||||
|
httpReq, err := http.NewRequest(r.fields.Method, urlStr, content)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanity check that the request fields will round-trip properly.
|
||||||
|
if httpReq.URL.RequestURI() != r.fields.RequestURI {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"gomatrixserverlib: Request URI didn't encode properly. Wanted %q. Got %q",
|
||||||
|
r.fields.RequestURI, httpReq.URL.RequestURI(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.fields.Content != nil {
|
||||||
|
httpReq.Header.Set("Content-Type", "application/json")
|
||||||
|
}
|
||||||
|
|
||||||
|
for keyID, sig := range r.fields.Signatures[r.fields.Origin] {
|
||||||
|
// Check that we can safely include the origin and key ID in the header.
|
||||||
|
// We don't need to check the signature since we already know that it is
|
||||||
|
// base64.
|
||||||
|
if !isSafeInHTTPQuotedString(r.fields.Origin) {
|
||||||
|
return nil, fmt.Errorf("gomatrixserverlib: Request Origin isn't safe to include in an HTTP header")
|
||||||
|
}
|
||||||
|
if !isSafeInHTTPQuotedString(keyID) {
|
||||||
|
return nil, fmt.Errorf("gomatrixserverlib: Request key ID isn't safe to include in an HTTP header")
|
||||||
|
}
|
||||||
|
httpReq.Header.Add("Authorization", fmt.Sprintf(
|
||||||
|
"X-Matrix origin=\"%s\",key=\"%s\",sig=\"%s\"", r.fields.Origin, keyID, sig,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
return httpReq, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// isSafeInHTTPQuotedString checks whether the string is safe to include
|
||||||
|
// in an HTTP quoted-string without escaping.
|
||||||
|
// According to https://tools.ietf.org/html/rfc7230#section-3.2.6 the safe
|
||||||
|
// charcters are:
|
||||||
|
//
|
||||||
|
// qdtext = HTAB / SP / %x21 / %x23-5B / %x5D-7E / %x80-FF
|
||||||
|
//
|
||||||
|
func isSafeInHTTPQuotedString(text string) bool {
|
||||||
|
for i := 0; i < len(text); i++ {
|
||||||
|
c := text[i]
|
||||||
|
switch {
|
||||||
|
case c == '\t':
|
||||||
|
continue
|
||||||
|
case c == ' ':
|
||||||
|
continue
|
||||||
|
case c == 0x21:
|
||||||
|
continue
|
||||||
|
case 0x23 <= c && c <= 0x5B:
|
||||||
|
continue
|
||||||
|
case 0x5D <= c && c <= 0x7E:
|
||||||
|
continue
|
||||||
|
case 0x80 <= c && c <= 0xFF:
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyHTTPRequest extracts and verifies the contents of a net/http.Request.
|
||||||
|
// It consumes the body of the request.
|
||||||
|
// The JSON content can be accessed using FederationRequest.Content()
|
||||||
|
// Returns an 400 error if there was a problem parsing the request.
|
||||||
|
// It authenticates the request using an ed25519 signature using the KeyRing.
|
||||||
|
// The origin server can be accessed using FederationRequest.Origin()
|
||||||
|
// Returns a 401 error if there was a problem authenticating the request.
|
||||||
|
// HTTP handlers using this should be careful that they only use the parts of
|
||||||
|
// the request that have been authenticated: the method, the request path,
|
||||||
|
// the query parameters, and the JSON content. In particular the version of
|
||||||
|
// HTTP and the headers aren't protected by the signature.
|
||||||
|
func VerifyHTTPRequest(
|
||||||
|
req *http.Request, now time.Time, destination string, keys KeyRing,
|
||||||
|
) (*FederationRequest, util.JSONResponse) {
|
||||||
|
request, err := readHTTPRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
util.GetLogger(req.Context()).WithError(err).Print("Error parsing HTTP headers")
|
||||||
|
return nil, util.MessageResponse(400, "Bad Request")
|
||||||
|
}
|
||||||
|
request.fields.Destination = destination
|
||||||
|
|
||||||
|
// The request fields are already in the form required by the specification
|
||||||
|
// So we can just serialise the request fields using the default marshaller
|
||||||
|
toVerify, err := json.Marshal(request.fields)
|
||||||
|
if err != nil {
|
||||||
|
util.GetLogger(req.Context()).WithError(err).Print("Error parsing JSON")
|
||||||
|
return nil, util.MessageResponse(400, "Invalid JSON")
|
||||||
|
}
|
||||||
|
|
||||||
|
if request.Origin() == "" {
|
||||||
|
message := "Missing \"Authorization: X-Matrix ...\" HTTP header"
|
||||||
|
util.GetLogger(req.Context()).WithError(err).Print(message)
|
||||||
|
return nil, util.MessageResponse(401, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
results, err := keys.VerifyJSONs([]VerifyJSONRequest{{
|
||||||
|
ServerName: request.Origin(),
|
||||||
|
AtTS: AsTimestamp(now),
|
||||||
|
Message: toVerify,
|
||||||
|
}})
|
||||||
|
if err != nil {
|
||||||
|
message := "Error authenticating request"
|
||||||
|
util.GetLogger(req.Context()).WithError(err).Print(message)
|
||||||
|
return nil, util.MessageResponse(500, message)
|
||||||
|
}
|
||||||
|
if results[0].Result != nil {
|
||||||
|
message := "Invalid request signature"
|
||||||
|
util.GetLogger(req.Context()).WithError(results[0].Result).Print(message)
|
||||||
|
return nil, util.MessageResponse(401, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return request, util.JSONResponse{Code: 200, JSON: struct{}{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns an error if there was a problem reading the content of the request
|
||||||
|
func readHTTPRequest(req *http.Request) (*FederationRequest, error) {
|
||||||
|
var result FederationRequest
|
||||||
|
|
||||||
|
result.fields.Method = req.Method
|
||||||
|
result.fields.RequestURI = req.URL.RequestURI()
|
||||||
|
|
||||||
|
content, err := ioutil.ReadAll(req.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(content) != 0 {
|
||||||
|
if req.Header.Get("Content-Type") != "application/json" {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"gomatrixserverlib: The request must be \"application/json\" not %q",
|
||||||
|
req.Header.Get("Content-Type"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
result.fields.Content = rawJSON(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, authorization := range req.Header["Authorization"] {
|
||||||
|
scheme, origin, key, sig := parseAuthorization(authorization)
|
||||||
|
if scheme != "X-Matrix" {
|
||||||
|
// Ignore unknown types of Authorization.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if origin == "" || key == "" || sig == "" {
|
||||||
|
return nil, fmt.Errorf("gomatrixserverlib: invalid X-Matrix authorization header")
|
||||||
|
}
|
||||||
|
if result.fields.Origin != "" && result.fields.Origin != origin {
|
||||||
|
return nil, fmt.Errorf("gomatrixserverlib: different origins in X-Matrix authorization headers")
|
||||||
|
}
|
||||||
|
result.fields.Origin = origin
|
||||||
|
if result.fields.Signatures == nil {
|
||||||
|
result.fields.Signatures = map[string]map[string]string{origin: map[string]string{key: sig}}
|
||||||
|
} else {
|
||||||
|
result.fields.Signatures[origin][key] = sig
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseAuthorization(header string) (scheme, origin, key, sig string) {
|
||||||
|
parts := strings.SplitN(header, " ", 2)
|
||||||
|
scheme = parts[0]
|
||||||
|
if scheme != "X-Matrix" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, data := range strings.Split(parts[1], ",") {
|
||||||
|
pair := strings.SplitN(data, "=", 2)
|
||||||
|
if len(pair) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name := pair[0]
|
||||||
|
value := strings.Trim(pair[1], "\"")
|
||||||
|
if name == "origin" {
|
||||||
|
origin = value
|
||||||
|
}
|
||||||
|
if name == "key" {
|
||||||
|
key = value
|
||||||
|
}
|
||||||
|
if name == "sig" {
|
||||||
|
sig = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
175
vendor/src/github.com/matrix-org/gomatrixserverlib/request_test.go
vendored
Normal file
175
vendor/src/github.com/matrix-org/gomatrixserverlib/request_test.go
vendored
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
package gomatrixserverlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This GET request is taken from a request made by a synapse run by sytest.
|
||||||
|
// The headers have been reordered to match the order net/http writes them in.
|
||||||
|
const exampleGetRequest = "GET /_matrix/federation/v1/query/directory?room_alias=%23test%3Alocalhost%3A44033 HTTP/1.1\r\n" +
|
||||||
|
"Host: localhost:44033\r\n" +
|
||||||
|
"Authorization: X-Matrix" +
|
||||||
|
" origin=\"localhost:8800\"" +
|
||||||
|
",key=\"ed25519:a_Obwu\"" +
|
||||||
|
",sig=\"7vt4vP/w8zYB3Zg77nuTPwie3TxEy2OHZQMsSa4nsXZzL4/qw+DguXbyMy3BF77XvSJmBt+Gw+fU6T4HId7fBg\"" +
|
||||||
|
"\r\n" +
|
||||||
|
"\r\n"
|
||||||
|
|
||||||
|
// This PUT request is taken from a request made by a synapse run by sytest.
|
||||||
|
// The headers have been reordered to match the order net/http writes them in.
|
||||||
|
const examplePutRequest = "PUT /_matrix/federation/v1/send/1493385816575/ HTTP/1.1\r\n" +
|
||||||
|
"Host: localhost:44033\r\n" +
|
||||||
|
"Content-Length: 321\r\n" +
|
||||||
|
"Authorization: X-Matrix" +
|
||||||
|
" origin=\"localhost:8800\"" +
|
||||||
|
",key=\"ed25519:a_Obwu\"" +
|
||||||
|
",sig=\"+hmW6UjEXx7vMt2+MXO/EImSfdEYdBsZEOmpiz3evYktAgGNpGuNMBYXIA969WGubmceREKA/r1phasUFHBpDg\"" +
|
||||||
|
"\r\n" +
|
||||||
|
"Content-Type: application/json\r\n" +
|
||||||
|
"\r\n" +
|
||||||
|
examplePutContent
|
||||||
|
|
||||||
|
const examplePutContent = `{"edus":[{"content":{"device_id":"YHRUBZNPFS",` +
|
||||||
|
`"keys":{"device_id":"YHRUBZNPFS","device_keys":{},"user_id":` +
|
||||||
|
`"@ANON-22:localhost:8800"},"prev_id":[],"stream_id":30,"user_id":` +
|
||||||
|
`"@ANON-22:localhost:8800"},"edu_type":"m.device_list_update"}],"origin"` +
|
||||||
|
`:"localhost:8800","origin_server_ts":1493385822396,"pdu_failures":[],` +
|
||||||
|
`"pdus":[]}`
|
||||||
|
|
||||||
|
func TestSignGetRequest(t *testing.T) {
|
||||||
|
request := NewFederationRequest(
|
||||||
|
"GET", "localhost:44033",
|
||||||
|
"/_matrix/federation/v1/query/directory?room_alias=%23test%3Alocalhost%3A44033",
|
||||||
|
)
|
||||||
|
if err := request.Sign("localhost:8800", "ed25519:a_Obwu", privateKey1); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
hr, err := request.HTTPRequest()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
hr.Header.Set("User-Agent", "")
|
||||||
|
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
if err = hr.Write(buf); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
got := string(buf.Bytes())
|
||||||
|
want := exampleGetRequest
|
||||||
|
if want != got {
|
||||||
|
t.Errorf("Wanted %q got %q", want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifyGetRequest(t *testing.T) {
|
||||||
|
hr, err := http.ReadRequest(bufio.NewReader(bytes.NewReader([]byte(exampleGetRequest))))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
request, jsonResp := VerifyHTTPRequest(
|
||||||
|
hr, time.Unix(1493142432, 96400), "localhost:44033", KeyRing{nil, &testKeyDatabase{}},
|
||||||
|
)
|
||||||
|
if request == nil {
|
||||||
|
t.Errorf("Wanted non-nil request got nil. (request was %#v, response was %#v)", hr, jsonResp)
|
||||||
|
}
|
||||||
|
|
||||||
|
if request.Method() != "GET" {
|
||||||
|
t.Errorf("Wanted request.Method() to be \"GET\" got %q", request.Method())
|
||||||
|
}
|
||||||
|
|
||||||
|
if request.Origin() != "localhost:8800" {
|
||||||
|
t.Errorf("Wanted request.Origin() to be \"localhost:8800\" got %q", request.Origin())
|
||||||
|
}
|
||||||
|
|
||||||
|
if request.Content() != nil {
|
||||||
|
t.Errorf("Wanted request.Content() to be nil got %q", string(request.Content()))
|
||||||
|
}
|
||||||
|
|
||||||
|
wantPath := "/_matrix/federation/v1/query/directory?room_alias=%23test%3Alocalhost%3A44033"
|
||||||
|
if request.RequestURI() != wantPath {
|
||||||
|
t.Errorf("Wanted request.RequestURI() to be %q got %q", wantPath, request.RequestURI())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSignPutRequest(t *testing.T) {
|
||||||
|
request := NewFederationRequest(
|
||||||
|
"PUT", "localhost:44033", "/_matrix/federation/v1/send/1493385816575/",
|
||||||
|
)
|
||||||
|
if err := request.SetContent(rawJSON([]byte(examplePutContent))); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := request.Sign("localhost:8800", "ed25519:a_Obwu", privateKey1); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
hr, err := request.HTTPRequest()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
hr.Header.Set("User-Agent", "")
|
||||||
|
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
if err = hr.Write(buf); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
got := string(buf.Bytes())
|
||||||
|
want := examplePutRequest
|
||||||
|
if want != got {
|
||||||
|
t.Errorf("Wanted %q got %q", want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifyPutRequest(t *testing.T) {
|
||||||
|
hr, err := http.ReadRequest(bufio.NewReader(bytes.NewReader([]byte(examplePutRequest))))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
request, jsonResp := VerifyHTTPRequest(
|
||||||
|
hr, time.Unix(1493142432, 96400), "localhost:44033", KeyRing{nil, &testKeyDatabase{}},
|
||||||
|
)
|
||||||
|
if request == nil {
|
||||||
|
t.Errorf("Wanted non-nil request got nil. (request was %#v, response was %#v)", hr, jsonResp)
|
||||||
|
}
|
||||||
|
|
||||||
|
if request.Method() != "PUT" {
|
||||||
|
t.Errorf("Wanted request.Method() to be \"PUT\" got %q", request.Method())
|
||||||
|
}
|
||||||
|
|
||||||
|
if request.Origin() != "localhost:8800" {
|
||||||
|
t.Errorf("Wanted request.Origin() to be \"localhost:8800\" got %q", request.Origin())
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(request.Content()) != examplePutContent {
|
||||||
|
t.Errorf("Wanted request.Content() to be %q got %q", examplePutContent, string(request.Content()))
|
||||||
|
}
|
||||||
|
|
||||||
|
wantPath := "/_matrix/federation/v1/send/1493385816575/"
|
||||||
|
if request.RequestURI() != wantPath {
|
||||||
|
t.Errorf("Wanted request.RequestURI() to be %q got %q", wantPath, request.RequestURI())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var privateKey1 = mustLoadPrivateKey(privateKeySeed1)
|
||||||
|
var privateKey2 = mustLoadPrivateKey(privateKeySeed2)
|
||||||
|
|
||||||
|
func mustLoadPrivateKey(seed string) ed25519.PrivateKey {
|
||||||
|
seedBytes, err := base64.RawStdEncoding.DecodeString(seed)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
random := bytes.NewBuffer(seedBytes)
|
||||||
|
_, privateKey, err := ed25519.GenerateKey(random)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return privateKey
|
||||||
|
}
|
|
@ -21,11 +21,17 @@ import (
|
||||||
"golang.org/x/crypto/ed25519"
|
"golang.org/x/crypto/ed25519"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// A KeyID is the ID of a ed25519 key used to sign JSON.
|
||||||
|
// The key IDs have a format of "ed25519:[0-9A-Za-z]+"
|
||||||
|
// If we switch to using a different signing algorithm then we will change the
|
||||||
|
// prefix used.
|
||||||
|
type KeyID string
|
||||||
|
|
||||||
// SignJSON signs a JSON object returning a copy signed with the given key.
|
// SignJSON signs a JSON object returning a copy signed with the given key.
|
||||||
// https://matrix.org/docs/spec/server_server/unstable.html#signing-json
|
// https://matrix.org/docs/spec/server_server/unstable.html#signing-json
|
||||||
func SignJSON(signingName, keyID string, privateKey ed25519.PrivateKey, message []byte) ([]byte, error) {
|
func SignJSON(signingName string, keyID KeyID, privateKey ed25519.PrivateKey, message []byte) ([]byte, error) {
|
||||||
var object map[string]*json.RawMessage
|
var object map[string]*json.RawMessage
|
||||||
var signatures map[string]map[string]Base64String
|
var signatures map[string]map[KeyID]Base64String
|
||||||
if err := json.Unmarshal(message, &object); err != nil {
|
if err := json.Unmarshal(message, &object); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -39,7 +45,7 @@ func SignJSON(signingName, keyID string, privateKey ed25519.PrivateKey, message
|
||||||
}
|
}
|
||||||
delete(object, "signatures")
|
delete(object, "signatures")
|
||||||
} else {
|
} else {
|
||||||
signatures = map[string]map[string]Base64String{}
|
signatures = map[string]map[KeyID]Base64String{}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsorted, err := json.Marshal(object)
|
unsorted, err := json.Marshal(object)
|
||||||
|
@ -58,7 +64,7 @@ func SignJSON(signingName, keyID string, privateKey ed25519.PrivateKey, message
|
||||||
if signaturesForEntity != nil {
|
if signaturesForEntity != nil {
|
||||||
signaturesForEntity[keyID] = signature
|
signaturesForEntity[keyID] = signature
|
||||||
} else {
|
} else {
|
||||||
signatures[signingName] = map[string]Base64String{keyID: signature}
|
signatures[signingName] = map[KeyID]Base64String{keyID: signature}
|
||||||
}
|
}
|
||||||
|
|
||||||
var rawSignatures json.RawMessage
|
var rawSignatures json.RawMessage
|
||||||
|
@ -75,10 +81,25 @@ func SignJSON(signingName, keyID string, privateKey ed25519.PrivateKey, message
|
||||||
return json.Marshal(object)
|
return json.Marshal(object)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListKeyIDs lists the key IDs a given entity has signed a message with.
|
||||||
|
func ListKeyIDs(signingName string, message []byte) ([]KeyID, error) {
|
||||||
|
var object struct {
|
||||||
|
Signatures map[string]map[KeyID]json.RawMessage `json:"signatures"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(message, &object); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var result []KeyID
|
||||||
|
for keyID := range object.Signatures[signingName] {
|
||||||
|
result = append(result, keyID)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
// VerifyJSON checks that the entity has signed the message using a particular key.
|
// VerifyJSON checks that the entity has signed the message using a particular key.
|
||||||
func VerifyJSON(signingName, keyID string, publicKey ed25519.PublicKey, message []byte) error {
|
func VerifyJSON(signingName string, keyID KeyID, publicKey ed25519.PublicKey, message []byte) error {
|
||||||
var object map[string]*json.RawMessage
|
var object map[string]*json.RawMessage
|
||||||
var signatures map[string]map[string]Base64String
|
var signatures map[string]map[KeyID]Base64String
|
||||||
if err := json.Unmarshal(message, &object); err != nil {
|
if err := json.Unmarshal(message, &object); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ func TestVerifyJSON(t *testing.T) {
|
||||||
}
|
}
|
||||||
random := bytes.NewBuffer(seed)
|
random := bytes.NewBuffer(seed)
|
||||||
entityName := "domain"
|
entityName := "domain"
|
||||||
keyID := "ed25519:1"
|
keyID := KeyID("ed25519:1")
|
||||||
|
|
||||||
publicKey, _, err := ed25519.GenerateKey(random)
|
publicKey, _, err := ed25519.GenerateKey(random)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -99,7 +99,7 @@ func TestVerifyJSON(t *testing.T) {
|
||||||
func TestSignJSON(t *testing.T) {
|
func TestSignJSON(t *testing.T) {
|
||||||
random := bytes.NewBuffer([]byte("Some 32 randomly generated bytes"))
|
random := bytes.NewBuffer([]byte("Some 32 randomly generated bytes"))
|
||||||
entityName := "example.com"
|
entityName := "example.com"
|
||||||
keyID := "ed25519:my_key_id"
|
keyID := KeyID("ed25519:my_key_id")
|
||||||
input := []byte(`{"this":"is","my":"message"}`)
|
input := []byte(`{"this":"is","my":"message"}`)
|
||||||
|
|
||||||
publicKey, privateKey, err := ed25519.GenerateKey(random)
|
publicKey, privateKey, err := ed25519.GenerateKey(random)
|
||||||
|
@ -139,7 +139,7 @@ func TestSignJSONTestVectors(t *testing.T) {
|
||||||
}
|
}
|
||||||
random := bytes.NewBuffer(seed)
|
random := bytes.NewBuffer(seed)
|
||||||
entityName := "domain"
|
entityName := "domain"
|
||||||
keyID := "ed25519:1"
|
keyID := KeyID("ed25519:1")
|
||||||
|
|
||||||
_, privateKey, err := ed25519.GenerateKey(random)
|
_, privateKey, err := ed25519.GenerateKey(random)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -185,7 +185,7 @@ type MyMessage struct {
|
||||||
func TestSignJSONWithUnsigned(t *testing.T) {
|
func TestSignJSONWithUnsigned(t *testing.T) {
|
||||||
random := bytes.NewBuffer([]byte("Some 32 randomly generated bytes"))
|
random := bytes.NewBuffer([]byte("Some 32 randomly generated bytes"))
|
||||||
entityName := "example.com"
|
entityName := "example.com"
|
||||||
keyID := "ed25519:my_key_id"
|
keyID := KeyID("ed25519:my_key_id")
|
||||||
content := json.RawMessage(`{"signed":"data"}`)
|
content := json.RawMessage(`{"signed":"data"}`)
|
||||||
unsigned := json.RawMessage(`{"unsigned":"data"}`)
|
unsigned := json.RawMessage(`{"unsigned":"data"}`)
|
||||||
message := MyMessage{&unsigned, &content, nil}
|
message := MyMessage{&unsigned, &content, nil}
|
||||||
|
|
|
@ -1,3 +1,18 @@
|
||||||
|
/* 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 gomatrixserverlib
|
package gomatrixserverlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
18
vendor/src/github.com/matrix-org/gomatrixserverlib/timestamp.go
vendored
Normal file
18
vendor/src/github.com/matrix-org/gomatrixserverlib/timestamp.go
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
package gomatrixserverlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Timestamp is a millisecond posix timestamp.
|
||||||
|
type Timestamp uint64
|
||||||
|
|
||||||
|
// AsTimestamp turns a time.Time into a millisecond posix timestamp.
|
||||||
|
func AsTimestamp(t time.Time) Timestamp {
|
||||||
|
return Timestamp(t.UnixNano() / 1000000)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time turns a millisecond posix timestamp into a UTC time.Time
|
||||||
|
func (t Timestamp) Time() time.Time {
|
||||||
|
return time.Unix(int64(t)/1000, (int64(t)%1000)*1000000).UTC()
|
||||||
|
}
|
Loading…
Reference in a new issue