Sign and Check ToFU Server Bundle
the build was successful Details

This commit is contained in:
Sarah Jamie Lewis 2020-10-01 10:13:45 -07:00
parent 3f522d4d23
commit b8d308763c
7 changed files with 171 additions and 13 deletions

15
model/errors.go Normal file
View File

@ -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")
)

View File

@ -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)

64
model/keyBundle_test.go Normal file
View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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...")