Sign and Check ToFU Server Bundle #321
|
@ -0,0 +1,15 @@
|
|||
package model
|
||||
|
||||
// Error models some common errors that need to be handled by applications that use Cwtch
|
||||
type Error string
|
||||
|
||||
// Error is the error interface
|
||||
func (e Error) Error() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
// Error definitions
|
||||
const (
|
||||
InvalidEd25519PublicKey = Error("InvalidEd25519PublicKey")
|
||||
InconsistentKeyBundleError = Error("InconsistentKeyBundleError")
|
||||
)
|
|
@ -1,6 +1,13 @@
|
|||
package model
|
||||
|
||||
import "errors"
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"cwtch.im/tapir/primitives"
|
||||
"encoding/base32"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// KeyType provides a wrapper for a generic public key type identifier (could be an onion address, a zcash address etc.)
|
||||
type KeyType string
|
||||
|
@ -21,7 +28,15 @@ type Key string
|
|||
|
||||
// KeyBundle manages a collection of related keys for various different services.
|
||||
type KeyBundle struct {
|
||||
Keys map[KeyType]Key
|
||||
Keys map[KeyType]Key
|
||||
Signature []byte
|
||||
}
|
||||
|
||||
// NewKeyBundle creates a new KeyBundle initialized with no keys.
|
||||
func NewKeyBundle() *KeyBundle {
|
||||
keyBundle := new(KeyBundle)
|
||||
keyBundle.Keys = make(map[KeyType]Key)
|
||||
return keyBundle
|
||||
}
|
||||
|
||||
// HasKeyType returns true if the bundle has a public key of a given type.
|
||||
|
@ -39,6 +54,41 @@ func (kb *KeyBundle) GetKey(keytype KeyType) (Key, error) {
|
|||
return "", errors.New("no such key")
|
||||
}
|
||||
|
||||
// Serialize produces a json encoded byte array.
|
||||
func (kb KeyBundle) Serialize() []byte {
|
||||
// json.Marshal sorts map keys
|
||||
bundle, _ := json.Marshal(kb)
|
||||
return bundle
|
||||
}
|
||||
|
||||
// Sign allows a server to authenticate a key bundle by signing it (this uses the tapir identity interface)
|
||||
func (kb *KeyBundle) Sign(identity primitives.Identity) {
|
||||
kb.Signature = identity.Sign(kb.Serialize())
|
||||
}
|
||||
|
||||
// DeserializeAndVerify takes in a json formatted bundle and only returns a valid key bundle
|
||||
// if it has been signed by the server.
|
||||
func DeserializeAndVerify(bundle []byte) (*KeyBundle, error) {
|
||||
keyBundle := new(KeyBundle)
|
||||
err := json.Unmarshal(bundle, &keyBundle)
|
||||
if err == nil {
|
||||
signature := keyBundle.Signature
|
||||
keyBundle.Signature = nil
|
||||
serverKey, _ := keyBundle.GetKey(KeyTypeServerOnion)
|
||||
|
||||
// We have to do convert the encoded key to a format that can be used to verify the signature
|
||||
var decodedPub []byte
|
||||
decodedPub, err = base32.StdEncoding.DecodeString(strings.ToUpper(string(serverKey)))
|
||||
if err == nil && len(decodedPub) == 35 {
|
||||
if ed25519.Verify(decodedPub[:32], keyBundle.Serialize(), signature) == true {
|
||||
return keyBundle, nil
|
||||
}
|
||||
}
|
||||
err = InvalidEd25519PublicKey
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// AttributeBundle returns a map that can be used as part of a peer attribute bundle
|
||||
func (kb *KeyBundle) AttributeBundle() map[string]string {
|
||||
ab := make(map[string]string)
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"cwtch.im/tapir/primitives"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDeserializeAndVerify(t *testing.T) {
|
||||
server, _ := primitives.InitializeEphemeralIdentity()
|
||||
|
||||
serverKeyBundle := NewKeyBundle()
|
||||
|
||||
serverKeyBundle.Keys[KeyTypeServerOnion] = Key(server.Hostname())
|
||||
serverKeyBundle.Keys[KeyTypePrivacyPass] = Key("random 1")
|
||||
serverKeyBundle.Keys[KeyTypeTokenOnion] = Key("random 2")
|
||||
serverKeyBundle.Sign(server)
|
||||
|
||||
//eyeball keys are sorted
|
||||
t.Logf("%s", serverKeyBundle.Serialize())
|
||||
serialize := serverKeyBundle.Serialize()
|
||||
|
||||
newKeyBundle, err := DeserializeAndVerify(serialize)
|
||||
if err != nil {
|
||||
t.Fatalf("Key Bundle did not Deserialize %v", err)
|
||||
}
|
||||
|
||||
if newKeyBundle.Keys[KeyTypeServerOnion] != Key(server.Hostname()) {
|
||||
t.Fatalf("Key Bundle did not Serialize Correctly Actual: %v Expected: %v", newKeyBundle, serverKeyBundle)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeserializeAndVerifyMaliciousSignShouldFail(t *testing.T) {
|
||||
server, _ := primitives.InitializeEphemeralIdentity()
|
||||
maliciousServer, _ := primitives.InitializeEphemeralIdentity()
|
||||
serverKeyBundle := NewKeyBundle()
|
||||
|
||||
serverKeyBundle.Keys[KeyTypeServerOnion] = Key(server.Hostname())
|
||||
|
||||
// This time we sign with a malicious server
|
||||
serverKeyBundle.Sign(maliciousServer)
|
||||
serialize := serverKeyBundle.Serialize()
|
||||
|
||||
newKeyBundle, err := DeserializeAndVerify(serialize)
|
||||
if err == nil {
|
||||
t.Fatalf("Key Bundle did Deserialize (it should have failed): %v", newKeyBundle)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeserializeAndVerifyUnsignedShouldFail(t *testing.T) {
|
||||
server, _ := primitives.InitializeEphemeralIdentity()
|
||||
|
||||
serverKeyBundle := NewKeyBundle()
|
||||
|
||||
serverKeyBundle.Keys[KeyTypeServerOnion] = Key(server.Hostname())
|
||||
|
||||
// This time we don't sign
|
||||
// serverKeyBundle.Sign(server)
|
||||
serialize := serverKeyBundle.Serialize()
|
||||
|
||||
newKeyBundle, err := DeserializeAndVerify(serialize)
|
||||
if err == nil {
|
||||
t.Fatalf("Key Bundle did Deserialize (it should have failed): %v", newKeyBundle)
|
||||
}
|
||||
}
|
|
@ -209,8 +209,11 @@ func (cp *cwtchPeer) AddContact(nick, onion string, authorization model.Authoriz
|
|||
// server assuming there are no errors and the contact doesn't already exist.
|
||||
// TODO in the future this function should also integrate with a trust provider to validate the key bundle.
|
||||
func (cp *cwtchPeer) AddServer(serverSpecification string) error {
|
||||
keyBundle := new(model.KeyBundle)
|
||||
err := json.Unmarshal([]byte(serverSpecification), &keyBundle)
|
||||
// This confirms that the server did at least sign the bundle
|
||||
keyBundle, err := model.DeserializeAndVerify([]byte(serverSpecification))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("Got new key bundle %v", keyBundle)
|
||||
if keyBundle.HasKeyType(model.KeyTypeServerOnion) {
|
||||
onionKey, _ := keyBundle.GetKey(model.KeyTypeServerOnion)
|
||||
|
@ -238,8 +241,32 @@ func (cp *cwtchPeer) AddServer(serverSpecification string) error {
|
|||
cp.eventBus.Publish(event.NewEventList(event.SetPeerAttribute, event.RemotePeer, onion, event.SaveHistoryKey, event.DeleteHistoryDefault))
|
||||
return nil
|
||||
}
|
||||
// Server Already Exists
|
||||
return errors.New("a contact already exists with the given onion address")
|
||||
|
||||
// We have already seen this server and so some additional checks are needed (and we don't need to create the
|
||||
// peer).
|
||||
server := cp.GetContact(onion)
|
||||
ab := keyBundle.AttributeBundle()
|
||||
|
||||
// Check server bundle for consistency
|
||||
for k, v := range ab {
|
||||
val, exists := server.GetAttribute(k)
|
||||
if exists {
|
||||
if val != v {
|
||||
// this is inconsistent!
|
||||
return model.InconsistentKeyBundleError
|
||||
}
|
||||
}
|
||||
// we haven't seen this key associated with the server before
|
||||
|
||||
}
|
||||
|
||||
// If we have gotten to this point we can assume this is a safe key bundle signed by the
|
||||
// server with no conflicting keys. So we are going to publish all the keys
|
||||
for k, v := range ab {
|
||||
log.Debugf("Server (%v) has %v key %v", onion, k, v)
|
||||
cp.eventBus.Publish(event.NewEventList(event.SetPeerAttribute, event.RemotePeer, onion, k, v))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
cwtchserver "cwtch.im/cwtch/server"
|
||||
"cwtch.im/tapir/primitives"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"git.openprivacy.ca/openprivacy/connectivity/tor"
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
|
@ -64,7 +63,7 @@ func main() {
|
|||
}
|
||||
fmt.Printf("%v", "torv3"+base64.StdEncoding.EncodeToString(invite))
|
||||
|
||||
bundle, _ := json.Marshal(server.KeyBundle())
|
||||
bundle := server.KeyBundle().Serialize()
|
||||
log.Infof("Server Config: server:%s", base64.StdEncoding.EncodeToString(bundle))
|
||||
server.Run(acn)
|
||||
}
|
||||
|
|
|
@ -86,13 +86,14 @@ func (s *Server) Run(acn connectivity.ACN) {
|
|||
}
|
||||
}
|
||||
|
||||
// KeyBundle provides the keybundle of the server (mostly used for testing)
|
||||
func (s *Server) KeyBundle() model.KeyBundle {
|
||||
kb := model.KeyBundle{Keys: make(map[model.KeyType]model.Key)}
|
||||
// KeyBundle provides the signed keybundle of the server
|
||||
func (s *Server) KeyBundle() *model.KeyBundle {
|
||||
kb := model.NewKeyBundle()
|
||||
identity := s.config.Identity()
|
||||
kb.Keys[model.KeyTypeServerOnion] = model.Key(identity.Hostname())
|
||||
kb.Keys[model.KeyTypeTokenOnion] = model.Key(tor.GetTorV3Hostname(s.tokenService.PublicKey()))
|
||||
kb.Keys[model.KeyTypeTokenOnion] = model.Key(s.tokenService.Hostname())
|
||||
kb.Keys[model.KeyTypePrivacyPass] = model.Key(s.tokenServer.Y.String())
|
||||
kb.Sign(identity)
|
||||
return kb
|
||||
}
|
||||
|
||||
|
|
|
@ -204,7 +204,9 @@ func TestCwtchPeerIntegration(t *testing.T) {
|
|||
// ***** Peering, server joining, group creation / invite *****
|
||||
|
||||
fmt.Println("Alice joining server...")
|
||||
alice.AddServer(string(serverKeyBundle))
|
||||
if err := alice.AddServer(string(serverKeyBundle)); err != nil {
|
||||
t.Fatalf("Failed to Add Server Bundle %v", err)
|
||||
}
|
||||
alice.JoinServer(serverAddr)
|
||||
|
||||
fmt.Println("Alice peering with Bob...")
|
||||
|
|
Loading…
Reference in New Issue
we don't record anywhere if we def didnt see the key or exit the function so we will ALWAS fall through to adding, even if it already exists in the bundle?
We can't do anything with the bundle until we've confirmed it isn't malicious so we first iterate through all the keys and then if that check passes we just publish the update key attributes (which yeah means if the user calls addserver twice on the same bundle it it will do some unnecessary work, but it simplifies the function a lot).