diff --git a/core/address.go b/core/address.go new file mode 100644 index 0000000..0cfa83b --- /dev/null +++ b/core/address.go @@ -0,0 +1,84 @@ +package core + +import ( + "crypto/rsa" + "errors" + "github.com/yawning/bulb/utils/pkcs1" + "strings" +) + +// Conversion functions between ricochet addresses, onion hostnames, and 80-bit base32 encoded fingerprints. +// As used in this file, these are referred to as 'address', 'onion', and 'plain host' respectively. + +func isBase32Valid(str string) bool { + for _, c := range []byte(str) { + if (c < 'a' || c > 'z') && (c < '2' || c > '7') { + return false + } + } + return true +} + +func IsAddressValid(addr string) bool { + return len(addr) == 25 && strings.HasPrefix(addr, "ricochet:") && isBase32Valid(addr[9:]) +} + +func IsOnionValid(onion string) bool { + return len(onion) == 22 && strings.HasSuffix(onion, ".onion") && isBase32Valid(onion[0:16]) +} + +func IsPlainHostValid(host string) bool { + return len(host) == 16 && isBase32Valid(host) +} + +func AddressFromOnion(onion string) (string, bool) { + if !IsOnionValid(onion) { + return "", false + } + return "ricochet:" + onion[0:16], true +} + +func OnionFromAddress(addr string) (string, bool) { + if !IsAddressValid(addr) { + return "", false + } + return addr[9:] + ".onion", true +} + +func OnionFromPlainHost(host string) (string, bool) { + if !IsPlainHostValid(host) { + return "", false + } + return host + ".onion", true +} + +func PlainHostFromOnion(onion string) (string, bool) { + if !IsOnionValid(onion) { + return "", false + } + return onion[0:16], true +} + +func AddressFromPlainHost(host string) (string, bool) { + if !IsPlainHostValid(host) { + return "", false + } + return "ricochet:" + host, true +} + +func PlainHostFromAddress(host string) (string, bool) { + if !IsAddressValid(host) { + return "", false + } + return host[9:], true +} + +func AddressFromKey(key *rsa.PublicKey) (string, error) { + addr, err := pkcs1.OnionAddr(key) + if err != nil { + return "", err + } else if addr == "" { + return "", errors.New("Invalid key") + } + return "ricochet:" + addr, nil +} diff --git a/core/contact.go b/core/contact.go index 26e5771..76fe079 100644 --- a/core/contact.go +++ b/core/contact.go @@ -8,7 +8,6 @@ import ( "golang.org/x/net/context" "log" "strconv" - "strings" "sync" "time" ) @@ -50,7 +49,7 @@ func ContactFromConfig(core *Ricochet, id int, data ConfigContact, events *utils if id < 0 { return nil, fmt.Errorf("Invalid contact ID '%d'", id) - } else if len(data.Hostname) != 22 || !strings.HasSuffix(data.Hostname, ".onion") { + } else if !IsOnionValid(data.Hostname) { return nil, fmt.Errorf("Invalid contact hostname '%s", data.Hostname) } @@ -78,7 +77,8 @@ func (c *Contact) Nickname() string { func (c *Contact) Address() string { c.mutex.Lock() defer c.mutex.Unlock() - return "ricochet:" + c.data.Hostname[0:16] + address, _ := AddressFromOnion(c.data.Hostname) + return address } func (c *Contact) Hostname() string { @@ -110,9 +110,10 @@ func (c *Contact) Status() ricochet.Contact_Status { func (c *Contact) Data() *ricochet.Contact { c.mutex.Lock() defer c.mutex.Unlock() + address, _ := AddressFromOnion(c.data.Hostname) data := &ricochet.Contact{ Id: int32(c.id), - Address: "ricochet:" + c.data.Hostname[0:16], + Address: address, Nickname: c.data.Nickname, WhenCreated: c.data.WhenCreated, LastConnected: c.data.LastConnected, @@ -134,9 +135,10 @@ func (c *Contact) Conversation() *Conversation { c.mutex.Lock() defer c.mutex.Unlock() if c.conversation == nil { + address, _ := AddressFromOnion(c.data.Hostname) entity := &ricochet.Entity{ ContactId: int32(c.id), - Address: "ricochet:" + c.data.Hostname[0:16], + Address: address, } c.conversation = NewConversation(c, entity, c.core.Identity.ConversationStream) } diff --git a/core/contactlist.go b/core/contactlist.go index 06a2866..be8bf14 100644 --- a/core/contactlist.go +++ b/core/contactlist.go @@ -81,10 +81,13 @@ func (this *ContactList) ContactByAddress(address string) *Contact { } func (this *ContactList) AddContactRequest(address, name, fromName, text string) (*Contact, error) { + if !IsAddressValid(address) { + return nil, errors.New("Invalid ricochet address") + } + this.mutex.Lock() defer this.mutex.Unlock() - // XXX check that address is valid before relying on format below // XXX validity checks on name/text also useful for _, contact := range this.contacts { @@ -111,8 +114,9 @@ func (this *ContactList) AddContactRequest(address, name, fromName, text string) } contactId := maxContactId + 1 + onion, _ := OnionFromAddress(address) configContact := ConfigContact{ - Hostname: address[9:] + ".onion", + Hostname: onion, Nickname: name, WhenCreated: time.Now().Format(time.RFC3339), Request: ConfigContactRequest{ diff --git a/core/identity.go b/core/identity.go index 11109ca..200e319 100644 --- a/core/identity.go +++ b/core/identity.go @@ -62,7 +62,7 @@ func (me *Identity) loadIdentity() error { if err != nil { return err } - me.address, err = utils.RicochetAddressFromKey(&me.privateKey.PublicKey) + me.address, err = AddressFromKey(&me.privateKey.PublicKey) if err != nil { return err } @@ -93,7 +93,7 @@ func (me *Identity) setPrivateKey(key *rsa.PrivateKey) error { config.Save() // Update Identity - me.address, err = utils.RicochetAddressFromKey(&key.PublicKey) + me.address, err = AddressFromKey(&key.PublicKey) if err != nil { return err } @@ -116,7 +116,11 @@ func (is *identityService) OnNewConnection(oc *protocol.OpenConnection) { handler := &ProtocolConnection{ Conn: oc, GetContactByHostname: func(hostname string) *Contact { - return identity.ContactList().ContactByAddress("ricochet:" + hostname) + address, ok := AddressFromPlainHost(hostname) + if !ok { + return nil + } + return identity.ContactList().ContactByAddress(address) }, } go oc.Process(handler) diff --git a/core/onionconnector.go b/core/onionconnector.go index d066732..55ca903 100644 --- a/core/onionconnector.go +++ b/core/onionconnector.go @@ -5,7 +5,6 @@ import ( "golang.org/x/net/context" "log" "net" - "strings" "time" ) @@ -38,7 +37,7 @@ func (oc *OnionConnector) Connect(address string, c context.Context) (net.Conn, } host, _, err := net.SplitHostPort(address) - if err != nil || !strings.HasSuffix(host, ".onion") { + if err != nil || !IsOnionValid(host) { return nil, errors.New("Invalid address") } diff --git a/core/utils/misc.go b/core/utils/misc.go index 6c95c23..9e24759 100644 --- a/core/utils/misc.go +++ b/core/utils/misc.go @@ -1,12 +1,5 @@ package utils -import ( - "crypto/rsa" - "errors" - "github.com/yawning/bulb/utils/pkcs1" - "strings" -) - // Take a string containing substrings separated by sep, and // return a slice of substrings as in strings.Split. Double quotes // are stripped from the output, and separator characters within @@ -35,27 +28,3 @@ func UnquoteStringSplit(s string, sep rune) []string { return append(re, current) } - -func RicochetAddressFromKey(key *rsa.PublicKey) (string, error) { - addr, err := pkcs1.OnionAddr(key) - if err != nil { - return "", err - } else if addr == "" { - return "", errors.New("Invalid key") - } - return "ricochet:" + addr, nil -} - -func RicochetAddressFromOnion(onion string) (string, error) { - if len(onion) != 23 || !strings.HasSuffix(onion, ".onion") { - return "", errors.New("Invalid onion address") - } - return "ricochet:" + onion[:16], nil -} - -func OnionFromRicochetAddress(address string) (string, error) { - if len(address) != 25 || !strings.HasPrefix(address, "ricochet:") { - return "", errors.New("Invalid ricochet address") - } - return address[9:] + ".onion", nil -}