-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #78 from ssb-ngi-pointer/aliases
Aliases: database and web/muxrpc handlers
- Loading branch information
Showing
50 changed files
with
3,071 additions
and
255 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
// Package aliases implements the validation and signing features of https://ssb-ngi-pointer.github.io/rooms2/#alias | ||
package aliases | ||
|
||
import ( | ||
"bytes" | ||
|
||
"golang.org/x/crypto/ed25519" | ||
|
||
refs "go.mindeco.de/ssb-refs" | ||
) | ||
|
||
// Registration ties an alias to the ID of the user and the RoomID it should be registered on | ||
type Registration struct { | ||
Alias string | ||
UserID refs.FeedRef | ||
RoomID refs.FeedRef | ||
} | ||
|
||
// Sign takes the public key (belonging to UserID) and returns the signed confirmation | ||
func (r Registration) Sign(privKey ed25519.PrivateKey) Confirmation { | ||
var conf Confirmation | ||
conf.Registration = r | ||
msg := r.createRegistrationMessage() | ||
conf.Signature = ed25519.Sign(privKey, msg) | ||
return conf | ||
} | ||
|
||
// createRegistrationMessage returns the string of bytes that should be signed | ||
func (r Registration) createRegistrationMessage() []byte { | ||
var message bytes.Buffer | ||
message.WriteString("=room-alias-registration:") | ||
message.WriteString(r.RoomID.Ref()) | ||
message.WriteString(":") | ||
message.WriteString(r.UserID.Ref()) | ||
message.WriteString(":") | ||
message.WriteString(r.Alias) | ||
return message.Bytes() | ||
} | ||
|
||
// Confirmation combines a registration with the corresponding signature | ||
type Confirmation struct { | ||
Registration | ||
|
||
Signature []byte | ||
} | ||
|
||
// Verify checks that the confirmation is for the expected room and from the expected feed | ||
func (c Confirmation) Verify() bool { | ||
// re-construct the registration | ||
message := c.createRegistrationMessage() | ||
|
||
// check the signature matches | ||
return ed25519.Verify(c.UserID.PubKey(), message, c.Signature) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
package aliases | ||
|
||
import ( | ||
"bytes" | ||
"testing" | ||
|
||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/maybemod/keys" | ||
"github.com/stretchr/testify/require" | ||
refs "go.mindeco.de/ssb-refs" | ||
) | ||
|
||
func TestConfirmation(t *testing.T) { | ||
r := require.New(t) | ||
|
||
// this is our room, it's not a valid feed but thats fine for this test | ||
roomID := refs.FeedRef{ | ||
ID: bytes.Repeat([]byte("test"), 8), | ||
Algo: "test", | ||
} | ||
|
||
// to make the test deterministic, decided by fair dice roll. | ||
seed := bytes.Repeat([]byte("yeah"), 8) | ||
// our user, who will sign the registration | ||
userKeyPair, err := keys.NewKeyPair(bytes.NewReader(seed)) | ||
r.NoError(err) | ||
|
||
// create and fill out the registration for an alias (in this case the name of the test) | ||
var valid Registration | ||
valid.RoomID = roomID | ||
valid.UserID = userKeyPair.Feed | ||
valid.Alias = t.Name() | ||
|
||
// internal function to create the registration string | ||
msg := valid.createRegistrationMessage() | ||
want := "=room-alias-registration:@dGVzdHRlc3R0ZXN0dGVzdHRlc3R0ZXN0dGVzdHRlc3Q=.test:@Rt2aJrtOqWXhBZ5/vlfzeWQ9Bj/z6iT8CMhlr2WWlG4=.ed25519:TestConfirmation" | ||
r.Equal(want, string(msg)) | ||
|
||
// create the signed confirmation | ||
confirmation := valid.Sign(userKeyPair.Pair.Secret) | ||
|
||
yes := confirmation.Verify() | ||
r.True(yes, "should be valid for this room and feed") | ||
|
||
// make up another id for the invalid test(s) | ||
otherID := refs.FeedRef{ | ||
ID: bytes.Repeat([]byte("nope"), 8), | ||
Algo: "test", | ||
} | ||
|
||
confirmation.RoomID = otherID | ||
yes = confirmation.Verify() | ||
r.False(yes, "should not be valid for another room") | ||
|
||
confirmation.RoomID = roomID // restore | ||
confirmation.UserID = otherID | ||
yes = confirmation.Verify() | ||
r.False(yes, "should not be valid for this room but another feed") | ||
|
||
// puncture the signature to emulate an invalid one | ||
confirmation.Signature[0] = confirmation.Signature[0] ^ 1 | ||
|
||
yes = confirmation.Verify() | ||
r.False(yes, "should not be valid anymore") | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
package aliases | ||
|
||
import ( | ||
"fmt" | ||
) | ||
|
||
// IsValid decides whether an alias is okay for use or not. | ||
// The room spec defines it as _labels valid under RFC 1035_ ( https://ssb-ngi-pointer.github.io/rooms2/#alias-string ) | ||
// but that can be mostly any string since DNS is a 8bit binary protocol, | ||
// as long as it's shorter then 63 charachters. | ||
// | ||
// Right now it's pretty basic set of characters (a-z, A-Z, 0-9). | ||
// In theory we could be more liberal but there is a bunch of stuff to figure out, | ||
// like homograph attacks (https://en.wikipedia.org/wiki/IDN_homograph_attack), | ||
// if we would decide to allow full utf8 unicode. | ||
func IsValid(alias string) bool { | ||
if len(alias) > 63 { | ||
return false | ||
} | ||
|
||
var valid = true | ||
for _, char := range alias { | ||
if char >= '0' && char <= '9' { // is an ASCII number | ||
continue | ||
} | ||
|
||
if char >= 'a' && char <= 'z' { // is an ASCII char between a and z | ||
continue | ||
} | ||
|
||
if char >= 'A' && char <= 'Z' { // is an ASCII upper-case char between a and z | ||
continue | ||
} | ||
|
||
fmt.Println("found", char) | ||
valid = false | ||
break | ||
} | ||
return valid | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
package aliases | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestIsValid(t *testing.T) { | ||
a := assert.New(t) | ||
|
||
cases := []struct { | ||
alias string | ||
valid bool | ||
}{ | ||
{"basic", true}, | ||
{"no spaces", false}, | ||
{"no.dots", false}, | ||
{"#*!(! nope", false}, | ||
|
||
// too long | ||
{"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", false}, | ||
} | ||
|
||
for i, tc := range cases { | ||
yes := IsValid(tc.alias) | ||
a.Equal(tc.valid, yes, "wrong for %d: %s", i, tc.alias) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
// Package alias implements the muxrpc handlers for alias needs. | ||
package alias | ||
|
||
import ( | ||
"context" | ||
"encoding/base64" | ||
"encoding/json" | ||
"fmt" | ||
"strings" | ||
|
||
kitlog "github.com/go-kit/kit/log" | ||
"go.cryptoscope.co/muxrpc/v2" | ||
|
||
"github.com/ssb-ngi-pointer/go-ssb-room/aliases" | ||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/network" | ||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb" | ||
refs "go.mindeco.de/ssb-refs" | ||
) | ||
|
||
// Handler implements the muxrpc methods for alias registration and recvocation | ||
type Handler struct { | ||
logger kitlog.Logger | ||
self refs.FeedRef | ||
|
||
db roomdb.AliasService | ||
} | ||
|
||
// New returns a fresh alias muxrpc handler | ||
func New(log kitlog.Logger, self refs.FeedRef, aliasDB roomdb.AliasService) Handler { | ||
var h Handler | ||
h.self = self | ||
h.logger = log | ||
h.db = aliasDB | ||
|
||
return h | ||
} | ||
|
||
const sigSuffix = ".sig.ed25519" | ||
|
||
// Register is an async muxrpc method handler for registering aliases. | ||
// It receives two string arguments over muxrpc (alias and signature), | ||
// checks the signature confirmation is correct (for this room and signed by the key of theconnection) | ||
// If it is valid, it registers the alias on the roomdb and returns true. If not it returns an error. | ||
func (h Handler) Register(ctx context.Context, req *muxrpc.Request) (interface{}, error) { | ||
var args []string | ||
|
||
err := json.Unmarshal(req.RawArgs, &args) | ||
if err != nil { | ||
return nil, fmt.Errorf("registerAlias: bad request: %w", err) | ||
} | ||
|
||
if n := len(args); n != 2 { | ||
return nil, fmt.Errorf("registerAlias: expected two arguments got %d", n) | ||
} | ||
|
||
if !strings.HasSuffix(args[1], sigSuffix) { | ||
return nil, fmt.Errorf("registerAlias: signature does not have the expected suffix") | ||
} | ||
|
||
// remove the suffix of the base64 string | ||
sig := strings.TrimSuffix(args[1], sigSuffix) | ||
|
||
var confirmation aliases.Confirmation | ||
confirmation.RoomID = h.self | ||
confirmation.Alias = args[0] | ||
confirmation.Signature, err = base64.StdEncoding.DecodeString(sig) | ||
if err != nil { | ||
return nil, fmt.Errorf("registerAlias: bad signature encoding: %w", err) | ||
} | ||
|
||
// check alias is valid | ||
if !aliases.IsValid(confirmation.Alias) { | ||
return nil, fmt.Errorf("registerAlias: invalid alias") | ||
} | ||
|
||
// get the user from the muxrpc connection | ||
userID, err := network.GetFeedRefFromAddr(req.RemoteAddr()) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
confirmation.UserID = *userID | ||
|
||
// check the signature | ||
if !confirmation.Verify() { | ||
return nil, fmt.Errorf("registerAlias: invalid signature") | ||
} | ||
|
||
err = h.db.Register(ctx, confirmation.Alias, confirmation.UserID, confirmation.Signature) | ||
if err != nil { | ||
return nil, fmt.Errorf("registerAlias: could not register alias: %w", err) | ||
} | ||
|
||
return true, nil | ||
} | ||
|
||
// Revoke checks that the alias is from that user before revoking the alias from the database. | ||
func (h Handler) Revoke(ctx context.Context, req *muxrpc.Request) (interface{}, error) { | ||
var args []string | ||
|
||
err := json.Unmarshal(req.RawArgs, &args) | ||
if err != nil { | ||
return nil, fmt.Errorf("registerAlias: bad request: %w", err) | ||
} | ||
|
||
if n := len(args); n != 1 { | ||
return nil, fmt.Errorf("registerAlias: expected two arguments got %d", n) | ||
} | ||
|
||
// get the user from the muxrpc connection | ||
userID, err := network.GetFeedRefFromAddr(req.RemoteAddr()) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
alias, err := h.db.Resolve(ctx, args[0]) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if !alias.Feed.Equal(userID) { | ||
return nil, fmt.Errorf("revokeAlias: not your alias (moderators need to use the web dashboard of the room") | ||
} | ||
|
||
err = h.db.Revoke(ctx, alias.Name) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return true, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.