forked from cwtch.im/cwtch
Merge pull request 'Sign and Check ToFU Server Bundle' (#321) from tapir_server into master
Reviewed-on: cwtch.im/cwtch#321
This commit is contained in:
commit
f9b345fc10
|
@ -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
|
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.)
|
// KeyType provides a wrapper for a generic public key type identifier (could be an onion address, a zcash address etc.)
|
||||||
type KeyType string
|
type KeyType string
|
||||||
|
@ -21,7 +28,15 @@ type Key string
|
||||||
|
|
||||||
// KeyBundle manages a collection of related keys for various different services.
|
// KeyBundle manages a collection of related keys for various different services.
|
||||||
type KeyBundle struct {
|
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.
|
// 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")
|
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
|
// AttributeBundle returns a map that can be used as part of a peer attribute bundle
|
||||||
func (kb *KeyBundle) AttributeBundle() map[string]string {
|
func (kb *KeyBundle) AttributeBundle() map[string]string {
|
||||||
ab := make(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.
|
// 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.
|
// 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 {
|
func (cp *cwtchPeer) AddServer(serverSpecification string) error {
|
||||||
keyBundle := new(model.KeyBundle)
|
// This confirms that the server did at least sign the bundle
|
||||||
err := json.Unmarshal([]byte(serverSpecification), &keyBundle)
|
keyBundle, err := model.DeserializeAndVerify([]byte(serverSpecification))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
log.Debugf("Got new key bundle %v", keyBundle)
|
log.Debugf("Got new key bundle %v", keyBundle)
|
||||||
if keyBundle.HasKeyType(model.KeyTypeServerOnion) {
|
if keyBundle.HasKeyType(model.KeyTypeServerOnion) {
|
||||||
onionKey, _ := keyBundle.GetKey(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))
|
cp.eventBus.Publish(event.NewEventList(event.SetPeerAttribute, event.RemotePeer, onion, event.SaveHistoryKey, event.DeleteHistoryDefault))
|
||||||
return nil
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
cwtchserver "cwtch.im/cwtch/server"
|
cwtchserver "cwtch.im/cwtch/server"
|
||||||
"cwtch.im/tapir/primitives"
|
"cwtch.im/tapir/primitives"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"git.openprivacy.ca/openprivacy/connectivity/tor"
|
"git.openprivacy.ca/openprivacy/connectivity/tor"
|
||||||
"git.openprivacy.ca/openprivacy/log"
|
"git.openprivacy.ca/openprivacy/log"
|
||||||
|
@ -64,7 +63,7 @@ func main() {
|
||||||
}
|
}
|
||||||
fmt.Printf("%v", "torv3"+base64.StdEncoding.EncodeToString(invite))
|
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))
|
log.Infof("Server Config: server:%s", base64.StdEncoding.EncodeToString(bundle))
|
||||||
server.Run(acn)
|
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)
|
// KeyBundle provides the signed keybundle of the server
|
||||||
func (s *Server) KeyBundle() model.KeyBundle {
|
func (s *Server) KeyBundle() *model.KeyBundle {
|
||||||
kb := model.KeyBundle{Keys: make(map[model.KeyType]model.Key)}
|
kb := model.NewKeyBundle()
|
||||||
identity := s.config.Identity()
|
identity := s.config.Identity()
|
||||||
kb.Keys[model.KeyTypeServerOnion] = model.Key(identity.Hostname())
|
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.Keys[model.KeyTypePrivacyPass] = model.Key(s.tokenServer.Y.String())
|
||||||
|
kb.Sign(identity)
|
||||||
return kb
|
return kb
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -204,7 +204,9 @@ func TestCwtchPeerIntegration(t *testing.T) {
|
||||||
// ***** Peering, server joining, group creation / invite *****
|
// ***** Peering, server joining, group creation / invite *****
|
||||||
|
|
||||||
fmt.Println("Alice joining server...")
|
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)
|
alice.JoinServer(serverAddr)
|
||||||
|
|
||||||
fmt.Println("Alice peering with Bob...")
|
fmt.Println("Alice peering with Bob...")
|
||||||
|
|
Loading…
Reference in New Issue