├── internal ├── box │ ├── box.go │ └── box_test.go ├── json.go ├── ansi.go └── log.go ├── screenshots ├── client.gif └── server.gif ├── vuvuzela_test.go ├── confs ├── bob.conf ├── alice.conf ├── local-first.conf ├── local-middle.conf ├── local-last.conf └── pki.conf ├── params.go ├── msgtype_string.go ├── LICENSE ├── pki_test.go ├── noise_test.go ├── vuvuzela-client ├── conversation_test.go ├── main.go ├── dialer.go ├── client.go ├── conversation.go └── gui.go ├── boxkey.go ├── vrpc └── client.go ├── noise.go ├── vuvuzela.go ├── pki.go ├── entry_api.go ├── vuvuzela-server ├── histogram.go └── server.go ├── README.md ├── dial.go ├── vuvuzela-entry-server └── server.go ├── convo.go └── agpl-3.0.txt /internal/box/box.go: -------------------------------------------------------------------------------- 1 | package box 2 | -------------------------------------------------------------------------------- /screenshots/client.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkgs/vuvuzela/master/screenshots/client.gif -------------------------------------------------------------------------------- /screenshots/server.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkgs/vuvuzela/master/screenshots/server.gif -------------------------------------------------------------------------------- /vuvuzela_test.go: -------------------------------------------------------------------------------- 1 | package vuvuzela 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestDialExchangeMarshal(t *testing.T) { 8 | ex := new(DialExchange) 9 | _ = ex.Marshal() 10 | } 11 | -------------------------------------------------------------------------------- /confs/bob.conf: -------------------------------------------------------------------------------- 1 | { 2 | "MyName": "bob", 3 | "MyPublicKey": "nhd9ja88j65zwmnszw0b12zg1wqgqqmq382tafyw3gd642e9s1ag", 4 | "MyPrivateKey": "wqdzrmdyvk7ee8w37r8ey56pt5dkkfz64039qhzv3w81a75bgsc0" 5 | } 6 | -------------------------------------------------------------------------------- /confs/alice.conf: -------------------------------------------------------------------------------- 1 | { 2 | "MyName": "alice", 3 | "MyPublicKey": "j10hpqtgnqc1y21xp5y7yamwa32jvdp89888q2semnxg95j4v82g", 4 | "MyPrivateKey": "82v7008ke1dyzatzq04mrtnxt5s92vnfxpdgr61rbtw30hbge330" 5 | } 6 | -------------------------------------------------------------------------------- /confs/local-first.conf: -------------------------------------------------------------------------------- 1 | { 2 | "ServerName": "local-first", 3 | "DebugAddr": ":12718", 4 | "PublicKey": "pd04y1ryrfxtrayjg9f4cfsw1ayfhwrcfd7g7emhfjrsc4cd20f0", 5 | "PrivateKey": "v5sr0d6d2efr3hrbfw5qxxsnvhqh44kkqed1f43txe4qr8rhk310", 6 | "ConvoMu": 1000.0, 7 | "ConvoB": 4.0, 8 | "DialMu": 100.0, 9 | "DialB": 4.0 10 | } 11 | -------------------------------------------------------------------------------- /confs/local-middle.conf: -------------------------------------------------------------------------------- 1 | { 2 | "ServerName": "local-middle", 3 | "ListenAddr": ":2719", 4 | "PublicKey": "349bs143gvm7n0kxwhsaayeta2ptjrybwf37s4j7sj0yfrc3dxs0", 5 | "PrivateKey": "c7g9y76ehpc90w3a9t541705enragpzg6p588b5xn8pnvk0a5h50", 6 | "ConvoMu": 1000.0, 7 | "ConvoB": 4.0, 8 | "DialMu": 100.0, 9 | "DialB": 4.0 10 | } 11 | -------------------------------------------------------------------------------- /confs/local-last.conf: -------------------------------------------------------------------------------- 1 | { 2 | "ServerName": "local-last", 3 | "ListenAddr": ":2720", 4 | "DebugAddr": ":12719", 5 | "PublicKey": "fkaf8ds0a4fmdsztqzpcn4em9npyv722bxv2683n9fdydzdjwgy0", 6 | "PrivateKey": "bvypy8wgg8a5tag3zw8r4atx8e31qcdrqxvveaz5cdv46s5sjyb0", 7 | "ConvoMu": 1000.0, 8 | "ConvoB": 4.0, 9 | "DialMu": 100.0, 10 | "DialB": 4.0 11 | } 12 | -------------------------------------------------------------------------------- /internal/json.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | 7 | log "github.com/Sirupsen/logrus" 8 | ) 9 | 10 | func ReadJSONFile(path string, val interface{}) { 11 | f, err := os.Open(path) 12 | if err != nil { 13 | log.Fatal(err) 14 | } 15 | defer f.Close() 16 | if err := json.NewDecoder(f).Decode(val); err != nil { 17 | log.Fatalf("json decoding error: %s", err) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /params.go: -------------------------------------------------------------------------------- 1 | package vuvuzela 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | const ( 8 | SizeMessage = 240 9 | 10 | // Eventually this might be dynamic, but one bucket is usually 11 | // sufficient if users don't dial very often. 12 | TotalDialBuckets = 1 13 | 14 | DialWait = 10 * time.Second 15 | DefaultReceiveWait = 5 * time.Second 16 | 17 | DefaultServerAddr = ":2718" 18 | DefaultServerPort = "2718" 19 | ) 20 | -------------------------------------------------------------------------------- /msgtype_string.go: -------------------------------------------------------------------------------- 1 | // generated by stringer -type=MsgType; DO NOT EDIT 2 | 3 | package vuvuzela 4 | 5 | import "fmt" 6 | 7 | const _MsgType_name = "MsgConvoRequestMsgDialRequestMsgBadRequestErrorMsgConvoErrorMsgConvoResponseMsgDialErrorMsgDialBucketMsgAnnounceConvoRoundMsgAnnounceDialRound" 8 | 9 | var _MsgType_index = [...]uint8{0, 15, 29, 47, 60, 76, 88, 101, 122, 142} 10 | 11 | func (i MsgType) String() string { 12 | if i >= MsgType(len(_MsgType_index)-1) { 13 | return fmt.Sprintf("MsgType(%d)", i) 14 | } 15 | return _MsgType_name[_MsgType_index[i]:_MsgType_index[i+1]] 16 | } 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Vuvuzela: Scalable Private Messaging 2 | Copyright (C) 2015-2016 David Lazar 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU Affero General Public License as 6 | published by the Free Software Foundation, either version 3 of the 7 | License, or (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Affero General Public License for more details. 13 | 14 | You should have received a copy of the GNU Affero General Public License 15 | along with this program. If not, see . 16 | -------------------------------------------------------------------------------- /confs/pki.conf: -------------------------------------------------------------------------------- 1 | { 2 | "People": { 3 | "alice": "j10hpqtgnqc1y21xp5y7yamwa32jvdp89888q2semnxg95j4v82g", 4 | "bob": "nhd9ja88j65zwmnszw0b12zg1wqgqqmq382tafyw3gd642e9s1ag" 5 | }, 6 | "Servers": { 7 | "local-first": { 8 | "Address": "localhost", 9 | "PublicKey": "pd04y1ryrfxtrayjg9f4cfsw1ayfhwrcfd7g7emhfjrsc4cd20f0" 10 | }, 11 | "local-middle": { 12 | "Address": "localhost:2719", 13 | "PublicKey": "349bs143gvm7n0kxwhsaayeta2ptjrybwf37s4j7sj0yfrc3dxs0" 14 | }, 15 | "local-last": { 16 | "Address": "localhost:2720", 17 | "PublicKey": "fkaf8ds0a4fmdsztqzpcn4em9npyv722bxv2683n9fdydzdjwgy0" 18 | } 19 | }, 20 | "ServerOrder": ["local-first", "local-middle", "local-last"], 21 | "EntryServer": "ws://localhost:8080" 22 | } 23 | -------------------------------------------------------------------------------- /internal/box/box_test.go: -------------------------------------------------------------------------------- 1 | package box 2 | 3 | import ( 4 | "crypto/rand" 5 | "golang.org/x/crypto/nacl/box" 6 | "testing" 7 | ) 8 | 9 | func BenchmarkSeal(b *testing.B) { 10 | _, myPrivate, _ := box.GenerateKey(rand.Reader) 11 | theirPublic, _, _ := box.GenerateKey(rand.Reader) 12 | message := make([]byte, 256) 13 | nonce := new([24]byte) 14 | b.ResetTimer() 15 | for i := 0; i < b.N; i++ { 16 | box.Seal(nil, message, nonce, theirPublic, myPrivate) 17 | } 18 | } 19 | 20 | func BenchmarkSealAfterPrecomputation(b *testing.B) { 21 | _, myPrivate, _ := box.GenerateKey(rand.Reader) 22 | theirPublic, _, _ := box.GenerateKey(rand.Reader) 23 | message := make([]byte, 256) 24 | nonce := new([24]byte) 25 | sharedKey := new([32]byte) 26 | box.Precompute(sharedKey, theirPublic, myPrivate) 27 | b.ResetTimer() 28 | for i := 0; i < b.N; i++ { 29 | box.SealAfterPrecomputation(nil, message, nonce, sharedKey) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /pki_test.go: -------------------------------------------------------------------------------- 1 | package vuvuzela 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func Key(s string) *BoxKey { 8 | k, err := KeyFromString(s) 9 | if err != nil { 10 | panic(err) 11 | } 12 | return k 13 | } 14 | 15 | var testPKI = &PKI{ 16 | People: map[string]*BoxKey{ 17 | "david": Key("st50pjmxgzv6pybrnxrxjd330s8hf37g5gzs1dqywy4bw3kdvcgg"), 18 | "alice": Key("j10hpqtgnqc1y21xp5y7yamwa32jvdp89888q2semnxg95j4v82g"), 19 | }, 20 | Servers: map[string]*ServerInfo{ 21 | "openstack1": { 22 | Address: "localhost", 23 | PublicKey: Key("pd04y1ryrfxtrayjg9f4cfsw1ayfhwrcfd7g7emhfjrsc4cd20f0"), 24 | }, 25 | "openstack2": { 26 | Address: "localhost:2719", 27 | PublicKey: Key("fkaf8ds0a4fmdsztqzpcn4em9npyv722bxv2683n9fdydzdjwgy0"), 28 | }, 29 | }, 30 | ServerOrder: []string{"openstack1", "openstack2"}, 31 | } 32 | 33 | func TestNextKeys(t *testing.T) { 34 | nextKeys := testPKI.NextServerKeys("openstack1") 35 | if len(nextKeys) != 1 { 36 | t.Fatalf("wrong length") 37 | } 38 | if nextKeys[0] != testPKI.Servers["openstack2"].PublicKey { 39 | t.Fatalf("wrong key") 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /noise_test.go: -------------------------------------------------------------------------------- 1 | package vuvuzela 2 | 3 | import ( 4 | "crypto/rand" 5 | "flag" 6 | "os" 7 | "runtime" 8 | "testing" 9 | 10 | "golang.org/x/crypto/nacl/box" 11 | ) 12 | 13 | var mu = 100000 14 | 15 | const numKeys = 2 16 | 17 | func BenchmarkFillWithFakeSingles(b *testing.B) { 18 | noise := make([][]byte, mu) 19 | keys := genKeys(numKeys) 20 | nonce := new([24]byte) 21 | b.ResetTimer() 22 | for i := 0; i < b.N; i++ { 23 | FillWithFakeSingles(noise, nonce, keys) 24 | } 25 | } 26 | 27 | func BenchmarkFillWithFakeDoubles(b *testing.B) { 28 | noise := make([][]byte, mu) 29 | keys := genKeys(numKeys) 30 | nonce := new([24]byte) 31 | b.ResetTimer() 32 | for i := 0; i < b.N; i++ { 33 | FillWithFakeDoubles(noise, nonce, keys) 34 | } 35 | } 36 | 37 | func TestMain(m *testing.M) { 38 | runtime.GOMAXPROCS(runtime.NumCPU()) 39 | flag.IntVar(&mu, "mu", 100000, "mu value") 40 | flag.Parse() 41 | os.Exit(m.Run()) 42 | } 43 | 44 | func genKeys(i int) []*[32]byte { 45 | keys := make([]*[32]byte, i) 46 | for i := range keys { 47 | pub, _, err := box.GenerateKey(rand.Reader) 48 | if err != nil { 49 | panic(err) 50 | } 51 | keys[i] = pub 52 | } 53 | return keys 54 | } 55 | -------------------------------------------------------------------------------- /vuvuzela-client/conversation_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "testing" 7 | "time" 8 | 9 | . "vuvuzela.io/vuvuzela" 10 | ) 11 | 12 | func TestSoloConversation(t *testing.T) { 13 | public, private, err := GenerateBoxKey(rand.Reader) 14 | if err != nil { 15 | t.Fatal(err) 16 | } 17 | convo := &Conversation{ 18 | peerPublicKey: public, 19 | myPublicKey: public, 20 | myPrivateKey: private, 21 | } 22 | if convo.myRole() != convo.theirRole() { 23 | t.Fatalf("expecting roles to match") 24 | } 25 | 26 | msg := make([]byte, 256) 27 | rand.Read(msg) 28 | 29 | var round uint32 = 42 30 | ctxt := convo.Seal(msg, round, convo.myRole()) 31 | xmsg, ok := convo.Open(ctxt, round, convo.theirRole()) 32 | if !ok { 33 | t.Fatalf("failed to decrypt message") 34 | } 35 | 36 | if bytes.Compare(msg, xmsg) != 0 { 37 | t.Fatalf("messages don't match") 38 | } 39 | } 40 | 41 | func TestMarshalConvoMessage(t *testing.T) { 42 | now := time.Now() 43 | tsm := &TimestampMessage{Timestamp: now} 44 | cm := &ConvoMessage{Body: tsm} 45 | data := cm.Marshal() 46 | 47 | xcm := new(ConvoMessage) 48 | if err := xcm.Unmarshal(data[:]); err != nil { 49 | t.Fatalf("Unmarshal error: %s", err) 50 | } 51 | 52 | xtsm := xcm.Body.(*TimestampMessage) 53 | if xtsm.Timestamp.Unix() != now.Unix() { 54 | t.Fatalf("timestamps don't match") 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /internal/ansi.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | ) 7 | 8 | type ansiCode int 9 | 10 | const ( 11 | ansiReset ansiCode = 0 12 | ansiReverse = 7 13 | red = 31 14 | green = 32 15 | yellow = 33 16 | blue = 34 17 | magenta = 35 18 | cyan = 36 19 | ) 20 | 21 | var allColors = []ansiCode{red, green, yellow, blue, magenta, cyan} 22 | 23 | type ansiFormatter struct { 24 | value interface{} 25 | codes []ansiCode 26 | } 27 | 28 | func color(value interface{}, codes ...ansiCode) interface{} { 29 | if len(codes) == 0 { 30 | return value 31 | } 32 | return &ansiFormatter{value, codes} 33 | } 34 | 35 | func (af *ansiFormatter) Format(f fmt.State, c rune) { 36 | // reconstruct the format string in bf 37 | bf := new(bytes.Buffer) 38 | bf.WriteByte('%') 39 | for _, x := range []byte{'-', '+', '#', ' ', '0'} { 40 | if f.Flag(int(x)) { 41 | bf.WriteByte(x) 42 | } 43 | } 44 | if w, ok := f.Width(); ok { 45 | fmt.Fprint(bf, w) 46 | } 47 | if p, ok := f.Precision(); ok { 48 | fmt.Fprintf(bf, ".%d", p) 49 | } 50 | bf.WriteRune(c) 51 | format := bf.String() 52 | 53 | if len(af.codes) == 0 { 54 | fmt.Fprintf(f, format, af.value) 55 | return 56 | } 57 | 58 | fmt.Fprintf(f, "\x1b[%d", af.codes[0]) 59 | for _, code := range af.codes[1:] { 60 | fmt.Fprintf(f, ";%d", code) 61 | } 62 | f.Write([]byte{'m'}) 63 | fmt.Fprintf(f, format, af.value) 64 | fmt.Fprint(f, "\x1b[0m") 65 | } 66 | -------------------------------------------------------------------------------- /boxkey.go: -------------------------------------------------------------------------------- 1 | package vuvuzela 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | 8 | "github.com/davidlazar/go-crypto/encoding/base32" 9 | "golang.org/x/crypto/nacl/box" 10 | ) 11 | 12 | type BoxKey [32]byte 13 | 14 | func GenerateBoxKey(rand io.Reader) (publicKey, privateKey *BoxKey, err error) { 15 | pub, priv, err := box.GenerateKey(rand) 16 | return (*BoxKey)(pub), (*BoxKey)(priv), err 17 | } 18 | 19 | func (k *BoxKey) Key() *[32]byte { 20 | return (*[32]byte)(k) 21 | } 22 | 23 | func (k *BoxKey) String() string { 24 | return base32.EncodeToString(k[:]) 25 | } 26 | 27 | func KeyFromString(s string) (*BoxKey, error) { 28 | key := new(BoxKey) 29 | b, err := base32.DecodeString(s) 30 | if err != nil { 31 | return nil, fmt.Errorf("base32 decode error: %s", err) 32 | } 33 | if copy(key[:], b) < 32 { 34 | return nil, fmt.Errorf("short key") 35 | } 36 | return key, nil 37 | } 38 | 39 | func (k *BoxKey) MarshalJSON() ([]byte, error) { 40 | return json.Marshal(k.String()) 41 | } 42 | 43 | func (k *BoxKey) UnmarshalJSON(b []byte) error { 44 | var s string 45 | if err := json.Unmarshal(b, &s); err != nil { 46 | return err 47 | } 48 | bs, err := base32.DecodeString(s) 49 | if err != nil { 50 | return fmt.Errorf("base32 decode error: %s", err) 51 | } 52 | if copy(k[:], bs) < 32 { 53 | return fmt.Errorf("short key") 54 | } 55 | return nil 56 | } 57 | 58 | type BoxKeys []*BoxKey 59 | 60 | func (keys BoxKeys) Keys() []*[32]byte { 61 | xs := make([]*[32]byte, len(keys)) 62 | for i := range keys { 63 | xs[i] = (*[32]byte)(keys[i]) 64 | } 65 | return xs 66 | } 67 | -------------------------------------------------------------------------------- /vuvuzela-client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/json" 6 | "flag" 7 | "io/ioutil" 8 | 9 | log "github.com/Sirupsen/logrus" 10 | 11 | . "vuvuzela.io/vuvuzela" 12 | . "vuvuzela.io/vuvuzela/internal" 13 | ) 14 | 15 | var doInit = flag.Bool("init", false, "create default config file") 16 | var confPath = flag.String("conf", "confs/client.conf", "config file") 17 | var pkiPath = flag.String("pki", "confs/pki.conf", "pki file") 18 | 19 | type Conf struct { 20 | MyName string 21 | MyPublicKey *BoxKey 22 | MyPrivateKey *BoxKey 23 | } 24 | 25 | func WriteDefaultConf(path string) { 26 | myPublicKey, myPrivateKey, err := GenerateBoxKey(rand.Reader) 27 | if err != nil { 28 | log.Fatalf("GenerateBoxKey: %s", err) 29 | } 30 | conf := &Conf{ 31 | MyPublicKey: myPublicKey, 32 | MyPrivateKey: myPrivateKey, 33 | } 34 | 35 | data, err := json.MarshalIndent(conf, "", " ") 36 | if err != nil { 37 | log.Fatalf("json encoding error: %s", err) 38 | } 39 | if err := ioutil.WriteFile(path, data, 0600); err != nil { 40 | log.Fatalf("WriteFile: %s", err) 41 | } 42 | } 43 | 44 | func main() { 45 | flag.Parse() 46 | 47 | if *doInit { 48 | WriteDefaultConf(*confPath) 49 | return 50 | } 51 | 52 | pki := ReadPKI(*pkiPath) 53 | 54 | conf := new(Conf) 55 | ReadJSONFile(*confPath, conf) 56 | if conf.MyName == "" || conf.MyPublicKey == nil || conf.MyPrivateKey == nil { 57 | log.Fatalf("missing required fields: %s", *confPath) 58 | } 59 | 60 | gc := &GuiClient{ 61 | pki: pki, 62 | myName: conf.MyName, 63 | myPublicKey: conf.MyPublicKey, 64 | myPrivateKey: conf.MyPrivateKey, 65 | } 66 | gc.Run() 67 | } 68 | -------------------------------------------------------------------------------- /vrpc/client.go: -------------------------------------------------------------------------------- 1 | package vrpc 2 | 3 | import ( 4 | "net/rpc" 5 | ) 6 | 7 | type Client struct { 8 | rpcClients []*rpc.Client 9 | } 10 | 11 | func Dial(network, address string, connections int) (*Client, error) { 12 | rpcClients := make([]*rpc.Client, connections) 13 | for i := range rpcClients { 14 | c, err := rpc.Dial(network, address) 15 | if err != nil { 16 | return nil, err 17 | } 18 | rpcClients[i] = c 19 | } 20 | return &Client{ 21 | rpcClients: rpcClients, 22 | }, nil 23 | } 24 | 25 | func (c *Client) Call(method string, args interface{}, reply interface{}) error { 26 | return c.rpcClients[0].Call(method, args, reply) 27 | } 28 | 29 | type Call struct { 30 | Method string 31 | Args interface{} 32 | Reply interface{} 33 | } 34 | 35 | func (c *Client) CallMany(calls []*Call) error { 36 | if len(calls) == 0 { 37 | return nil 38 | } 39 | 40 | done := make(chan struct{}) 41 | callChan := make(chan *Call, 4) 42 | 43 | go func() { 44 | for _, c := range calls { 45 | select { 46 | case callChan <- c: 47 | // ok 48 | case <-done: 49 | break 50 | } 51 | } 52 | close(callChan) 53 | }() 54 | 55 | results := make(chan *rpc.Call, len(calls)) 56 | for _, rc := range c.rpcClients { 57 | go func(rc *rpc.Client) { 58 | for call := range callChan { 59 | rc.Go(call.Method, call.Args, call.Reply, results) 60 | } 61 | }(rc) 62 | } 63 | 64 | var err error 65 | var received int 66 | for call := range results { 67 | err = call.Error 68 | if err != nil { 69 | break 70 | } 71 | 72 | received++ 73 | if received == len(calls) { 74 | close(results) 75 | break 76 | } 77 | } 78 | 79 | close(done) 80 | return err 81 | } 82 | -------------------------------------------------------------------------------- /noise.go: -------------------------------------------------------------------------------- 1 | package vuvuzela 2 | 3 | import ( 4 | "encoding/binary" 5 | 6 | "vuvuzela.io/concurrency" 7 | "vuvuzela.io/crypto/onionbox" 8 | "vuvuzela.io/crypto/rand" 9 | ) 10 | 11 | func FillWithFakeSingles(dest [][]byte, nonce *[24]byte, nextKeys []*[32]byte) { 12 | concurrency.ParallelFor(len(dest), func(p *concurrency.P) { 13 | for i, ok := p.Next(); ok; i, ok = p.Next() { 14 | var exchange [SizeConvoExchange]byte 15 | rand.Read(exchange[:]) 16 | onion, _ := onionbox.Seal(exchange[:], nonce, nextKeys) 17 | dest[i] = onion 18 | } 19 | }) 20 | } 21 | 22 | func FillWithFakeDoubles(dest [][]byte, nonce *[24]byte, nextKeys []*[32]byte) { 23 | concurrency.ParallelFor(len(dest)/2, func(p *concurrency.P) { 24 | for i, ok := p.Next(); ok; i, ok = p.Next() { 25 | var exchange1 [SizeConvoExchange]byte 26 | var exchange2 [SizeConvoExchange]byte 27 | rand.Read(exchange1[:]) 28 | copy(exchange2[0:16], exchange1[0:16]) 29 | rand.Read(exchange2[16:]) 30 | onion1, _ := onionbox.Seal(exchange1[:], nonce, nextKeys) 31 | onion2, _ := onionbox.Seal(exchange2[:], nonce, nextKeys) 32 | dest[i*2] = onion1 33 | dest[i*2+1] = onion2 34 | } 35 | }) 36 | } 37 | 38 | func FillWithFakeIntroductions(dest [][]byte, noiseCounts []uint32, nonce *[24]byte, nextKeys []*[32]byte) { 39 | buckets := make([]int, len(dest)) 40 | idx := 0 41 | for b, count := range noiseCounts { 42 | for i := uint32(0); i < count; i++ { 43 | buckets[idx] = b 44 | idx++ 45 | } 46 | } 47 | 48 | concurrency.ParallelFor(len(dest), func(p *concurrency.P) { 49 | for i, ok := p.Next(); ok; i, ok = p.Next() { 50 | var exchange [SizeDialExchange]byte 51 | binary.BigEndian.PutUint32(exchange[0:4], uint32(buckets[i])) 52 | rand.Read(exchange[4:]) 53 | onion, _ := onionbox.Seal(exchange[:], nonce, nextKeys) 54 | dest[i] = onion 55 | } 56 | }) 57 | } 58 | -------------------------------------------------------------------------------- /vuvuzela-client/dialer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/rand" 5 | 6 | "golang.org/x/crypto/nacl/box" 7 | 8 | "vuvuzela.io/crypto/onionbox" 9 | . "vuvuzela.io/vuvuzela" 10 | ) 11 | 12 | type Dialer struct { 13 | gui *GuiClient 14 | pki *PKI 15 | myPublicKey *BoxKey 16 | myPrivateKey *BoxKey 17 | 18 | userDialRequests chan *BoxKey 19 | } 20 | 21 | func (d *Dialer) Init() { 22 | d.userDialRequests = make(chan *BoxKey, 4) 23 | } 24 | 25 | func (d *Dialer) QueueRequest(publicKey *BoxKey) { 26 | d.userDialRequests <- publicKey 27 | } 28 | 29 | func (d *Dialer) NextDialRequest(round uint32, buckets uint32) *DialRequest { 30 | var ex *DialExchange 31 | select { 32 | case pk := <-d.userDialRequests: 33 | intro := (&Introduction{ 34 | Rendezvous: round + 4, 35 | LongTermKey: *d.myPublicKey, 36 | }).Marshal() 37 | ctxt, _ := onionbox.Seal(intro, ForwardNonce(round), BoxKeys{pk}.Keys()) 38 | ex = &DialExchange{ 39 | Bucket: KeyDialBucket(pk, buckets), 40 | } 41 | copy(ex.EncryptedIntro[:], ctxt) 42 | default: 43 | ex = &DialExchange{ 44 | Bucket: 0, 45 | } 46 | rand.Read(ex.EncryptedIntro[:]) 47 | } 48 | 49 | onion, _ := onionbox.Seal(ex.Marshal(), ForwardNonce(round), d.pki.ServerKeys().Keys()) 50 | 51 | return &DialRequest{ 52 | Round: round, 53 | Onion: onion, 54 | } 55 | } 56 | 57 | func (d *Dialer) HandleDialBucket(db *DialBucket) { 58 | nonce := ForwardNonce(db.Round) 59 | 60 | OUTER: 61 | for _, b := range db.Intros { 62 | var pk [32]byte 63 | copy(pk[:], b[0:32]) 64 | data, ok := box.Open(nil, b[32:], nonce, &pk, d.myPrivateKey.Key()) 65 | if !ok { 66 | continue 67 | } 68 | 69 | intro := new(Introduction) 70 | if err := intro.Unmarshal(data); err != nil { 71 | continue 72 | } 73 | 74 | for name, key := range d.pki.People { 75 | if *key == intro.LongTermKey { 76 | d.gui.Warnf("Received introduction: %s\n", name) 77 | continue OUTER 78 | } 79 | } 80 | d.gui.Warnf("Received introduction: (%s)\n", &intro.LongTermKey) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /vuvuzela.go: -------------------------------------------------------------------------------- 1 | package vuvuzela 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "unsafe" 7 | 8 | "golang.org/x/crypto/nacl/box" 9 | 10 | "vuvuzela.io/crypto/onionbox" 11 | ) 12 | 13 | type DeadDrop [16]byte 14 | 15 | const ( 16 | SizeEncryptedMessage = SizeMessage + box.Overhead 17 | SizeConvoExchange = int(unsafe.Sizeof(ConvoExchange{})) 18 | SizeEncryptedIntro = int(unsafe.Sizeof(Introduction{})) + onionbox.Overhead 19 | SizeDialExchange = int(unsafe.Sizeof(DialExchange{})) 20 | ) 21 | 22 | type ConvoExchange struct { 23 | DeadDrop DeadDrop 24 | EncryptedMessage [SizeEncryptedMessage]byte 25 | } 26 | 27 | func (e *ConvoExchange) Marshal() []byte { 28 | buf := new(bytes.Buffer) 29 | if err := binary.Write(buf, binary.BigEndian, e); err != nil { 30 | panic(err) 31 | } 32 | return buf.Bytes() 33 | } 34 | 35 | func (e *ConvoExchange) Unmarshal(data []byte) error { 36 | buf := bytes.NewReader(data) 37 | return binary.Read(buf, binary.BigEndian, e) 38 | } 39 | 40 | type Introduction struct { 41 | Rendezvous uint32 42 | LongTermKey BoxKey 43 | } 44 | 45 | func (i *Introduction) Marshal() []byte { 46 | buf := new(bytes.Buffer) 47 | if err := binary.Write(buf, binary.BigEndian, i); err != nil { 48 | panic(err) 49 | } 50 | return buf.Bytes() 51 | } 52 | 53 | func (i *Introduction) Unmarshal(data []byte) error { 54 | buf := bytes.NewReader(data) 55 | return binary.Read(buf, binary.BigEndian, i) 56 | } 57 | 58 | type DialExchange struct { 59 | Bucket uint32 60 | EncryptedIntro [SizeEncryptedIntro]byte 61 | } 62 | 63 | func (e *DialExchange) Marshal() []byte { 64 | buf := new(bytes.Buffer) 65 | if err := binary.Write(buf, binary.BigEndian, e); err != nil { 66 | panic(err) 67 | } 68 | return buf.Bytes() 69 | } 70 | 71 | func (e *DialExchange) Unmarshal(data []byte) error { 72 | buf := bytes.NewReader(data) 73 | return binary.Read(buf, binary.BigEndian, e) 74 | } 75 | 76 | func ForwardNonce(round uint32) *[24]byte { 77 | var nonce [24]byte 78 | binary.BigEndian.PutUint32(nonce[0:4], round) 79 | nonce[4] = 0 80 | return &nonce 81 | } 82 | 83 | func BackwardNonce(round uint32) *[24]byte { 84 | var nonce [24]byte 85 | binary.BigEndian.PutUint32(nonce[0:4], round) 86 | nonce[4] = 1 87 | return &nonce 88 | } 89 | 90 | func KeyDialBucket(key *BoxKey, buckets uint32) uint32 { 91 | return binary.BigEndian.Uint32(key[28:32])%buckets + 1 92 | } 93 | -------------------------------------------------------------------------------- /pki.go: -------------------------------------------------------------------------------- 1 | package vuvuzela 2 | 3 | import ( 4 | "net" 5 | "strings" 6 | 7 | log "github.com/Sirupsen/logrus" 8 | "golang.org/x/crypto/nacl/box" 9 | 10 | "vuvuzela.io/crypto/onionbox" 11 | . "vuvuzela.io/vuvuzela/internal" 12 | ) 13 | 14 | type ServerInfo struct { 15 | Address string 16 | PublicKey *BoxKey 17 | } 18 | 19 | type PKI struct { 20 | People map[string]*BoxKey 21 | Servers map[string]*ServerInfo 22 | ServerOrder []string 23 | EntryServer string 24 | } 25 | 26 | func ReadPKI(jsonPath string) *PKI { 27 | pki := new(PKI) 28 | ReadJSONFile(jsonPath, pki) 29 | if len(pki.ServerOrder) == 0 { 30 | log.Fatalf("%q: ServerOrder must contain at least one server", jsonPath) 31 | } 32 | for _, s := range pki.ServerOrder { 33 | info, ok := pki.Servers[s] 34 | if !ok { 35 | log.Fatalf("%q: server %q not found", jsonPath, s) 36 | } 37 | addr := info.Address 38 | if addr == "" { 39 | log.Fatalf("%q: server %q does not specify an Address", jsonPath, s) 40 | } 41 | 42 | if strings.IndexByte(addr, ':') == -1 { 43 | info.Address = net.JoinHostPort(addr, DefaultServerPort) 44 | } 45 | } 46 | return pki 47 | } 48 | 49 | func (pki *PKI) ServerKeys() BoxKeys { 50 | keys := make([]*BoxKey, 0, 3) 51 | for _, s := range pki.ServerOrder { 52 | info := pki.Servers[s] 53 | keys = append(keys, info.PublicKey) 54 | } 55 | return keys 56 | } 57 | 58 | func (pki *PKI) FirstServer() string { 59 | s := pki.ServerOrder[0] 60 | return pki.Servers[s].Address 61 | } 62 | 63 | func (pki *PKI) LastServer() string { 64 | s := pki.ServerOrder[len(pki.ServerOrder)-1] 65 | return pki.Servers[s].Address 66 | } 67 | 68 | func (pki *PKI) Index(serverName string) int { 69 | for i, s := range pki.ServerOrder { 70 | if s == serverName { 71 | return i 72 | } 73 | } 74 | log.Fatalf("pki.Index: server %q not found", serverName) 75 | return -1 76 | } 77 | 78 | func (pki *PKI) NextServer(serverName string) string { 79 | i := pki.Index(serverName) 80 | if i < len(pki.ServerOrder)-1 { 81 | s := pki.ServerOrder[i+1] 82 | return pki.Servers[s].Address 83 | } else { 84 | return "" 85 | } 86 | } 87 | 88 | func (pki *PKI) NextServerKeys(serverName string) BoxKeys { 89 | i := pki.Index(serverName) 90 | var keys []*BoxKey 91 | for _, s := range pki.ServerOrder[i+1:] { 92 | keys = append(keys, pki.Servers[s].PublicKey) 93 | } 94 | return keys 95 | } 96 | 97 | func (pki *PKI) IncomingOnionOverhead(serverName string) int { 98 | i := len(pki.ServerOrder) - pki.Index(serverName) 99 | return i * onionbox.Overhead 100 | } 101 | 102 | func (pki *PKI) OutgoingOnionOverhead(serverName string) int { 103 | i := len(pki.ServerOrder) - pki.Index(serverName) 104 | return i * box.Overhead 105 | } 106 | -------------------------------------------------------------------------------- /internal/log.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | 7 | log "github.com/Sirupsen/logrus" 8 | ) 9 | 10 | type ServerFormatter struct{} 11 | 12 | func (f *ServerFormatter) Format(entry *log.Entry) ([]byte, error) { 13 | buf := new(bytes.Buffer) 14 | 15 | ts := entry.Time.Format("15:04:05") 16 | 17 | var tsColor []ansiCode 18 | switch entry.Level { 19 | case log.ErrorLevel, log.FatalLevel, log.PanicLevel: 20 | tsColor = append(tsColor, red) 21 | } 22 | if bug, hasBug := entry.Data["bug"].(bool); bug && hasBug { 23 | tsColor = append(tsColor, ansiReverse) 24 | } 25 | 26 | fmt.Fprintf(buf, "%s | ", color(ts, tsColor...)) 27 | 28 | service, _ := entry.Data["service"].(string) 29 | round, hasRound := entry.Data["round"].(uint32) 30 | rpc, hasRpc := entry.Data["rpc"].(string) 31 | call, hasCall := entry.Data["call"].(string) 32 | 33 | if hasRound { 34 | c := allColors[int(round)%len(allColors)] 35 | switch service { 36 | case "dial": 37 | fmt.Fprintf(buf, "%d ", color(round, c, ansiReverse)) 38 | default: 39 | fmt.Fprintf(buf, "%d ", color(round, c)) 40 | } 41 | } 42 | 43 | if hasRpc { 44 | fmt.Fprintf(buf, "%s ", rpc) 45 | } 46 | if hasCall { 47 | fmt.Fprintf(buf, "%s ", call) 48 | } 49 | if entry.Message != "" { 50 | fmt.Fprintf(buf, "%s ", entry.Message) 51 | } 52 | 53 | for _, k := range []string{"service", "round", "rpc", "call"} { 54 | delete(entry.Data, k) 55 | } 56 | 57 | if len(entry.Data) > 0 { 58 | fmt.Fprint(buf, "| ") 59 | writeMap(buf, entry.Data) 60 | } 61 | 62 | buf.WriteByte('\n') 63 | return buf.Bytes(), nil 64 | } 65 | 66 | type GuiFormatter struct{} 67 | 68 | // TODO gocui doesn't support color text: 69 | // https://github.com/jroimartin/gocui/issues/9 70 | func (f *GuiFormatter) Format(entry *log.Entry) ([]byte, error) { 71 | buf := new(bytes.Buffer) 72 | 73 | ts := entry.Time.Format("15:04:05") 74 | 75 | fmt.Fprintf(buf, "%s %s | ", ts, entry.Level.String()) 76 | 77 | call, hasCall := entry.Data["call"].(string) 78 | if hasCall { 79 | fmt.Fprintf(buf, "%s ", call) 80 | } 81 | if entry.Message != "" { 82 | fmt.Fprintf(buf, "%s ", entry.Message) 83 | } 84 | 85 | for _, k := range []string{"call"} { 86 | delete(entry.Data, k) 87 | } 88 | 89 | if len(entry.Data) > 0 { 90 | fmt.Fprint(buf, "| ") 91 | writeMap(buf, entry.Data) 92 | } 93 | 94 | buf.WriteByte('\n') 95 | return buf.Bytes(), nil 96 | } 97 | 98 | func writeMap(buf *bytes.Buffer, m map[string]interface{}) { 99 | for k, v := range m { 100 | buf.WriteString(k) 101 | buf.WriteByte('=') 102 | switch v := v.(type) { 103 | case string, error: 104 | fmt.Fprintf(buf, "%q", v) 105 | default: 106 | fmt.Fprint(buf, v) 107 | } 108 | buf.WriteByte(' ') 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /entry_api.go: -------------------------------------------------------------------------------- 1 | // Types used by the entry server and client 2 | package vuvuzela 3 | 4 | import ( 5 | "encoding/json" 6 | "fmt" 7 | ) 8 | 9 | //go:generate stringer -type=MsgType 10 | type MsgType uint8 11 | 12 | const ( 13 | // from client to server 14 | MsgConvoRequest MsgType = iota 15 | MsgDialRequest 16 | 17 | // from server to client 18 | MsgBadRequestError 19 | MsgConvoError 20 | MsgConvoResponse 21 | MsgDialError 22 | MsgDialBucket 23 | MsgAnnounceConvoRound 24 | MsgAnnounceDialRound 25 | ) 26 | 27 | type Envelope struct { 28 | Type MsgType 29 | Message json.RawMessage 30 | } 31 | 32 | func (e *Envelope) Open() (interface{}, error) { 33 | var v interface{} 34 | switch e.Type { 35 | case MsgConvoRequest: 36 | v = new(ConvoRequest) 37 | case MsgDialRequest: 38 | v = new(DialRequest) 39 | case MsgBadRequestError: 40 | v = new(BadRequestError) 41 | case MsgConvoError: 42 | v = new(ConvoError) 43 | case MsgConvoResponse: 44 | v = new(ConvoResponse) 45 | case MsgDialBucket: 46 | v = new(DialBucket) 47 | case MsgAnnounceConvoRound: 48 | v = new(AnnounceConvoRound) 49 | case MsgAnnounceDialRound: 50 | v = new(AnnounceDialRound) 51 | default: 52 | return nil, fmt.Errorf("unknown message type: %d", e.Type) 53 | } 54 | if err := json.Unmarshal(e.Message, v); err != nil { 55 | return nil, fmt.Errorf("json.Unmarshal: %s", err) 56 | } 57 | return v, nil 58 | } 59 | 60 | func Envelop(v interface{}) (*Envelope, error) { 61 | var t MsgType 62 | switch v.(type) { 63 | case *ConvoRequest: 64 | t = MsgConvoRequest 65 | case *DialRequest: 66 | t = MsgDialRequest 67 | case *BadRequestError: 68 | t = MsgBadRequestError 69 | case *ConvoError: 70 | t = MsgConvoError 71 | case *ConvoResponse: 72 | t = MsgConvoResponse 73 | case *DialError: 74 | t = MsgDialError 75 | case *DialBucket: 76 | t = MsgDialBucket 77 | case *AnnounceConvoRound: 78 | t = MsgAnnounceConvoRound 79 | case *AnnounceDialRound: 80 | t = MsgAnnounceDialRound 81 | default: 82 | return nil, fmt.Errorf("unsupported message type: %T", v) 83 | } 84 | data, err := json.Marshal(v) 85 | if err != nil { 86 | return nil, fmt.Errorf("json.Marshal: %s", err) 87 | } 88 | return &Envelope{ 89 | Type: t, 90 | Message: data, 91 | }, nil 92 | } 93 | 94 | type ConvoRequest struct { 95 | Round uint32 96 | Onion []byte 97 | } 98 | 99 | type DialRequest struct { 100 | Round uint32 101 | Onion []byte 102 | } 103 | 104 | type BadRequestError struct { 105 | Err string 106 | } 107 | 108 | func (e *BadRequestError) Error() string { 109 | return e.Err 110 | } 111 | 112 | type ConvoError struct { 113 | Round uint32 114 | Err string 115 | } 116 | 117 | func (e *ConvoError) Error() string { 118 | return fmt.Sprintf("round c%d: %s", e.Round, e.Err) 119 | } 120 | 121 | type ConvoResponse struct { 122 | Round uint32 123 | Onion []byte 124 | } 125 | 126 | type DialError struct { 127 | Round uint32 128 | Err string 129 | } 130 | 131 | func (e *DialError) Error() string { 132 | return fmt.Sprintf("round d%d: %s", e.Round, e.Err) 133 | } 134 | 135 | type DialBucket struct { 136 | Round uint32 137 | Intros [][SizeEncryptedIntro]byte 138 | } 139 | 140 | type AnnounceConvoRound struct { 141 | Round uint32 142 | } 143 | 144 | type AnnounceDialRound struct { 145 | Round uint32 146 | Buckets uint32 147 | } 148 | -------------------------------------------------------------------------------- /vuvuzela-server/histogram.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | log "github.com/Sirupsen/logrus" 7 | "gopkg.in/gizak/termui.v1" 8 | 9 | "vuvuzela.io/vuvuzela" 10 | ) 11 | 12 | type Histogram struct { 13 | Mu float64 14 | NumServers int 15 | 16 | singles []int 17 | doubles []int 18 | 19 | normalizedSingles []int 20 | normalizedDoubles []int 21 | 22 | spSingles *termui.Sparklines 23 | spDoubles *termui.Sparklines 24 | } 25 | 26 | func (h *Histogram) resize() { 27 | tw := termui.TermWidth() - 4 28 | h.spSingles.Width = tw 29 | h.spDoubles.Width = tw 30 | 31 | th := termui.TermHeight()/2 - 1 32 | h.spSingles.Height = th 33 | h.spDoubles.Height = th 34 | if th > 3 { 35 | h.spSingles.Lines[0].Height = th - 3 36 | h.spDoubles.Lines[0].Height = th - 3 37 | } else { 38 | h.spSingles.Lines[0].Height = 1 39 | h.spDoubles.Lines[0].Height = 1 40 | } 41 | 42 | h.spDoubles.Y = th + 2 43 | 44 | termui.Body.Width = termui.TermWidth() 45 | termui.Body.Align() 46 | h.render() 47 | } 48 | 49 | // Shift the distribution so the user can more clearly see the variation 50 | // in noise across rounds. 51 | func (h *Histogram) render() { 52 | singleShift := (h.NumServers-1)*int(h.Mu) - h.spSingles.Height - 32 53 | doubleShift := (h.NumServers-1)*int(h.Mu/2) - h.spDoubles.Height - 16 54 | 55 | for i := range h.singles { 56 | if s := h.singles[i]; s == 0 { 57 | h.normalizedSingles[i] = 0 58 | } else if n := s - singleShift; n > 2 { 59 | h.normalizedSingles[i] = n 60 | } else { 61 | // to prevent confusion, don't let the sparkline go to 0 62 | h.normalizedSingles[i] = 2 63 | } 64 | 65 | if s := h.doubles[i]; s == 0 { 66 | h.normalizedDoubles[i] = 0 67 | } else if n := s - doubleShift; n > 2 { 68 | h.normalizedDoubles[i] = n 69 | } else { 70 | h.normalizedDoubles[i] = 2 71 | } 72 | } 73 | 74 | h.spSingles.Lines[0].Data = h.normalizedSingles 75 | h.spDoubles.Lines[0].Data = h.normalizedDoubles 76 | termui.Render(h.spSingles, h.spDoubles) 77 | } 78 | 79 | func (h *Histogram) run(accessCounts chan *vuvuzela.AccessCount) { 80 | h.singles = make([]int, 512) 81 | h.doubles = make([]int, 512) 82 | h.normalizedSingles = make([]int, 512) 83 | h.normalizedDoubles = make([]int, 512) 84 | 85 | // log will corrupt display, so only log errors 86 | log.SetLevel(log.ErrorLevel) 87 | 88 | err := termui.Init() 89 | if err != nil { 90 | panic(err) 91 | } 92 | defer termui.Close() 93 | 94 | termui.UseTheme("helloworld") 95 | th := termui.Theme() 96 | th.BodyBg = termui.ColorDefault 97 | th.BlockBg = termui.ColorDefault 98 | th.BorderBg = termui.ColorDefault 99 | th.BorderLabelTextBg = termui.ColorDefault 100 | termui.SetTheme(th) 101 | 102 | spSingles := termui.NewSparkline() 103 | spSingles.Data = h.singles 104 | spSingles.LineColor = termui.ColorBlue 105 | 106 | spDoubles := termui.NewSparkline() 107 | spDoubles.Data = h.doubles 108 | spDoubles.LineColor = termui.ColorMagenta 109 | 110 | h.spSingles = termui.NewSparklines(spSingles) 111 | h.spSingles.X = 2 112 | h.spSingles.Y = 1 113 | h.spSingles.Border.Label = "Idle Users" 114 | 115 | h.spDoubles = termui.NewSparklines(spDoubles) 116 | h.spDoubles.X = 2 117 | h.spDoubles.Border.Label = "Active Users" 118 | 119 | h.resize() 120 | 121 | for { 122 | select { 123 | case e := <-termui.EventCh(): 124 | if e.Type == termui.EventKey && e.Ch == 'q' { 125 | log.SetLevel(log.InfoLevel) 126 | return 127 | } 128 | if e.Type == termui.EventKey && e.Key == termui.KeyCtrlC { 129 | termui.Close() 130 | os.Exit(1) 131 | } 132 | if e.Type == termui.EventResize { 133 | h.resize() 134 | } 135 | case a := <-accessCounts: 136 | h.singles = append(h.singles[1:], int(a.Singles)) 137 | h.doubles = append(h.doubles[1:], int(a.Doubles)) 138 | //log.Errorf("%#v", a) 139 | h.render() 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /vuvuzela-client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | 8 | log "github.com/Sirupsen/logrus" 9 | "github.com/gorilla/websocket" 10 | 11 | . "vuvuzela.io/vuvuzela" 12 | ) 13 | 14 | type Client struct { 15 | sync.Mutex 16 | 17 | EntryServer string 18 | MyPublicKey *BoxKey 19 | 20 | ws *websocket.Conn 21 | 22 | roundHandlers map[uint32]ConvoHandler 23 | convoHandler ConvoHandler 24 | dialHandler DialHandler 25 | } 26 | 27 | type ConvoHandler interface { 28 | NextConvoRequest(round uint32) *ConvoRequest 29 | HandleConvoResponse(response *ConvoResponse) 30 | } 31 | 32 | type DialHandler interface { 33 | NextDialRequest(round uint32, buckets uint32) *DialRequest 34 | HandleDialBucket(db *DialBucket) 35 | } 36 | 37 | func NewClient(entryServer string, publicKey *BoxKey) *Client { 38 | c := &Client{ 39 | EntryServer: entryServer, 40 | MyPublicKey: publicKey, 41 | 42 | roundHandlers: make(map[uint32]ConvoHandler), 43 | } 44 | return c 45 | } 46 | 47 | func (c *Client) SetConvoHandler(convo ConvoHandler) { 48 | c.Lock() 49 | c.convoHandler = convo 50 | c.Unlock() 51 | } 52 | 53 | func (c *Client) SetDialHandler(dialer DialHandler) { 54 | c.Lock() 55 | c.dialHandler = dialer 56 | c.Unlock() 57 | } 58 | 59 | func (c *Client) Connect() error { 60 | // TODO check if already connected 61 | if c.convoHandler == nil { 62 | return fmt.Errorf("no convo handler") 63 | } 64 | if c.dialHandler == nil { 65 | return fmt.Errorf("no dial handler") 66 | } 67 | 68 | wsaddr := fmt.Sprintf("%s/ws?publickey=%s", c.EntryServer, c.MyPublicKey.String()) 69 | dialer := &websocket.Dialer{ 70 | HandshakeTimeout: 5 * time.Second, 71 | } 72 | ws, _, err := dialer.Dial(wsaddr, nil) 73 | if err != nil { 74 | return err 75 | } 76 | c.ws = ws 77 | go c.readLoop() 78 | return nil 79 | } 80 | 81 | func (c *Client) Close() { 82 | c.ws.Close() 83 | } 84 | 85 | func (c *Client) Send(v interface{}) { 86 | const writeWait = 10 * time.Second 87 | 88 | e, err := Envelop(v) 89 | if err != nil { 90 | log.WithFields(log.Fields{"bug": true, "call": "Envelop"}).Error(err) 91 | return 92 | } 93 | 94 | c.Lock() 95 | c.ws.SetWriteDeadline(time.Now().Add(writeWait)) 96 | if err := c.ws.WriteJSON(e); err != nil { 97 | log.WithFields(log.Fields{"call": "WriteJSON"}).Debug(err) 98 | c.Unlock() 99 | c.Close() 100 | return 101 | } 102 | c.Unlock() 103 | } 104 | 105 | func (c *Client) readLoop() { 106 | for { 107 | var e Envelope 108 | if err := c.ws.ReadJSON(&e); err != nil { 109 | log.WithFields(log.Fields{"call": "ReadJSON"}).Debug(err) 110 | c.Close() 111 | break 112 | } 113 | 114 | v, err := e.Open() 115 | if err != nil { 116 | log.WithFields(log.Fields{"call": "Envelope.Open"}).Error(err) 117 | continue 118 | } 119 | go c.handleResponse(v) 120 | } 121 | } 122 | 123 | func (c *Client) handleResponse(v interface{}) { 124 | switch v := v.(type) { 125 | case *BadRequestError: 126 | log.Printf("bad request error: %s", v.Error()) 127 | case *AnnounceConvoRound: 128 | c.Send(c.nextConvoRequest(v.Round)) 129 | case *AnnounceDialRound: 130 | c.Send(c.dialHandler.NextDialRequest(v.Round, v.Buckets)) 131 | case *ConvoResponse: 132 | c.deliverConvoResponse(v) 133 | case *DialBucket: 134 | c.dialHandler.HandleDialBucket(v) 135 | } 136 | } 137 | 138 | func (c *Client) nextConvoRequest(round uint32) *ConvoRequest { 139 | c.Lock() 140 | c.roundHandlers[round] = c.convoHandler 141 | c.Unlock() 142 | return c.convoHandler.NextConvoRequest(round) 143 | } 144 | 145 | func (c *Client) deliverConvoResponse(r *ConvoResponse) { 146 | c.Lock() 147 | convo, ok := c.roundHandlers[r.Round] 148 | delete(c.roundHandlers, r.Round) 149 | c.Unlock() 150 | if !ok { 151 | log.WithFields(log.Fields{"round": r.Round}).Error("round not found") 152 | return 153 | } 154 | 155 | convo.HandleConvoResponse(r) 156 | } 157 | -------------------------------------------------------------------------------- /vuvuzela-server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/json" 6 | "flag" 7 | "fmt" 8 | "io/ioutil" 9 | "net" 10 | "net/http" 11 | _ "net/http/pprof" 12 | "net/rpc" 13 | "runtime" 14 | "sync" 15 | 16 | log "github.com/Sirupsen/logrus" 17 | 18 | vrand "vuvuzela.io/crypto/rand" 19 | . "vuvuzela.io/vuvuzela" 20 | . "vuvuzela.io/vuvuzela/internal" 21 | "vuvuzela.io/vuvuzela/vrpc" 22 | ) 23 | 24 | var doInit = flag.Bool("init", false, "create default config file") 25 | var confPath = flag.String("conf", "", "config file") 26 | var pkiPath = flag.String("pki", "confs/pki.conf", "pki file") 27 | var muOverride = flag.Float64("mu", -1.0, "override ConvoMu in conf file") 28 | 29 | type Conf struct { 30 | ServerName string 31 | PublicKey *BoxKey 32 | PrivateKey *BoxKey 33 | ListenAddr string `json:",omitempty"` 34 | DebugAddr string `json:",omitempty"` 35 | 36 | ConvoMu float64 37 | ConvoB float64 38 | 39 | DialMu float64 40 | DialB float64 41 | } 42 | 43 | func WriteDefaultConf(path string) { 44 | myPublicKey, myPrivateKey, err := GenerateBoxKey(rand.Reader) 45 | if err != nil { 46 | log.Fatalf("GenerateKey: %s", err) 47 | } 48 | conf := &Conf{ 49 | ServerName: "mit", 50 | PublicKey: myPublicKey, 51 | PrivateKey: myPrivateKey, 52 | } 53 | 54 | data, err := json.MarshalIndent(conf, "", " ") 55 | if err != nil { 56 | log.Fatalf("json encoding error: %s", err) 57 | } 58 | if err := ioutil.WriteFile(path, data, 0600); err != nil { 59 | log.Fatalf("WriteFile: %s", err) 60 | } 61 | fmt.Printf("wrote %q\n", path) 62 | } 63 | 64 | func main() { 65 | flag.Parse() 66 | log.SetFormatter(&ServerFormatter{}) 67 | 68 | if *confPath == "" { 69 | log.Fatalf("must specify -conf flag") 70 | } 71 | 72 | if *doInit { 73 | WriteDefaultConf(*confPath) 74 | return 75 | } 76 | 77 | pki := ReadPKI(*pkiPath) 78 | 79 | conf := new(Conf) 80 | ReadJSONFile(*confPath, conf) 81 | if conf.ServerName == "" || conf.PublicKey == nil || conf.PrivateKey == nil { 82 | log.Fatalf("missing required fields: %s", *confPath) 83 | } 84 | 85 | if *muOverride >= 0 { 86 | conf.ConvoMu = *muOverride 87 | } 88 | 89 | var err error 90 | var client *vrpc.Client 91 | if addr := pki.NextServer(conf.ServerName); addr != "" { 92 | client, err = vrpc.Dial("tcp", addr, runtime.NumCPU()) 93 | if err != nil { 94 | log.Fatalf("vrpc.Dial: %s", err) 95 | } 96 | } 97 | 98 | var idle sync.Mutex 99 | 100 | convoService := &ConvoService{ 101 | Idle: &idle, 102 | 103 | Laplace: vrand.Laplace{ 104 | Mu: conf.ConvoMu, 105 | B: conf.ConvoB, 106 | }, 107 | 108 | PKI: pki, 109 | ServerName: conf.ServerName, 110 | PrivateKey: conf.PrivateKey, 111 | 112 | Client: client, 113 | LastServer: client == nil, 114 | } 115 | InitConvoService(convoService) 116 | 117 | if convoService.LastServer { 118 | histogram := &Histogram{Mu: conf.ConvoMu, NumServers: len(pki.ServerOrder)} 119 | go histogram.run(convoService.AccessCounts) 120 | } 121 | 122 | dialService := &DialService{ 123 | Idle: &idle, 124 | 125 | Laplace: vrand.Laplace{ 126 | Mu: conf.ConvoMu, 127 | B: conf.ConvoB, 128 | }, 129 | 130 | PKI: pki, 131 | ServerName: conf.ServerName, 132 | PrivateKey: conf.PrivateKey, 133 | 134 | Client: client, 135 | LastServer: client == nil, 136 | } 137 | InitDialService(dialService) 138 | 139 | if err := rpc.Register(dialService); err != nil { 140 | log.Fatalf("rpc.Register: %s", err) 141 | } 142 | if err := rpc.Register(convoService); err != nil { 143 | log.Fatalf("rpc.Register: %s", err) 144 | } 145 | 146 | if conf.DebugAddr != "" { 147 | go func() { 148 | log.Println(http.ListenAndServe(conf.DebugAddr, nil)) 149 | }() 150 | runtime.SetBlockProfileRate(1) 151 | } 152 | 153 | if conf.ListenAddr == "" { 154 | conf.ListenAddr = DefaultServerAddr 155 | } 156 | listen, err := net.Listen("tcp", conf.ListenAddr) 157 | if err != nil { 158 | log.Fatal("Listen:", err) 159 | } 160 | rpc.Accept(listen) 161 | } 162 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > *Metadata absolutely tells you everything about somebody's life* 2 | > 3 | > — [Stewart Baker](http://www.nybooks.com/articles/archives/2013/nov/21/snowden-leaks-and-public/), former General Counsel of the NSA 4 | 5 | | 6 | 7 | > *We kill people based on metadata* 8 | > 9 | > — [Michael Hayden](https://www.youtube.com/watch?v=kV2HDM86XgI&t=17m53s), former Director of the NSA 10 | 11 | # Vuvuzela 12 | 13 | Vuvuzela is a messaging system that protects the privacy of message contents 14 | and message metadata. Users communicating through Vuvuzela do not reveal who 15 | they are talking to, even in the presence of powerful nation-state adversaries. 16 | Our [SOSP 2015 paper](https://davidlazar.org/papers/vuvuzela.pdf) explains 17 | the system, its threat model, performance, limitations, and more. Our 18 | [SOSP 2015 slides](https://davidlazar.org/slides/vuvuzela-sosp2015.pdf) give 19 | a more graphical overview of the system. 20 | 21 | Vuvuzela is the first system that provides strong metadata privacy while 22 | scaling to millions of users. Previous systems that hide metadata using 23 | Tor (such as [Pond](https://pond.imperialviolet.org/)) are prone to traffic 24 | analysis attacks. Systems that encrypt metadata using techniques like 25 | DC-nets and PIR don't scale beyond thousands of users. 26 | 27 | Vuvuzela uses efficient cryptography ([NaCl](http://nacl.cr.yp.to)) to hide as 28 | much metadata as possible and adds noise to metadata that can't be encrypted 29 | efficiently. This approach provides less privacy than encrypting all of the 30 | metadata, but it enables Vuvuzela to support millions of users. Nonetheless, 31 | Vuvuzela adds enough noise to thwart adversaries like the NSA and guarantees 32 | [differential privacy](https://en.wikipedia.org/wiki/Differential_privacy) for 33 | users' metadata. 34 | 35 | 36 | ## Screenshots 37 | 38 | **A conversation in the Vuvuzela client** 39 | 40 | ![client](https://github.com/vuvuzela/vuvuzela/blob/master/screenshots/client.gif) 41 | 42 | In practice, the message latency would be around 20s to 40s, depending 43 | on security parameters and the number of users connected to the system. 44 | 45 | **Noise generated by the Vuvuzela servers** 46 | 47 | ![server](https://github.com/vuvuzela/vuvuzela/blob/master/screenshots/server.gif) 48 | 49 | Vuvuzela is unable to encrypt two kinds of metadata: the number of idle users 50 | (connected users without a conversation partner) and the number of active users 51 | (users engaged in a conversation). Without noise, a sophisticated adversary 52 | could use this metadata to learn who is talking to who. However, the Vuvuzela 53 | servers generate noise that perturbs this metadata so that it is difficult to 54 | exploit. 55 | 56 | 57 | ## Usage 58 | 59 | Follow these steps to run the Vuvuzela system locally using the provided 60 | sample configs. 61 | 62 | 1. Install Vuvuzela (assuming `GOPATH=~/go`, requires Go 1.4 or later): 63 | 64 | $ go get vuvuzela.io/vuvuzela/... 65 | 66 | The remaining steps assume `PATH` contains `~/go/bin` and that the 67 | current working directory is `~/go/src/vuvuzela.io/vuvuzela`. 68 | 69 | 2. Start the last Vuvuzela server: 70 | 71 | $ vuvuzela-server -conf confs/local-last.conf 72 | 73 | 3. Start the middle server (in a new shell): 74 | 75 | $ vuvuzela-server -conf confs/local-middle.conf 76 | 77 | 4. Start the first server (in a new shell): 78 | 79 | $ vuvuzela-server -conf confs/local-first.conf 80 | 81 | 5. Start the entry server (in a new shell): 82 | 83 | $ vuvuzela-entry-server -wait 1s 84 | 85 | 6. Run the Vuvuzela client: 86 | 87 | $ vuvuzela-client -conf confs/alice.conf 88 | 89 | The client supports these commands: 90 | 91 | * `/dial ` to dial another user 92 | * `/talk ` to start a conversation 93 | * `/talk ` to end a conversation 94 | 95 | 96 | ## Deployment considerations 97 | 98 | This Vuvuzela implementation is not ready for wide-use deployment. 99 | In particular, we haven't yet implemented these crucial components: 100 | 101 | * **Public Key Infrastructure**: 102 | Vuvuzela assumes the existence of a PKI in which users can privately 103 | learn each others public keys. This implementation uses `pki.conf` 104 | as a placeholder until we integrate a real PKI. 105 | 106 | * **CDN to distribute dialing dead drops**: 107 | Vuvuzela's dialing protocol (used to initiate conversations) uses a 108 | lot of server bandwidth. To make dialing practical, Vuvuzela should 109 | use a CDN or BitTorrent to distribute the dialing dead drops. 110 | 111 | There is a lot more interesting work to do. See the 112 | [issue tracker](https://github.com/vuvuzela/vuvuzela/issues) 113 | for more information. 114 | 115 | 116 | ## Acknowledgements 117 | 118 | This code is written by David Lazar with contributions from 119 | Jelle van den Hooff, Nickolai Zeldovich, and Matei Zaharia. 120 | 121 | 122 | ## See also 123 | 124 | [Vuvuzela web client](https://github.com/jlmart88/vuvuzela-web-client) 125 | -------------------------------------------------------------------------------- /vuvuzela-client/conversation.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "crypto/hmac" 6 | "crypto/rand" 7 | "crypto/sha256" 8 | "encoding/binary" 9 | "fmt" 10 | "strings" 11 | "sync" 12 | "time" 13 | 14 | log "github.com/Sirupsen/logrus" 15 | "golang.org/x/crypto/nacl/box" 16 | 17 | "vuvuzela.io/crypto/onionbox" 18 | . "vuvuzela.io/vuvuzela" 19 | ) 20 | 21 | type Conversation struct { 22 | sync.RWMutex 23 | 24 | pki *PKI 25 | peerName string 26 | peerPublicKey *BoxKey 27 | myPublicKey *BoxKey 28 | myPrivateKey *BoxKey 29 | gui *GuiClient 30 | 31 | outQueue chan []byte 32 | pendingRounds map[uint32]*pendingRound 33 | 34 | lastPeerResponding bool 35 | lastLatency time.Duration 36 | lastRound uint32 37 | } 38 | 39 | func (c *Conversation) Init() { 40 | c.Lock() 41 | c.outQueue = make(chan []byte, 64) 42 | c.pendingRounds = make(map[uint32]*pendingRound) 43 | c.lastPeerResponding = false 44 | c.Unlock() 45 | } 46 | 47 | type pendingRound struct { 48 | onionSharedKeys []*[32]byte 49 | sentMessage [SizeEncryptedMessage]byte 50 | } 51 | 52 | type ConvoMessage struct { 53 | Body interface{} 54 | // seq/ack numbers can go here 55 | } 56 | 57 | type TextMessage struct { 58 | Message []byte 59 | } 60 | 61 | type TimestampMessage struct { 62 | Timestamp time.Time 63 | } 64 | 65 | func (cm *ConvoMessage) Marshal() (msg [SizeMessage]byte) { 66 | switch v := cm.Body.(type) { 67 | case *TimestampMessage: 68 | msg[0] = 0 69 | binary.PutVarint(msg[1:], v.Timestamp.Unix()) 70 | case *TextMessage: 71 | msg[0] = 1 72 | copy(msg[1:], v.Message) 73 | } 74 | return 75 | } 76 | 77 | func (cm *ConvoMessage) Unmarshal(msg []byte) error { 78 | switch msg[0] { 79 | case 0: 80 | ts, _ := binary.Varint(msg[1:]) 81 | cm.Body = &TimestampMessage{ 82 | Timestamp: time.Unix(ts, 0), 83 | } 84 | case 1: 85 | cm.Body = &TextMessage{msg[1:]} 86 | default: 87 | return fmt.Errorf("unexpected message type: %d", msg[0]) 88 | } 89 | return nil 90 | } 91 | 92 | func (c *Conversation) QueueTextMessage(msg []byte) { 93 | c.outQueue <- msg 94 | } 95 | 96 | func (c *Conversation) NextConvoRequest(round uint32) *ConvoRequest { 97 | c.Lock() 98 | c.lastRound = round 99 | c.Unlock() 100 | go c.gui.Flush() 101 | 102 | var body interface{} 103 | 104 | select { 105 | case m := <-c.outQueue: 106 | body = &TextMessage{Message: m} 107 | default: 108 | body = &TimestampMessage{ 109 | Timestamp: time.Now(), 110 | } 111 | } 112 | msg := &ConvoMessage{ 113 | Body: body, 114 | } 115 | msgdata := msg.Marshal() 116 | 117 | var encmsg [SizeEncryptedMessage]byte 118 | ctxt := c.Seal(msgdata[:], round, c.myRole()) 119 | copy(encmsg[:], ctxt) 120 | 121 | exchange := &ConvoExchange{ 122 | DeadDrop: c.deadDrop(round), 123 | EncryptedMessage: encmsg, 124 | } 125 | 126 | onion, sharedKeys := onionbox.Seal(exchange.Marshal(), ForwardNonce(round), c.pki.ServerKeys().Keys()) 127 | 128 | pr := &pendingRound{ 129 | onionSharedKeys: sharedKeys, 130 | sentMessage: encmsg, 131 | } 132 | c.Lock() 133 | c.pendingRounds[round] = pr 134 | c.Unlock() 135 | 136 | return &ConvoRequest{ 137 | Round: round, 138 | Onion: onion, 139 | } 140 | } 141 | 142 | func (c *Conversation) HandleConvoResponse(r *ConvoResponse) { 143 | rlog := log.WithFields(log.Fields{"round": r.Round}) 144 | 145 | var responding bool 146 | defer func() { 147 | c.Lock() 148 | c.lastPeerResponding = responding 149 | c.Unlock() 150 | c.gui.Flush() 151 | }() 152 | 153 | c.Lock() 154 | pr, ok := c.pendingRounds[r.Round] 155 | delete(c.pendingRounds, r.Round) 156 | c.Unlock() 157 | if !ok { 158 | rlog.Error("round not found") 159 | return 160 | } 161 | 162 | encmsg, ok := onionbox.Open(r.Onion, BackwardNonce(r.Round), pr.onionSharedKeys) 163 | if !ok { 164 | rlog.Error("decrypting onion failed") 165 | return 166 | } 167 | 168 | if bytes.Compare(encmsg, pr.sentMessage[:]) == 0 && !c.Solo() { 169 | return 170 | } 171 | 172 | msgdata, ok := c.Open(encmsg, r.Round, c.theirRole()) 173 | if !ok { 174 | rlog.Error("decrypting peer message failed") 175 | return 176 | } 177 | 178 | msg := new(ConvoMessage) 179 | if err := msg.Unmarshal(msgdata); err != nil { 180 | rlog.Error("unmarshaling peer message failed") 181 | return 182 | } 183 | 184 | responding = true 185 | 186 | switch m := msg.Body.(type) { 187 | case *TextMessage: 188 | s := strings.TrimRight(string(m.Message), "\x00") 189 | c.gui.Printf("<%s> %s\n", c.peerName, s) 190 | case *TimestampMessage: 191 | latency := time.Now().Sub(m.Timestamp) 192 | c.Lock() 193 | c.lastLatency = latency 194 | c.Unlock() 195 | } 196 | } 197 | 198 | type Status struct { 199 | PeerResponding bool 200 | Round uint32 201 | Latency float64 202 | } 203 | 204 | func (c *Conversation) Status() *Status { 205 | c.RLock() 206 | status := &Status{ 207 | PeerResponding: c.lastPeerResponding, 208 | Round: c.lastRound, 209 | Latency: float64(c.lastLatency) / float64(time.Second), 210 | } 211 | c.RUnlock() 212 | return status 213 | } 214 | 215 | func (c *Conversation) Solo() bool { 216 | return bytes.Compare(c.myPublicKey[:], c.peerPublicKey[:]) == 0 217 | } 218 | 219 | // Roles ensure that messages to the peer and messages from 220 | // the peer have distinct nonces. 221 | func (c *Conversation) myRole() byte { 222 | if bytes.Compare(c.myPublicKey[:], c.peerPublicKey[:]) < 0 { 223 | return 0 224 | } else { 225 | return 1 226 | } 227 | } 228 | 229 | func (c *Conversation) theirRole() byte { 230 | if bytes.Compare(c.peerPublicKey[:], c.myPublicKey[:]) < 0 { 231 | return 0 232 | } else { 233 | return 1 234 | } 235 | } 236 | 237 | func (c *Conversation) Seal(message []byte, round uint32, role byte) []byte { 238 | var nonce [24]byte 239 | binary.BigEndian.PutUint32(nonce[:], round) 240 | nonce[23] = role 241 | 242 | ctxt := box.Seal(nil, message, &nonce, c.peerPublicKey.Key(), c.myPrivateKey.Key()) 243 | return ctxt 244 | } 245 | 246 | func (c *Conversation) Open(ctxt []byte, round uint32, role byte) ([]byte, bool) { 247 | var nonce [24]byte 248 | binary.BigEndian.PutUint32(nonce[:], round) 249 | nonce[23] = role 250 | 251 | return box.Open(nil, ctxt, &nonce, c.peerPublicKey.Key(), c.myPrivateKey.Key()) 252 | } 253 | 254 | func (c *Conversation) deadDrop(round uint32) (id DeadDrop) { 255 | if c.Solo() { 256 | rand.Read(id[:]) 257 | } else { 258 | var sharedKey [32]byte 259 | box.Precompute(&sharedKey, c.peerPublicKey.Key(), c.myPrivateKey.Key()) 260 | 261 | h := hmac.New(sha256.New, sharedKey[:]) 262 | binary.Write(h, binary.BigEndian, round) 263 | r := h.Sum(nil) 264 | copy(id[:], r) 265 | } 266 | return 267 | } 268 | -------------------------------------------------------------------------------- /vuvuzela-client/gui.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "strings" 7 | "sync" 8 | "time" 9 | 10 | log "github.com/Sirupsen/logrus" 11 | "github.com/davidlazar/gocui" 12 | 13 | . "vuvuzela.io/vuvuzela" 14 | . "vuvuzela.io/vuvuzela/internal" 15 | ) 16 | 17 | type GuiClient struct { 18 | sync.Mutex 19 | 20 | pki *PKI 21 | myName string 22 | myPublicKey *BoxKey 23 | myPrivateKey *BoxKey 24 | 25 | gui *gocui.Gui 26 | client *Client 27 | 28 | selectedConvo *Conversation 29 | conversations map[string]*Conversation 30 | dialer *Dialer 31 | } 32 | 33 | func (gc *GuiClient) switchConversation(peer string) { 34 | var convo *Conversation 35 | 36 | convo, ok := gc.conversations[peer] 37 | if !ok { 38 | peerPublicKey, ok := gc.pki.People[peer] 39 | if !ok { 40 | gc.Warnf("unknown user: %s", peer) 41 | return 42 | } 43 | convo = &Conversation{ 44 | pki: gc.pki, 45 | peerName: peer, 46 | peerPublicKey: peerPublicKey, 47 | myPublicKey: gc.myPublicKey, 48 | myPrivateKey: gc.myPrivateKey, 49 | gui: gc, 50 | } 51 | convo.Init() 52 | gc.conversations[peer] = convo 53 | } 54 | 55 | gc.selectedConvo = convo 56 | gc.activateConvo(convo) 57 | gc.Warnf("Now talking to %s\n", peer) 58 | } 59 | 60 | func (gc *GuiClient) activateConvo(convo *Conversation) { 61 | if gc.client != nil { 62 | convo.Lock() 63 | convo.lastPeerResponding = false 64 | convo.lastLatency = 0 65 | convo.Unlock() 66 | gc.client.SetConvoHandler(convo) 67 | } 68 | } 69 | 70 | func (gc *GuiClient) handleLine(line string) error { 71 | switch { 72 | case line == "/quit": 73 | return gocui.Quit 74 | case strings.HasPrefix(line, "/talk "): 75 | peer := line[6:] 76 | gc.switchConversation(peer) 77 | case strings.HasPrefix(line, "/dial "): 78 | peer := line[6:] 79 | pk, ok := gc.pki.People[peer] 80 | if !ok { 81 | gc.Warnf("Unknown user: %q (see %s)\n", peer, *pkiPath) 82 | return nil 83 | } 84 | gc.Warnf("Dialing user: %s\n", peer) 85 | gc.dialer.QueueRequest(pk) 86 | default: 87 | msg := strings.TrimSpace(line) 88 | gc.selectedConvo.QueueTextMessage([]byte(msg)) 89 | gc.Printf("<%s> %s\n", gc.myName, msg) 90 | } 91 | return nil 92 | } 93 | 94 | func (gc *GuiClient) readLine(_ *gocui.Gui, v *gocui.View) error { 95 | // HACK: pressing enter on startup causes panic 96 | if len(v.Buffer()) == 0 { 97 | return nil 98 | } 99 | _, cy := v.Cursor() 100 | line, err := v.Line(cy - 1) 101 | if err != nil { 102 | return err 103 | } 104 | if line == "" { 105 | return nil 106 | } 107 | v.Clear() 108 | 109 | return gc.handleLine(line) 110 | } 111 | 112 | func (gc *GuiClient) Flush() { 113 | gc.gui.Flush() 114 | } 115 | 116 | func (gc *GuiClient) Warnf(format string, v ...interface{}) { 117 | mv, err := gc.gui.View("main") 118 | if err != nil { 119 | return 120 | } 121 | fmt.Fprintf(mv, "-!- "+format, v...) 122 | gc.gui.Flush() 123 | } 124 | 125 | func (gc *GuiClient) Printf(format string, v ...interface{}) { 126 | mv, err := gc.gui.View("main") 127 | if err != nil { 128 | return 129 | } 130 | fmt.Fprintf(mv, format, v...) 131 | gc.gui.Flush() 132 | } 133 | 134 | func (gc *GuiClient) layout(g *gocui.Gui) error { 135 | maxX, maxY := g.Size() 136 | if v, err := g.SetView("main", 0, -1, maxX-1, maxY-1); err != nil { 137 | if err != gocui.ErrorUnkView { 138 | return err 139 | } 140 | v.Autoscroll = true 141 | v.Wrap = true 142 | v.Frame = false 143 | log.AddHook(gc) 144 | log.SetOutput(ioutil.Discard) 145 | log.SetFormatter(&GuiFormatter{}) 146 | } 147 | sv, err := g.SetView("status", -1, maxY-3, maxX, maxY-1) 148 | if err != nil { 149 | if err != gocui.ErrorUnkView { 150 | return err 151 | } 152 | sv.Wrap = false 153 | sv.Frame = false 154 | sv.BgColor = gocui.ColorBlue 155 | sv.FgColor = gocui.ColorWhite 156 | } 157 | sv.Clear() 158 | 159 | st := gc.selectedConvo.Status() 160 | latency := fmt.Sprintf("%.1fs", st.Latency) 161 | if st.Latency == 0.0 { 162 | latency = "-" 163 | } 164 | round := fmt.Sprintf("%d", st.Round) 165 | if st.Round == 0 { 166 | round = "-" 167 | } 168 | fmt.Fprintf(sv, " [%s] [round: %s] [latency: %s]", gc.myName, round, latency) 169 | 170 | partner := "(no partner)" 171 | if !gc.selectedConvo.Solo() { 172 | partner = gc.selectedConvo.peerName 173 | } 174 | 175 | pv, err := g.SetView("partner", -1, maxY-2, len(partner)+1, maxY) 176 | if err != nil { 177 | if err != gocui.ErrorUnkView { 178 | return err 179 | } 180 | pv.Wrap = false 181 | pv.Frame = false 182 | } 183 | pv.Clear() 184 | 185 | if st.PeerResponding { 186 | pv.FgColor = gocui.ColorGreen 187 | } else { 188 | pv.FgColor = gocui.ColorRed 189 | } 190 | fmt.Fprintf(pv, "%s>", partner) 191 | 192 | if v, err := g.SetView("input", len(partner)+1, maxY-2, maxX, maxY); err != nil { 193 | if err != gocui.ErrorUnkView { 194 | return err 195 | } 196 | v.Editable = true 197 | v.Wrap = false 198 | v.Frame = false 199 | if err := g.SetCurrentView("input"); err != nil { 200 | return err 201 | } 202 | } 203 | 204 | return nil 205 | } 206 | 207 | func quit(g *gocui.Gui, v *gocui.View) error { 208 | return gocui.Quit 209 | } 210 | 211 | func (gc *GuiClient) Connect() error { 212 | if gc.client == nil { 213 | gc.client = NewClient(gc.pki.EntryServer, gc.myPublicKey) 214 | gc.client.SetDialHandler(gc.dialer) 215 | } 216 | gc.activateConvo(gc.selectedConvo) 217 | return gc.client.Connect() 218 | } 219 | 220 | func (gc *GuiClient) Run() { 221 | gui := gocui.NewGui() 222 | if err := gui.Init(); err != nil { 223 | log.Panicln(err) 224 | } 225 | defer gui.Close() 226 | gc.gui = gui 227 | 228 | if err := gui.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil { 229 | log.Panicln(err) 230 | } 231 | if err := gui.SetKeybinding("input", gocui.KeyEnter, gocui.ModNone, gc.readLine); err != nil { 232 | log.Panicln(err) 233 | } 234 | gui.ShowCursor = true 235 | gui.BgColor = gocui.ColorDefault 236 | gui.FgColor = gocui.ColorDefault 237 | gui.SetLayout(gc.layout) 238 | 239 | gc.conversations = make(map[string]*Conversation) 240 | gc.switchConversation(gc.myName) 241 | 242 | gc.dialer = &Dialer{ 243 | gui: gc, 244 | pki: gc.pki, 245 | myPublicKey: gc.myPublicKey, 246 | myPrivateKey: gc.myPrivateKey, 247 | } 248 | gc.dialer.Init() 249 | 250 | go func() { 251 | time.Sleep(500 * time.Millisecond) 252 | if err := gc.Connect(); err != nil { 253 | gc.Warnf("Failed to connect: %s\n", err) 254 | } 255 | gc.Warnf("Connected: %s\n", gc.pki.EntryServer) 256 | }() 257 | 258 | err := gui.MainLoop() 259 | if err != nil && err != gocui.Quit { 260 | log.Panicln(err) 261 | } 262 | } 263 | 264 | func (gc *GuiClient) Fire(entry *log.Entry) error { 265 | line, err := entry.String() 266 | if err != nil { 267 | return err 268 | } 269 | 270 | gc.Warnf(line) 271 | return nil 272 | } 273 | 274 | func (gc *GuiClient) Levels() []log.Level { 275 | return []log.Level{ 276 | log.PanicLevel, 277 | log.FatalLevel, 278 | log.ErrorLevel, 279 | log.WarnLevel, 280 | log.InfoLevel, 281 | log.DebugLevel, 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /dial.go: -------------------------------------------------------------------------------- 1 | package vuvuzela 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "sync" 7 | 8 | log "github.com/Sirupsen/logrus" 9 | "golang.org/x/crypto/nacl/box" 10 | 11 | "vuvuzela.io/concurrency" 12 | "vuvuzela.io/crypto/rand" 13 | "vuvuzela.io/crypto/shuffle" 14 | "vuvuzela.io/vuvuzela/vrpc" 15 | ) 16 | 17 | type DialService struct { 18 | roundsMu sync.RWMutex 19 | rounds map[uint32]*DialRound 20 | 21 | Idle *sync.Mutex 22 | 23 | Laplace rand.Laplace 24 | 25 | PKI *PKI 26 | ServerName string 27 | PrivateKey *BoxKey 28 | Client *vrpc.Client 29 | LastServer bool 30 | } 31 | 32 | type DialRound struct { 33 | sync.Mutex 34 | 35 | status dialStatus 36 | incoming [][]byte 37 | 38 | noise [][]byte 39 | noiseWg sync.WaitGroup 40 | } 41 | 42 | type dialStatus int 43 | 44 | const ( 45 | dialRoundOpen dialStatus = iota + 1 46 | dialRoundClosed 47 | ) 48 | 49 | func InitDialService(srv *DialService) { 50 | srv.rounds = make(map[uint32]*DialRound) 51 | } 52 | 53 | func (srv *DialService) getRound(round uint32, expectedStatus dialStatus) (*DialRound, error) { 54 | srv.roundsMu.RLock() 55 | r, ok := srv.rounds[round] 56 | srv.roundsMu.RUnlock() 57 | if !ok { 58 | return nil, fmt.Errorf("round %d not found", round) 59 | } 60 | if r.status != expectedStatus { 61 | return r, fmt.Errorf("round %d: status %v, expecting %v", round, r.status, expectedStatus) 62 | } 63 | return r, nil 64 | } 65 | 66 | func (srv *DialService) NewRound(Round uint32, _ *struct{}) error { 67 | log.WithFields(log.Fields{"service": "dial", "rpc": "NewRound", "round": Round}).Info() 68 | srv.Idle.Lock() 69 | 70 | srv.roundsMu.Lock() 71 | defer srv.roundsMu.Unlock() 72 | 73 | _, exists := srv.rounds[Round] 74 | if exists { 75 | return fmt.Errorf("round %d already exists", Round) 76 | } 77 | 78 | round := &DialRound{} 79 | srv.rounds[Round] = round 80 | 81 | round.noiseWg.Add(1) 82 | go func() { 83 | // NOTE: unlike the convo protocol, the last server also adds noise 84 | noiseTotal := uint32(0) 85 | noiseCounts := make([]uint32, TotalDialBuckets+1) 86 | for b := range noiseCounts { 87 | bmu := srv.Laplace.Uint32() 88 | noiseCounts[b] = bmu 89 | noiseTotal += bmu 90 | } 91 | round.noise = make([][]byte, noiseTotal) 92 | 93 | nonce := ForwardNonce(Round) 94 | nextKeys := srv.PKI.NextServerKeys(srv.ServerName).Keys() 95 | 96 | FillWithFakeIntroductions(round.noise, noiseCounts, nonce, nextKeys) 97 | round.noiseWg.Done() 98 | }() 99 | 100 | round.status = dialRoundOpen 101 | return nil 102 | } 103 | 104 | type DialAddArgs struct { 105 | Round uint32 106 | Onions [][]byte 107 | } 108 | 109 | func (srv *DialService) Add(args *DialAddArgs, _ *struct{}) error { 110 | log.WithFields(log.Fields{"service": "dial", "rpc": "Add", "round": args.Round, "onions": len(args.Onions)}).Debug() 111 | 112 | round, err := srv.getRound(args.Round, dialRoundOpen) 113 | if err != nil { 114 | return err 115 | } 116 | 117 | nonce := ForwardNonce(args.Round) 118 | messages := make([][]byte, 0, len(args.Onions)) 119 | expectedOnionSize := srv.PKI.IncomingOnionOverhead(srv.ServerName) + SizeDialExchange 120 | 121 | for _, onion := range args.Onions { 122 | if len(onion) == expectedOnionSize { 123 | var theirPublic [32]byte 124 | copy(theirPublic[:], onion[0:32]) 125 | 126 | message, ok := box.Open(nil, onion[32:], nonce, &theirPublic, srv.PrivateKey.Key()) 127 | if ok { 128 | messages = append(messages, message) 129 | } 130 | } 131 | } 132 | 133 | round.Lock() 134 | round.incoming = append(round.incoming, messages...) 135 | round.Unlock() 136 | 137 | return nil 138 | } 139 | 140 | func (srv *DialService) filterIncoming(round *DialRound) { 141 | incomingValid := make([][]byte, 0, len(round.incoming)) 142 | 143 | seen := make(map[uint64]bool) 144 | for _, msg := range round.incoming { 145 | msgkey := binary.BigEndian.Uint64(msg[len(msg)-8:]) 146 | if !seen[msgkey] { 147 | seen[msgkey] = true 148 | incomingValid = append(incomingValid, msg) 149 | } 150 | } 151 | 152 | round.incoming = incomingValid 153 | } 154 | 155 | func (srv *DialService) Close(Round uint32, _ *struct{}) error { 156 | log.WithFields(log.Fields{"service": "dial", "rpc": "Close", "round": Round}).Info() 157 | 158 | round, err := srv.getRound(Round, dialRoundOpen) 159 | if err != nil { 160 | return err 161 | } 162 | 163 | srv.filterIncoming(round) 164 | 165 | round.noiseWg.Wait() 166 | round.incoming = append(round.incoming, round.noise...) 167 | 168 | shuffler := shuffle.New(rand.Reader, len(round.incoming)) 169 | shuffler.Shuffle(round.incoming) 170 | 171 | if !srv.LastServer { 172 | if err := NewDialRound(srv.Client, Round); err != nil { 173 | return fmt.Errorf("NewDialRound: %s", err) 174 | } 175 | srv.Idle.Unlock() 176 | 177 | if err := RunDialRound(srv.Client, Round, round.incoming); err != nil { 178 | return fmt.Errorf("RunDialRound: %s", err) 179 | } 180 | round.incoming = nil 181 | } else { 182 | srv.Idle.Unlock() 183 | } 184 | round.noise = nil 185 | 186 | round.status = dialRoundClosed 187 | return nil 188 | } 189 | 190 | type DialBucketsArgs struct { 191 | Round uint32 192 | } 193 | 194 | type DialBucketsResult struct { 195 | Buckets [][][SizeEncryptedIntro]byte 196 | } 197 | 198 | func (srv *DialService) Buckets(args *DialBucketsArgs, result *DialBucketsResult) error { 199 | log.WithFields(log.Fields{"service": "dial", "rpc": "Buckets", "round": args.Round}).Info() 200 | 201 | if !srv.LastServer { 202 | return fmt.Errorf("Dial.Buckets can only be called on the last server") 203 | } 204 | 205 | round, err := srv.getRound(args.Round, dialRoundClosed) 206 | if err != nil { 207 | return err 208 | } 209 | 210 | buckets := make([][][SizeEncryptedIntro]byte, TotalDialBuckets) 211 | 212 | ex := new(DialExchange) 213 | for _, m := range round.incoming { 214 | if len(m) != SizeDialExchange { 215 | continue 216 | } 217 | if err := ex.Unmarshal(m); err != nil { 218 | continue 219 | } 220 | if ex.Bucket == 0 { 221 | continue // dummy dead drop 222 | } 223 | if ex.Bucket-1 >= uint32(len(buckets)) { 224 | continue 225 | } 226 | buckets[ex.Bucket-1] = append(buckets[ex.Bucket-1], ex.EncryptedIntro) 227 | } 228 | 229 | result.Buckets = buckets 230 | return nil 231 | } 232 | 233 | // TODO we should probably have a corresponding Delete rpc 234 | 235 | func NewDialRound(client *vrpc.Client, round uint32) error { 236 | return client.Call("DialService.NewRound", round, nil) 237 | } 238 | 239 | func RunDialRound(client *vrpc.Client, round uint32, onions [][]byte) error { 240 | spans := concurrency.Spans(len(onions), 4000) 241 | calls := make([]*vrpc.Call, len(spans)) 242 | 243 | concurrency.ParallelFor(len(calls), func(p *concurrency.P) { 244 | for i, ok := p.Next(); ok; i, ok = p.Next() { 245 | span := spans[i] 246 | calls[i] = &vrpc.Call{ 247 | Method: "DialService.Add", 248 | Args: &DialAddArgs{ 249 | Round: round, 250 | Onions: onions[span.Start : span.Start+span.Count], 251 | }, 252 | Reply: nil, 253 | } 254 | } 255 | }) 256 | 257 | if err := client.CallMany(calls); err != nil { 258 | return fmt.Errorf("Add: %s", err) 259 | } 260 | 261 | if err := client.Call("DialService.Close", round, nil); err != nil { 262 | return fmt.Errorf("Close: %s", err) 263 | } 264 | 265 | return nil 266 | } 267 | -------------------------------------------------------------------------------- /vuvuzela-entry-server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "net/http" 7 | "runtime" 8 | "sync" 9 | "time" 10 | 11 | log "github.com/Sirupsen/logrus" 12 | "github.com/gorilla/websocket" 13 | 14 | "vuvuzela.io/concurrency" 15 | . "vuvuzela.io/vuvuzela" 16 | . "vuvuzela.io/vuvuzela/internal" 17 | "vuvuzela.io/vuvuzela/vrpc" 18 | ) 19 | 20 | type server struct { 21 | connectionsMu sync.Mutex 22 | connections map[*connection]bool 23 | 24 | convoMu sync.Mutex 25 | convoRound uint32 26 | convoRequests []*convoReq 27 | 28 | dialMu sync.Mutex 29 | dialRound uint32 30 | dialRequests []*dialReq 31 | 32 | firstServer *vrpc.Client 33 | lastServer *vrpc.Client 34 | } 35 | 36 | type convoReq struct { 37 | conn *connection 38 | onion []byte 39 | } 40 | 41 | type dialReq struct { 42 | conn *connection 43 | onion []byte 44 | } 45 | 46 | type connection struct { 47 | sync.Mutex 48 | 49 | ws *websocket.Conn 50 | srv *server 51 | publicKey *BoxKey 52 | } 53 | 54 | func (srv *server) register(c *connection) { 55 | srv.connectionsMu.Lock() 56 | srv.connections[c] = true 57 | srv.connectionsMu.Unlock() 58 | } 59 | 60 | func (srv *server) allConnections() []*connection { 61 | srv.connectionsMu.Lock() 62 | conns := make([]*connection, len(srv.connections)) 63 | i := 0 64 | for c := range srv.connections { 65 | conns[i] = c 66 | i++ 67 | } 68 | srv.connectionsMu.Unlock() 69 | return conns 70 | } 71 | 72 | func broadcast(conns []*connection, v interface{}) { 73 | concurrency.ParallelFor(len(conns), func(p *concurrency.P) { 74 | for i, ok := p.Next(); ok; i, ok = p.Next() { 75 | conns[i].Send(v) 76 | } 77 | }) 78 | } 79 | 80 | func (c *connection) Close() { 81 | c.ws.Close() 82 | 83 | c.srv.connectionsMu.Lock() 84 | delete(c.srv.connections, c) 85 | c.srv.connectionsMu.Unlock() 86 | } 87 | 88 | func (c *connection) Send(v interface{}) { 89 | const writeWait = 10 * time.Second 90 | 91 | e, err := Envelop(v) 92 | if err != nil { 93 | log.WithFields(log.Fields{"bug": true, "call": "Envelop"}).Error(err) 94 | return 95 | } 96 | 97 | c.Lock() 98 | c.ws.SetWriteDeadline(time.Now().Add(writeWait)) 99 | if err := c.ws.WriteJSON(e); err != nil { 100 | log.WithFields(log.Fields{"call": "WriteJSON"}).Debug(err) 101 | c.Unlock() 102 | c.Close() 103 | return 104 | } 105 | c.Unlock() 106 | } 107 | 108 | func (c *connection) readLoop() { 109 | for { 110 | var e Envelope 111 | if err := c.ws.ReadJSON(&e); err != nil { 112 | log.WithFields(log.Fields{"call": "ReadJSON"}).Debug(err) 113 | c.Close() 114 | break 115 | } 116 | 117 | v, err := e.Open() 118 | if err != nil { 119 | msg := fmt.Sprintf("error parsing request: %s", err) 120 | go c.Send(&BadRequestError{Err: msg}) 121 | } 122 | go c.handleRequest(v) 123 | } 124 | } 125 | 126 | func (c *connection) handleRequest(v interface{}) { 127 | switch v := v.(type) { 128 | case *ConvoRequest: 129 | c.handleConvoRequest(v) 130 | case *DialRequest: 131 | c.handleDialRequest(v) 132 | } 133 | } 134 | 135 | func (c *connection) handleConvoRequest(r *ConvoRequest) { 136 | srv := c.srv 137 | srv.convoMu.Lock() 138 | currRound := srv.convoRound 139 | if r.Round != currRound { 140 | srv.convoMu.Unlock() 141 | err := fmt.Sprintf("wrong round (currently %d)", currRound) 142 | go c.Send(&ConvoError{Round: r.Round, Err: err}) 143 | return 144 | } 145 | rr := &convoReq{ 146 | conn: c, 147 | onion: r.Onion, 148 | } 149 | srv.convoRequests = append(srv.convoRequests, rr) 150 | srv.convoMu.Unlock() 151 | } 152 | 153 | func (c *connection) handleDialRequest(r *DialRequest) { 154 | srv := c.srv 155 | srv.dialMu.Lock() 156 | currRound := srv.dialRound 157 | if r.Round != currRound { 158 | srv.dialMu.Unlock() 159 | err := fmt.Sprintf("wrong round (currently %d)", currRound) 160 | go c.Send(&DialError{Round: r.Round, Err: err}) 161 | return 162 | } 163 | rr := &dialReq{ 164 | conn: c, 165 | onion: r.Onion, 166 | } 167 | srv.dialRequests = append(srv.dialRequests, rr) 168 | srv.dialMu.Unlock() 169 | } 170 | 171 | func (srv *server) convoRoundLoop() { 172 | for { 173 | if err := NewConvoRound(srv.firstServer, srv.convoRound); err != nil { 174 | log.WithFields(log.Fields{"service": "convo", "round": srv.convoRound, "call": "NewConvoRound"}).Error(err) 175 | time.Sleep(10 * time.Second) 176 | continue 177 | } 178 | log.WithFields(log.Fields{"service": "convo", "round": srv.convoRound}).Info("Broadcast") 179 | 180 | broadcast(srv.allConnections(), &AnnounceConvoRound{srv.convoRound}) 181 | time.Sleep(*receiveWait) 182 | 183 | srv.convoMu.Lock() 184 | go srv.runConvoRound(srv.convoRound, srv.convoRequests) 185 | 186 | srv.convoRound += 1 187 | srv.convoRequests = make([]*convoReq, 0, len(srv.convoRequests)) 188 | srv.convoMu.Unlock() 189 | } 190 | } 191 | 192 | func (srv *server) dialRoundLoop() { 193 | for { 194 | time.Sleep(DialWait) 195 | if err := NewDialRound(srv.firstServer, srv.dialRound); err != nil { 196 | log.WithFields(log.Fields{"service": "dial", "round": srv.dialRound, "call": "NewDialRound"}).Error(err) 197 | time.Sleep(10 * time.Second) 198 | continue 199 | } 200 | log.WithFields(log.Fields{"service": "dial", "round": srv.dialRound}).Info("Broadcast") 201 | 202 | broadcast(srv.allConnections(), &AnnounceDialRound{srv.dialRound, TotalDialBuckets}) 203 | time.Sleep(*receiveWait) 204 | 205 | srv.dialMu.Lock() 206 | go srv.runDialRound(srv.dialRound, srv.dialRequests) 207 | 208 | srv.dialRound += 1 209 | srv.dialRequests = make([]*dialReq, 0, len(srv.dialRequests)) 210 | srv.dialMu.Unlock() 211 | } 212 | } 213 | 214 | func (srv *server) runConvoRound(round uint32, requests []*convoReq) { 215 | conns := make([]*connection, len(requests)) 216 | onions := make([][]byte, len(requests)) 217 | for i, r := range requests { 218 | conns[i] = r.conn 219 | onions[i] = r.onion 220 | } 221 | 222 | rlog := log.WithFields(log.Fields{"service": "convo", "round": round}) 223 | rlog.WithFields(log.Fields{"call": "RunConvoRound", "onions": len(onions)}).Info() 224 | 225 | replies, err := RunConvoRound(srv.firstServer, round, onions) 226 | if err != nil { 227 | rlog.WithFields(log.Fields{"call": "RunConvoRound"}).Error(err) 228 | broadcast(conns, &ConvoError{Round: round, Err: "server error"}) 229 | return 230 | } 231 | 232 | rlog.WithFields(log.Fields{"replies": len(replies)}).Info("Success") 233 | 234 | concurrency.ParallelFor(len(replies), func(p *concurrency.P) { 235 | for i, ok := p.Next(); ok; i, ok = p.Next() { 236 | reply := &ConvoResponse{ 237 | Round: round, 238 | Onion: replies[i], 239 | } 240 | conns[i].Send(reply) 241 | } 242 | }) 243 | } 244 | 245 | func (srv *server) runDialRound(round uint32, requests []*dialReq) { 246 | conns := make([]*connection, len(requests)) 247 | onions := make([][]byte, len(requests)) 248 | for i, r := range requests { 249 | conns[i] = r.conn 250 | onions[i] = r.onion 251 | } 252 | 253 | rlog := log.WithFields(log.Fields{"service": "dial", "round": round}) 254 | rlog.WithFields(log.Fields{"call": "RunDialRound", "onions": len(onions)}).Info() 255 | 256 | if err := RunDialRound(srv.firstServer, round, onions); err != nil { 257 | rlog.WithFields(log.Fields{"call": "RunDialRound"}).Error(err) 258 | broadcast(conns, &DialError{Round: round, Err: "server error"}) 259 | return 260 | } 261 | 262 | args := &DialBucketsArgs{Round: round} 263 | result := new(DialBucketsResult) 264 | if err := srv.lastServer.Call("DialService.Buckets", args, result); err != nil { 265 | rlog.WithFields(log.Fields{"call": "Buckets"}).Error(err) 266 | broadcast(conns, &DialError{Round: round, Err: "server error"}) 267 | return 268 | } 269 | 270 | intros := 0 271 | for _, b := range result.Buckets { 272 | intros += len(b) 273 | } 274 | rlog.WithFields(log.Fields{"buckets": len(result.Buckets), "intros": intros}).Info("Buckets") 275 | 276 | concurrency.ParallelFor(len(conns), func(p *concurrency.P) { 277 | for i, ok := p.Next(); ok; i, ok = p.Next() { 278 | c := conns[i] 279 | bi := KeyDialBucket(c.publicKey, TotalDialBuckets) 280 | 281 | db := &DialBucket{ 282 | Round: round, 283 | Intros: result.Buckets[bi-1], 284 | } 285 | c.Send(db) 286 | } 287 | }) 288 | } 289 | 290 | var upgrader = websocket.Upgrader{ 291 | ReadBufferSize: 1024, 292 | WriteBufferSize: 1024, 293 | } 294 | 295 | func (srv *server) wsHandler(w http.ResponseWriter, r *http.Request) { 296 | if r.Method != "GET" { 297 | http.Error(w, "Method not allowed", 405) 298 | return 299 | } 300 | 301 | pk, err := KeyFromString(r.URL.Query().Get("publickey")) 302 | if err != nil { 303 | http.Error(w, "expecting box key in publickey query parameter", http.StatusBadRequest) 304 | return 305 | } 306 | 307 | ws, err := upgrader.Upgrade(w, r, nil) 308 | if err != nil { 309 | log.Printf("Upgrade: %s", err) 310 | return 311 | } 312 | 313 | c := &connection{ 314 | ws: ws, 315 | srv: srv, 316 | publicKey: pk, 317 | } 318 | srv.register(c) 319 | c.readLoop() 320 | } 321 | 322 | var addr = flag.String("addr", ":8080", "http service address") 323 | var pkiPath = flag.String("pki", "confs/pki.conf", "pki file") 324 | var receiveWait = flag.Duration("wait", DefaultReceiveWait, "") 325 | 326 | func main() { 327 | flag.Parse() 328 | log.SetFormatter(&ServerFormatter{}) 329 | 330 | pki := ReadPKI(*pkiPath) 331 | 332 | firstServer, err := vrpc.Dial("tcp", pki.FirstServer(), runtime.NumCPU()) 333 | if err != nil { 334 | log.Fatalf("vrpc.Dial: %s", err) 335 | } 336 | 337 | lastServer, err := vrpc.Dial("tcp", pki.LastServer(), 1) 338 | if err != nil { 339 | log.Fatalf("vrpc.Dial: %s", err) 340 | } 341 | 342 | srv := &server{ 343 | firstServer: firstServer, 344 | lastServer: lastServer, 345 | connections: make(map[*connection]bool), 346 | convoRound: 0, 347 | convoRequests: make([]*convoReq, 0, 10000), 348 | dialRound: 0, 349 | dialRequests: make([]*dialReq, 0, 10000), 350 | } 351 | 352 | go srv.convoRoundLoop() 353 | go srv.dialRoundLoop() 354 | 355 | http.HandleFunc("/ws", srv.wsHandler) 356 | 357 | httpServer := &http.Server{ 358 | Addr: *addr, 359 | } 360 | 361 | if err := httpServer.ListenAndServe(); err != nil { 362 | log.Fatal("ListenAndServe: ", err) 363 | } 364 | } 365 | -------------------------------------------------------------------------------- /convo.go: -------------------------------------------------------------------------------- 1 | package vuvuzela 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "sync" 7 | 8 | log "github.com/Sirupsen/logrus" 9 | "golang.org/x/crypto/nacl/box" 10 | 11 | "vuvuzela.io/concurrency" 12 | "vuvuzela.io/crypto/rand" 13 | "vuvuzela.io/crypto/shuffle" 14 | "vuvuzela.io/vuvuzela/vrpc" 15 | ) 16 | 17 | type ConvoService struct { 18 | roundsMu sync.RWMutex 19 | rounds map[uint32]*ConvoRound 20 | 21 | Idle *sync.Mutex 22 | 23 | Laplace rand.Laplace 24 | 25 | PKI *PKI 26 | ServerName string 27 | PrivateKey *BoxKey 28 | Client *vrpc.Client 29 | LastServer bool 30 | 31 | AccessCounts chan *AccessCount 32 | } 33 | 34 | type ConvoRound struct { 35 | srv *ConvoService 36 | status convoStatus 37 | 38 | numIncoming int 39 | sharedKeys []*[32]byte 40 | incoming [][]byte 41 | incomingIndex []int 42 | 43 | replies [][]byte 44 | 45 | numFakeSingles uint32 46 | numFakeDoubles uint32 47 | 48 | noise [][]byte 49 | noiseWg sync.WaitGroup 50 | } 51 | 52 | type convoStatus int 53 | 54 | const ( 55 | convoRoundNew convoStatus = iota + 1 56 | convoRoundOpen 57 | convoRoundClosed 58 | ) 59 | 60 | type AccessCount struct { 61 | Singles int64 62 | Doubles int64 63 | } 64 | 65 | func InitConvoService(srv *ConvoService) { 66 | srv.rounds = make(map[uint32]*ConvoRound) 67 | srv.AccessCounts = make(chan *AccessCount, 8) 68 | } 69 | 70 | func (srv *ConvoService) getRound(round uint32, expectedStatus convoStatus) (*ConvoRound, error) { 71 | srv.roundsMu.RLock() 72 | r, ok := srv.rounds[round] 73 | srv.roundsMu.RUnlock() 74 | if !ok { 75 | return nil, fmt.Errorf("round %d not found", round) 76 | } 77 | if r.status != expectedStatus { 78 | return r, fmt.Errorf("round %d: status %v, expecting %v", round, r.status, expectedStatus) 79 | } 80 | return r, nil 81 | } 82 | 83 | func (srv *ConvoService) NewRound(Round uint32, _ *struct{}) error { 84 | log.WithFields(log.Fields{"service": "convo", "rpc": "NewRound", "round": Round}).Info() 85 | 86 | // wait for the service to become idle before starting a new round 87 | // TODO temporary hack 88 | srv.Idle.Lock() 89 | 90 | srv.roundsMu.Lock() 91 | defer srv.roundsMu.Unlock() 92 | 93 | _, exists := srv.rounds[Round] 94 | if exists { 95 | return fmt.Errorf("round %d already exists", Round) 96 | } 97 | 98 | round := &ConvoRound{ 99 | srv: srv, 100 | } 101 | srv.rounds[Round] = round 102 | 103 | if !srv.LastServer { 104 | round.numFakeSingles = srv.Laplace.Uint32() 105 | round.numFakeDoubles = srv.Laplace.Uint32() 106 | round.numFakeDoubles += round.numFakeDoubles % 2 // ensure numFakeDoubles is even 107 | round.noise = make([][]byte, round.numFakeSingles+round.numFakeDoubles) 108 | 109 | nonce := ForwardNonce(Round) 110 | nextKeys := srv.PKI.NextServerKeys(srv.ServerName).Keys() 111 | round.noiseWg.Add(1) 112 | go func() { 113 | FillWithFakeSingles(round.noise[:round.numFakeSingles], nonce, nextKeys) 114 | FillWithFakeDoubles(round.noise[round.numFakeSingles:], nonce, nextKeys) 115 | round.noiseWg.Done() 116 | }() 117 | } 118 | 119 | round.status = convoRoundNew 120 | return nil 121 | } 122 | 123 | type ConvoOpenArgs struct { 124 | Round uint32 125 | NumIncoming int 126 | } 127 | 128 | func (srv *ConvoService) Open(args *ConvoOpenArgs, _ *struct{}) error { 129 | log.WithFields(log.Fields{"service": "convo", "rpc": "Open", "round": args.Round, "incoming": args.NumIncoming}).Info() 130 | 131 | round, err := srv.getRound(args.Round, convoRoundNew) 132 | if err != nil { 133 | return err 134 | } 135 | 136 | round.numIncoming = args.NumIncoming 137 | round.sharedKeys = make([]*[32]byte, round.numIncoming) 138 | round.incoming = make([][]byte, round.numIncoming) 139 | round.status = convoRoundOpen 140 | 141 | return nil 142 | } 143 | 144 | type ConvoAddArgs struct { 145 | Round uint32 146 | Offset int 147 | Onions [][]byte 148 | } 149 | 150 | func (srv *ConvoService) Add(args *ConvoAddArgs, _ *struct{}) error { 151 | log.WithFields(log.Fields{"service": "convo", "rpc": "Add", "round": args.Round, "onions": len(args.Onions)}).Debug() 152 | 153 | round, err := srv.getRound(args.Round, convoRoundOpen) 154 | if err != nil { 155 | return err 156 | } 157 | 158 | nonce := ForwardNonce(args.Round) 159 | expectedOnionSize := srv.PKI.IncomingOnionOverhead(srv.ServerName) + SizeConvoExchange 160 | 161 | if args.Offset+len(args.Onions) > round.numIncoming { 162 | return fmt.Errorf("overflowing onions (offset=%d, onions=%d, incoming=%d)", args.Offset, len(args.Onions), round.numIncoming) 163 | } 164 | 165 | for k, onion := range args.Onions { 166 | i := args.Offset + k 167 | round.sharedKeys[i] = new([32]byte) 168 | 169 | if len(onion) == expectedOnionSize { 170 | var theirPublic [32]byte 171 | copy(theirPublic[:], onion[0:32]) 172 | 173 | box.Precompute(round.sharedKeys[i], &theirPublic, srv.PrivateKey.Key()) 174 | 175 | message, ok := box.OpenAfterPrecomputation(nil, onion[32:], nonce, round.sharedKeys[i]) 176 | if ok { 177 | round.incoming[i] = message 178 | } 179 | } else { 180 | // for debugging 181 | log.WithFields(log.Fields{"round": args.Round, "offset": args.Offset, "onions": len(args.Onions), "onion": k, "onionLen": len(onion)}).Error("bad onion size") 182 | } 183 | } 184 | 185 | return nil 186 | } 187 | 188 | func (srv *ConvoService) filterIncoming(round *ConvoRound) { 189 | incomingValid := make([][]byte, len(round.incoming)) 190 | incomingIndex := make([]int, len(round.incoming)) 191 | 192 | seen := make(map[uint64]bool) 193 | v := 0 194 | for i, msg := range round.incoming { 195 | if msg == nil { 196 | incomingIndex[i] = -1 197 | continue 198 | } 199 | msgkey := binary.BigEndian.Uint64(msg[len(msg)-8:]) 200 | if seen[msgkey] { 201 | incomingIndex[i] = -1 202 | } else { 203 | seen[msgkey] = true 204 | incomingValid[v] = msg 205 | incomingIndex[i] = v 206 | v++ 207 | } 208 | } 209 | 210 | round.incoming = incomingValid[:v] 211 | round.incomingIndex = incomingIndex 212 | } 213 | 214 | func (srv *ConvoService) Close(Round uint32, _ *struct{}) error { 215 | log.WithFields(log.Fields{"service": "convo", "rpc": "Close", "round": Round}).Info() 216 | 217 | round, err := srv.getRound(Round, convoRoundOpen) 218 | if err != nil { 219 | return err 220 | } 221 | 222 | srv.filterIncoming(round) 223 | 224 | if !srv.LastServer { 225 | round.noiseWg.Wait() 226 | 227 | outgoing := append(round.incoming, round.noise...) 228 | round.noise = nil 229 | 230 | shuffler := shuffle.New(rand.Reader, len(outgoing)) 231 | shuffler.Shuffle(outgoing) 232 | 233 | if err := NewConvoRound(srv.Client, Round); err != nil { 234 | return fmt.Errorf("NewConvoRound: %s", err) 235 | } 236 | srv.Idle.Unlock() 237 | 238 | replies, err := RunConvoRound(srv.Client, Round, outgoing) 239 | if err != nil { 240 | return fmt.Errorf("RunConvoRound: %s", err) 241 | } 242 | 243 | shuffler.Unshuffle(replies) 244 | round.replies = replies[:round.numIncoming] 245 | } else { 246 | exchanges := make([]*ConvoExchange, len(round.incoming)) 247 | concurrency.ParallelFor(len(round.incoming), func(p *concurrency.P) { 248 | for i, ok := p.Next(); ok; i, ok = p.Next() { 249 | exchanges[i] = new(ConvoExchange) 250 | if err := exchanges[i].Unmarshal(round.incoming[i]); err != nil { 251 | log.WithFields(log.Fields{"bug": true, "call": "ConvoExchange.Unmarshal"}).Error(err) 252 | } 253 | } 254 | }) 255 | 256 | var singles, doubles int64 257 | deadDrops := make(map[DeadDrop][]int) 258 | for i, ex := range exchanges { 259 | drop := deadDrops[ex.DeadDrop] 260 | if len(drop) == 0 { 261 | singles++ 262 | deadDrops[ex.DeadDrop] = append(drop, i) 263 | } else if len(drop) == 1 { 264 | singles-- 265 | doubles++ 266 | deadDrops[ex.DeadDrop] = append(drop, i) 267 | } 268 | } 269 | 270 | round.replies = make([][]byte, len(round.incoming)) 271 | concurrency.ParallelFor(len(exchanges), func(p *concurrency.P) { 272 | for i, ok := p.Next(); ok; i, ok = p.Next() { 273 | ex := exchanges[i] 274 | drop := deadDrops[ex.DeadDrop] 275 | if len(drop) == 1 { 276 | round.replies[i] = ex.EncryptedMessage[:] 277 | } 278 | if len(drop) == 2 { 279 | var k int 280 | if i == drop[0] { 281 | k = drop[1] 282 | } else { 283 | k = drop[0] 284 | } 285 | round.replies[i] = exchanges[k].EncryptedMessage[:] 286 | } 287 | } 288 | }) 289 | srv.Idle.Unlock() 290 | 291 | ac := &AccessCount{ 292 | Singles: singles, 293 | Doubles: doubles, 294 | } 295 | select { 296 | case srv.AccessCounts <- ac: 297 | default: 298 | } 299 | } 300 | 301 | round.status = convoRoundClosed 302 | return nil 303 | } 304 | 305 | type ConvoGetArgs struct { 306 | Round uint32 307 | Offset int 308 | Count int 309 | } 310 | 311 | type ConvoGetResult struct { 312 | Onions [][]byte 313 | } 314 | 315 | func (srv *ConvoService) Get(args *ConvoGetArgs, result *ConvoGetResult) error { 316 | log.WithFields(log.Fields{"service": "convo", "rpc": "Get", "round": args.Round, "count": args.Count}).Debug() 317 | 318 | round, err := srv.getRound(args.Round, convoRoundClosed) 319 | if err != nil { 320 | return err 321 | } 322 | 323 | nonce := BackwardNonce(args.Round) 324 | outgoingOnionSize := srv.PKI.OutgoingOnionOverhead(srv.ServerName) + SizeEncryptedMessage 325 | 326 | result.Onions = make([][]byte, args.Count) 327 | for k := range result.Onions { 328 | i := args.Offset + k 329 | 330 | if v := round.incomingIndex[i]; v > -1 { 331 | reply := round.replies[v] 332 | onion := box.SealAfterPrecomputation(nil, reply, nonce, round.sharedKeys[i]) 333 | result.Onions[k] = onion 334 | } 335 | if len(result.Onions[k]) != outgoingOnionSize { 336 | onion := make([]byte, outgoingOnionSize) 337 | rand.Read(onion) 338 | result.Onions[k] = onion 339 | } 340 | } 341 | 342 | return nil 343 | } 344 | 345 | func (srv *ConvoService) Delete(Round uint32, _ *struct{}) error { 346 | log.WithFields(log.Fields{"service": "convo", "rpc": "Delete", "round": Round}).Info() 347 | 348 | srv.roundsMu.Lock() 349 | delete(srv.rounds, Round) 350 | srv.roundsMu.Unlock() 351 | return nil 352 | } 353 | 354 | func NewConvoRound(client *vrpc.Client, round uint32) error { 355 | return client.Call("ConvoService.NewRound", round, nil) 356 | } 357 | 358 | func RunConvoRound(client *vrpc.Client, round uint32, onions [][]byte) ([][]byte, error) { 359 | openArgs := &ConvoOpenArgs{ 360 | Round: round, 361 | NumIncoming: len(onions), 362 | } 363 | if err := client.Call("ConvoService.Open", openArgs, nil); err != nil { 364 | return nil, fmt.Errorf("Open: %s", err) 365 | } 366 | 367 | spans := concurrency.Spans(len(onions), 4000) 368 | calls := make([]*vrpc.Call, len(spans)) 369 | 370 | concurrency.ParallelFor(len(calls), func(p *concurrency.P) { 371 | for i, ok := p.Next(); ok; i, ok = p.Next() { 372 | span := spans[i] 373 | calls[i] = &vrpc.Call{ 374 | Method: "ConvoService.Add", 375 | Args: &ConvoAddArgs{ 376 | Round: round, 377 | Offset: span.Start, 378 | Onions: onions[span.Start : span.Start+span.Count], 379 | }, 380 | Reply: nil, 381 | } 382 | } 383 | }) 384 | 385 | if err := client.CallMany(calls); err != nil { 386 | return nil, fmt.Errorf("Add: %s", err) 387 | } 388 | 389 | if err := client.Call("ConvoService.Close", round, nil); err != nil { 390 | return nil, fmt.Errorf("Close: %s", err) 391 | } 392 | 393 | concurrency.ParallelFor(len(calls), func(p *concurrency.P) { 394 | for i, ok := p.Next(); ok; i, ok = p.Next() { 395 | span := spans[i] 396 | calls[i] = &vrpc.Call{ 397 | Method: "ConvoService.Get", 398 | Args: &ConvoGetArgs{ 399 | Round: round, 400 | Offset: span.Start, 401 | Count: span.Count, 402 | }, 403 | Reply: new(ConvoGetResult), 404 | } 405 | } 406 | }) 407 | 408 | if err := client.CallMany(calls); err != nil { 409 | return nil, fmt.Errorf("Get: %s", err) 410 | } 411 | 412 | replies := make([][]byte, len(onions)) 413 | concurrency.ParallelFor(len(calls), func(p *concurrency.P) { 414 | for i, ok := p.Next(); ok; i, ok = p.Next() { 415 | span := spans[i] 416 | copy(replies[span.Start:span.Start+span.Count], calls[i].Reply.(*ConvoGetResult).Onions) 417 | } 418 | }) 419 | 420 | if err := client.Call("ConvoService.Delete", round, nil); err != nil { 421 | return nil, fmt.Errorf("Delete: %s", err) 422 | } 423 | 424 | return replies, nil 425 | } 426 | -------------------------------------------------------------------------------- /agpl-3.0.txt: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU Affero General Public License is a free, copyleft license for 11 | software and other kinds of works, specifically designed to ensure 12 | cooperation with the community in the case of network server software. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | our General Public Licenses are intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Our General Public Licenses are designed to make sure that you 22 | have the freedom to distribute copies of free software (and charge for 23 | them if you wish), that you receive source code or can get it if you 24 | want it, that you can change the software or use pieces of it in new 25 | free programs, and that you know you can do these things. 26 | 27 | Developers that use our General Public Licenses protect your rights 28 | with two steps: (1) assert copyright on the software, and (2) offer 29 | you this License which gives you legal permission to copy, distribute 30 | and/or modify the software. 31 | 32 | A secondary benefit of defending all users' freedom is that 33 | improvements made in alternate versions of the program, if they 34 | receive widespread use, become available for other developers to 35 | incorporate. Many developers of free software are heartened and 36 | encouraged by the resulting cooperation. However, in the case of 37 | software used on network servers, this result may fail to come about. 38 | The GNU General Public License permits making a modified version and 39 | letting the public access it on a server without ever releasing its 40 | source code to the public. 41 | 42 | The GNU Affero General Public License is designed specifically to 43 | ensure that, in such cases, the modified source code becomes available 44 | to the community. It requires the operator of a network server to 45 | provide the source code of the modified version running there to the 46 | users of that server. Therefore, public use of a modified version, on 47 | a publicly accessible server, gives the public access to the source 48 | code of the modified version. 49 | 50 | An older license, called the Affero General Public License and 51 | published by Affero, was designed to accomplish similar goals. This is 52 | a different license, not a version of the Affero GPL, but Affero has 53 | released a new version of the Affero GPL which permits relicensing under 54 | this license. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | TERMS AND CONDITIONS 60 | 61 | 0. Definitions. 62 | 63 | "This License" refers to version 3 of the GNU Affero General Public License. 64 | 65 | "Copyright" also means copyright-like laws that apply to other kinds of 66 | works, such as semiconductor masks. 67 | 68 | "The Program" refers to any copyrightable work licensed under this 69 | License. Each licensee is addressed as "you". "Licensees" and 70 | "recipients" may be individuals or organizations. 71 | 72 | To "modify" a work means to copy from or adapt all or part of the work 73 | in a fashion requiring copyright permission, other than the making of an 74 | exact copy. The resulting work is called a "modified version" of the 75 | earlier work or a work "based on" the earlier work. 76 | 77 | A "covered work" means either the unmodified Program or a work based 78 | on the Program. 79 | 80 | To "propagate" a work means to do anything with it that, without 81 | permission, would make you directly or secondarily liable for 82 | infringement under applicable copyright law, except executing it on a 83 | computer or modifying a private copy. Propagation includes copying, 84 | distribution (with or without modification), making available to the 85 | public, and in some countries other activities as well. 86 | 87 | To "convey" a work means any kind of propagation that enables other 88 | parties to make or receive copies. Mere interaction with a user through 89 | a computer network, with no transfer of a copy, is not conveying. 90 | 91 | An interactive user interface displays "Appropriate Legal Notices" 92 | to the extent that it includes a convenient and prominently visible 93 | feature that (1) displays an appropriate copyright notice, and (2) 94 | tells the user that there is no warranty for the work (except to the 95 | extent that warranties are provided), that licensees may convey the 96 | work under this License, and how to view a copy of this License. If 97 | the interface presents a list of user commands or options, such as a 98 | menu, a prominent item in the list meets this criterion. 99 | 100 | 1. Source Code. 101 | 102 | The "source code" for a work means the preferred form of the work 103 | for making modifications to it. "Object code" means any non-source 104 | form of a work. 105 | 106 | A "Standard Interface" means an interface that either is an official 107 | standard defined by a recognized standards body, or, in the case of 108 | interfaces specified for a particular programming language, one that 109 | is widely used among developers working in that language. 110 | 111 | The "System Libraries" of an executable work include anything, other 112 | than the work as a whole, that (a) is included in the normal form of 113 | packaging a Major Component, but which is not part of that Major 114 | Component, and (b) serves only to enable use of the work with that 115 | Major Component, or to implement a Standard Interface for which an 116 | implementation is available to the public in source code form. A 117 | "Major Component", in this context, means a major essential component 118 | (kernel, window system, and so on) of the specific operating system 119 | (if any) on which the executable work runs, or a compiler used to 120 | produce the work, or an object code interpreter used to run it. 121 | 122 | The "Corresponding Source" for a work in object code form means all 123 | the source code needed to generate, install, and (for an executable 124 | work) run the object code and to modify the work, including scripts to 125 | control those activities. However, it does not include the work's 126 | System Libraries, or general-purpose tools or generally available free 127 | programs which are used unmodified in performing those activities but 128 | which are not part of the work. For example, Corresponding Source 129 | includes interface definition files associated with source files for 130 | the work, and the source code for shared libraries and dynamically 131 | linked subprograms that the work is specifically designed to require, 132 | such as by intimate data communication or control flow between those 133 | subprograms and other parts of the work. 134 | 135 | The Corresponding Source need not include anything that users 136 | can regenerate automatically from other parts of the Corresponding 137 | Source. 138 | 139 | The Corresponding Source for a work in source code form is that 140 | same work. 141 | 142 | 2. Basic Permissions. 143 | 144 | All rights granted under this License are granted for the term of 145 | copyright on the Program, and are irrevocable provided the stated 146 | conditions are met. This License explicitly affirms your unlimited 147 | permission to run the unmodified Program. The output from running a 148 | covered work is covered by this License only if the output, given its 149 | content, constitutes a covered work. This License acknowledges your 150 | rights of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not 153 | convey, without conditions so long as your license otherwise remains 154 | in force. You may convey covered works to others for the sole purpose 155 | of having them make modifications exclusively for you, or provide you 156 | with facilities for running those works, provided that you comply with 157 | the terms of this License in conveying all material for which you do 158 | not control copyright. Those thus making or running the covered works 159 | for you must do so exclusively on your behalf, under your direction 160 | and control, on terms that prohibit them from making any copies of 161 | your copyrighted material outside their relationship with you. 162 | 163 | Conveying under any other circumstances is permitted solely under 164 | the conditions stated below. Sublicensing is not allowed; section 10 165 | makes it unnecessary. 166 | 167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 | 169 | No covered work shall be deemed part of an effective technological 170 | measure under any applicable law fulfilling obligations under article 171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 | similar laws prohibiting or restricting circumvention of such 173 | measures. 174 | 175 | When you convey a covered work, you waive any legal power to forbid 176 | circumvention of technological measures to the extent such circumvention 177 | is effected by exercising rights under this License with respect to 178 | the covered work, and you disclaim any intention to limit operation or 179 | modification of the work as a means of enforcing, against the work's 180 | users, your or third parties' legal rights to forbid circumvention of 181 | technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you 186 | receive it, in any medium, provided that you conspicuously and 187 | appropriately publish on each copy an appropriate copyright notice; 188 | keep intact all notices stating that this License and any 189 | non-permissive terms added in accord with section 7 apply to the code; 190 | keep intact all notices of the absence of any warranty; and give all 191 | recipients a copy of this License along with the Program. 192 | 193 | You may charge any price or no price for each copy that you convey, 194 | and you may offer support or warranty protection for a fee. 195 | 196 | 5. Conveying Modified Source Versions. 197 | 198 | You may convey a work based on the Program, or the modifications to 199 | produce it from the Program, in the form of source code under the 200 | terms of section 4, provided that you also meet all of these conditions: 201 | 202 | a) The work must carry prominent notices stating that you modified 203 | it, and giving a relevant date. 204 | 205 | b) The work must carry prominent notices stating that it is 206 | released under this License and any conditions added under section 207 | 7. This requirement modifies the requirement in section 4 to 208 | "keep intact all notices". 209 | 210 | c) You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | 218 | d) If the work has interactive user interfaces, each must display 219 | Appropriate Legal Notices; however, if the Program has interactive 220 | interfaces that do not display Appropriate Legal Notices, your 221 | work need not make them do so. 222 | 223 | A compilation of a covered work with other separate and independent 224 | works, which are not by their nature extensions of the covered work, 225 | and which are not combined with it such as to form a larger program, 226 | in or on a volume of a storage or distribution medium, is called an 227 | "aggregate" if the compilation and its resulting copyright are not 228 | used to limit the access or legal rights of the compilation's users 229 | beyond what the individual works permit. Inclusion of a covered work 230 | in an aggregate does not cause this License to apply to the other 231 | parts of the aggregate. 232 | 233 | 6. Conveying Non-Source Forms. 234 | 235 | You may convey a covered work in object code form under the terms 236 | of sections 4 and 5, provided that you also convey the 237 | machine-readable Corresponding Source under the terms of this License, 238 | in one of these ways: 239 | 240 | a) Convey the object code in, or embodied in, a physical product 241 | (including a physical distribution medium), accompanied by the 242 | Corresponding Source fixed on a durable physical medium 243 | customarily used for software interchange. 244 | 245 | b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the 255 | Corresponding Source from a network server at no charge. 256 | 257 | c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | 263 | d) Convey the object code by offering access from a designated 264 | place (gratis or for a charge), and offer equivalent access to the 265 | Corresponding Source in the same way through the same place at no 266 | further charge. You need not require recipients to copy the 267 | Corresponding Source along with the object code. If the place to 268 | copy the object code is a network server, the Corresponding Source 269 | may be on a different server (operated by you or a third party) 270 | that supports equivalent copying facilities, provided you maintain 271 | clear directions next to the object code saying where to find the 272 | Corresponding Source. Regardless of what server hosts the 273 | Corresponding Source, you remain obligated to ensure that it is 274 | available for as long as needed to satisfy these requirements. 275 | 276 | e) Convey the object code using peer-to-peer transmission, provided 277 | you inform other peers where the object code and Corresponding 278 | Source of the work are being offered to the general public at no 279 | charge under subsection 6d. 280 | 281 | A separable portion of the object code, whose source code is excluded 282 | from the Corresponding Source as a System Library, need not be 283 | included in conveying the object code work. 284 | 285 | A "User Product" is either (1) a "consumer product", which means any 286 | tangible personal property which is normally used for personal, family, 287 | or household purposes, or (2) anything designed or sold for incorporation 288 | into a dwelling. In determining whether a product is a consumer product, 289 | doubtful cases shall be resolved in favor of coverage. For a particular 290 | product received by a particular user, "normally used" refers to a 291 | typical or common use of that class of product, regardless of the status 292 | of the particular user or of the way in which the particular user 293 | actually uses, or expects or is expected to use, the product. A product 294 | is a consumer product regardless of whether the product has substantial 295 | commercial, industrial or non-consumer uses, unless such uses represent 296 | the only significant mode of use of the product. 297 | 298 | "Installation Information" for a User Product means any methods, 299 | procedures, authorization keys, or other information required to install 300 | and execute modified versions of a covered work in that User Product from 301 | a modified version of its Corresponding Source. The information must 302 | suffice to ensure that the continued functioning of the modified object 303 | code is in no case prevented or interfered with solely because 304 | modification has been made. 305 | 306 | If you convey an object code work under this section in, or with, or 307 | specifically for use in, a User Product, and the conveying occurs as 308 | part of a transaction in which the right of possession and use of the 309 | User Product is transferred to the recipient in perpetuity or for a 310 | fixed term (regardless of how the transaction is characterized), the 311 | Corresponding Source conveyed under this section must be accompanied 312 | by the Installation Information. But this requirement does not apply 313 | if neither you nor any third party retains the ability to install 314 | modified object code on the User Product (for example, the work has 315 | been installed in ROM). 316 | 317 | The requirement to provide Installation Information does not include a 318 | requirement to continue to provide support service, warranty, or updates 319 | for a work that has been modified or installed by the recipient, or for 320 | the User Product in which it has been modified or installed. Access to a 321 | network may be denied when the modification itself materially and 322 | adversely affects the operation of the network or violates the rules and 323 | protocols for communication across the network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | 7. Additional Terms. 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders of 351 | that material) supplement the terms of this License with terms: 352 | 353 | a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | 356 | b) Requiring preservation of specified reasonable legal notices or 357 | author attributions in that material or in the Appropriate Legal 358 | Notices displayed by works containing it; or 359 | 360 | c) Prohibiting misrepresentation of the origin of that material, or 361 | requiring that modified versions of such material be marked in 362 | reasonable ways as different from the original version; or 363 | 364 | d) Limiting the use for publicity purposes of names of licensors or 365 | authors of the material; or 366 | 367 | e) Declining to grant rights under trademark law for use of some 368 | trade names, trademarks, or service marks; or 369 | 370 | f) Requiring indemnification of licensors and authors of that 371 | material by anyone who conveys the material (or modified versions of 372 | it) with contractual assumptions of liability to the recipient, for 373 | any liability that these contractual assumptions directly impose on 374 | those licensors and authors. 375 | 376 | All other non-permissive additional terms are considered "further 377 | restrictions" within the meaning of section 10. If the Program as you 378 | received it, or any part of it, contains a notice stating that it is 379 | governed by this License along with a term that is a further 380 | restriction, you may remove that term. If a license document contains 381 | a further restriction but permits relicensing or conveying under this 382 | License, you may add to a covered work material governed by the terms 383 | of that license document, provided that the further restriction does 384 | not survive such relicensing or conveying. 385 | 386 | If you add terms to a covered work in accord with this section, you 387 | must place, in the relevant source files, a statement of the 388 | additional terms that apply to those files, or a notice indicating 389 | where to find the applicable terms. 390 | 391 | Additional terms, permissive or non-permissive, may be stated in the 392 | form of a separately written license, or stated as exceptions; 393 | the above requirements apply either way. 394 | 395 | 8. Termination. 396 | 397 | You may not propagate or modify a covered work except as expressly 398 | provided under this License. Any attempt otherwise to propagate or 399 | modify it is void, and will automatically terminate your rights under 400 | this License (including any patent licenses granted under the third 401 | paragraph of section 11). 402 | 403 | However, if you cease all violation of this License, then your 404 | license from a particular copyright holder is reinstated (a) 405 | provisionally, unless and until the copyright holder explicitly and 406 | finally terminates your license, and (b) permanently, if the copyright 407 | holder fails to notify you of the violation by some reasonable means 408 | prior to 60 days after the cessation. 409 | 410 | Moreover, your license from a particular copyright holder is 411 | reinstated permanently if the copyright holder notifies you of the 412 | violation by some reasonable means, this is the first time you have 413 | received notice of violation of this License (for any work) from that 414 | copyright holder, and you cure the violation prior to 30 days after 415 | your receipt of the notice. 416 | 417 | Termination of your rights under this section does not terminate the 418 | licenses of parties who have received copies or rights from you under 419 | this License. If your rights have been terminated and not permanently 420 | reinstated, you do not qualify to receive new licenses for the same 421 | material under section 10. 422 | 423 | 9. Acceptance Not Required for Having Copies. 424 | 425 | You are not required to accept this License in order to receive or 426 | run a copy of the Program. Ancillary propagation of a covered work 427 | occurring solely as a consequence of using peer-to-peer transmission 428 | to receive a copy likewise does not require acceptance. However, 429 | nothing other than this License grants you permission to propagate or 430 | modify any covered work. These actions infringe copyright if you do 431 | not accept this License. Therefore, by modifying or propagating a 432 | covered work, you indicate your acceptance of this License to do so. 433 | 434 | 10. Automatic Licensing of Downstream Recipients. 435 | 436 | Each time you convey a covered work, the recipient automatically 437 | receives a license from the original licensors, to run, modify and 438 | propagate that work, subject to this License. You are not responsible 439 | for enforcing compliance by third parties with this License. 440 | 441 | An "entity transaction" is a transaction transferring control of an 442 | organization, or substantially all assets of one, or subdividing an 443 | organization, or merging organizations. If propagation of a covered 444 | work results from an entity transaction, each party to that 445 | transaction who receives a copy of the work also receives whatever 446 | licenses to the work the party's predecessor in interest had or could 447 | give under the previous paragraph, plus a right to possession of the 448 | Corresponding Source of the work from the predecessor in interest, if 449 | the predecessor has it or can get it with reasonable efforts. 450 | 451 | You may not impose any further restrictions on the exercise of the 452 | rights granted or affirmed under this License. For example, you may 453 | not impose a license fee, royalty, or other charge for exercise of 454 | rights granted under this License, and you may not initiate litigation 455 | (including a cross-claim or counterclaim in a lawsuit) alleging that 456 | any patent claim is infringed by making, using, selling, offering for 457 | sale, or importing the Program or any portion of it. 458 | 459 | 11. Patents. 460 | 461 | A "contributor" is a copyright holder who authorizes use under this 462 | License of the Program or a work on which the Program is based. The 463 | work thus licensed is called the contributor's "contributor version". 464 | 465 | A contributor's "essential patent claims" are all patent claims 466 | owned or controlled by the contributor, whether already acquired or 467 | hereafter acquired, that would be infringed by some manner, permitted 468 | by this License, of making, using, or selling its contributor version, 469 | but do not include claims that would be infringed only as a 470 | consequence of further modification of the contributor version. For 471 | purposes of this definition, "control" includes the right to grant 472 | patent sublicenses in a manner consistent with the requirements of 473 | this License. 474 | 475 | Each contributor grants you a non-exclusive, worldwide, royalty-free 476 | patent license under the contributor's essential patent claims, to 477 | make, use, sell, offer for sale, import and otherwise run, modify and 478 | propagate the contents of its contributor version. 479 | 480 | In the following three paragraphs, a "patent license" is any express 481 | agreement or commitment, however denominated, not to enforce a patent 482 | (such as an express permission to practice a patent or covenant not to 483 | sue for patent infringement). To "grant" such a patent license to a 484 | party means to make such an agreement or commitment not to enforce a 485 | patent against the party. 486 | 487 | If you convey a covered work, knowingly relying on a patent license, 488 | and the Corresponding Source of the work is not available for anyone 489 | to copy, free of charge and under the terms of this License, through a 490 | publicly available network server or other readily accessible means, 491 | then you must either (1) cause the Corresponding Source to be so 492 | available, or (2) arrange to deprive yourself of the benefit of the 493 | patent license for this particular work, or (3) arrange, in a manner 494 | consistent with the requirements of this License, to extend the patent 495 | license to downstream recipients. "Knowingly relying" means you have 496 | actual knowledge that, but for the patent license, your conveying the 497 | covered work in a country, or your recipient's use of the covered work 498 | in a country, would infringe one or more identifiable patents in that 499 | country that you have reason to believe are valid. 500 | 501 | If, pursuant to or in connection with a single transaction or 502 | arrangement, you convey, or propagate by procuring conveyance of, a 503 | covered work, and grant a patent license to some of the parties 504 | receiving the covered work authorizing them to use, propagate, modify 505 | or convey a specific copy of the covered work, then the patent license 506 | you grant is automatically extended to all recipients of the covered 507 | work and works based on it. 508 | 509 | A patent license is "discriminatory" if it does not include within 510 | the scope of its coverage, prohibits the exercise of, or is 511 | conditioned on the non-exercise of one or more of the rights that are 512 | specifically granted under this License. You may not convey a covered 513 | work if you are a party to an arrangement with a third party that is 514 | in the business of distributing software, under which you make payment 515 | to the third party based on the extent of your activity of conveying 516 | the work, and under which the third party grants, to any of the 517 | parties who would receive the covered work from you, a discriminatory 518 | patent license (a) in connection with copies of the covered work 519 | conveyed by you (or copies made from those copies), or (b) primarily 520 | for and in connection with specific products or compilations that 521 | contain the covered work, unless you entered into that arrangement, 522 | or that patent license was granted, prior to 28 March 2007. 523 | 524 | Nothing in this License shall be construed as excluding or limiting 525 | any implied license or other defenses to infringement that may 526 | otherwise be available to you under applicable patent law. 527 | 528 | 12. No Surrender of Others' Freedom. 529 | 530 | If conditions are imposed on you (whether by court order, agreement or 531 | otherwise) that contradict the conditions of this License, they do not 532 | excuse you from the conditions of this License. If you cannot convey a 533 | covered work so as to satisfy simultaneously your obligations under this 534 | License and any other pertinent obligations, then as a consequence you may 535 | not convey it at all. For example, if you agree to terms that obligate you 536 | to collect a royalty for further conveying from those to whom you convey 537 | the Program, the only way you could satisfy both those terms and this 538 | License would be to refrain entirely from conveying the Program. 539 | 540 | 13. Remote Network Interaction; Use with the GNU General Public License. 541 | 542 | Notwithstanding any other provision of this License, if you modify the 543 | Program, your modified version must prominently offer all users 544 | interacting with it remotely through a computer network (if your version 545 | supports such interaction) an opportunity to receive the Corresponding 546 | Source of your version by providing access to the Corresponding Source 547 | from a network server at no charge, through some standard or customary 548 | means of facilitating copying of software. This Corresponding Source 549 | shall include the Corresponding Source for any work covered by version 3 550 | of the GNU General Public License that is incorporated pursuant to the 551 | following paragraph. 552 | 553 | Notwithstanding any other provision of this License, you have 554 | permission to link or combine any covered work with a work licensed 555 | under version 3 of the GNU General Public License into a single 556 | combined work, and to convey the resulting work. The terms of this 557 | License will continue to apply to the part which is the covered work, 558 | but the work with which it is combined will remain governed by version 559 | 3 of the GNU General Public License. 560 | 561 | 14. Revised Versions of this License. 562 | 563 | The Free Software Foundation may publish revised and/or new versions of 564 | the GNU Affero General Public License from time to time. Such new versions 565 | will be similar in spirit to the present version, but may differ in detail to 566 | address new problems or concerns. 567 | 568 | Each version is given a distinguishing version number. If the 569 | Program specifies that a certain numbered version of the GNU Affero General 570 | Public License "or any later version" applies to it, you have the 571 | option of following the terms and conditions either of that numbered 572 | version or of any later version published by the Free Software 573 | Foundation. If the Program does not specify a version number of the 574 | GNU Affero General Public License, you may choose any version ever published 575 | by the Free Software Foundation. 576 | 577 | If the Program specifies that a proxy can decide which future 578 | versions of the GNU Affero General Public License can be used, that proxy's 579 | public statement of acceptance of a version permanently authorizes you 580 | to choose that version for the Program. 581 | 582 | Later license versions may give you additional or different 583 | permissions. However, no additional obligations are imposed on any 584 | author or copyright holder as a result of your choosing to follow a 585 | later version. 586 | 587 | 15. Disclaimer of Warranty. 588 | 589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 | 598 | 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 | SUCH DAMAGES. 609 | 610 | 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS 620 | 621 | How to Apply These Terms to Your New Programs 622 | 623 | If you develop a new program, and you want it to be of the greatest 624 | possible use to the public, the best way to achieve this is to make it 625 | free software which everyone can redistribute and change under these terms. 626 | 627 | To do so, attach the following notices to the program. It is safest 628 | to attach them to the start of each source file to most effectively 629 | state the exclusion of warranty; and each file should have at least 630 | the "copyright" line and a pointer to where the full notice is found. 631 | 632 | 633 | Copyright (C) 634 | 635 | This program is free software: you can redistribute it and/or modify 636 | it under the terms of the GNU Affero General Public License as published by 637 | the Free Software Foundation, either version 3 of the License, or 638 | (at your option) any later version. 639 | 640 | This program is distributed in the hope that it will be useful, 641 | but WITHOUT ANY WARRANTY; without even the implied warranty of 642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 643 | GNU Affero General Public License for more details. 644 | 645 | You should have received a copy of the GNU Affero General Public License 646 | along with this program. If not, see . 647 | 648 | Also add information on how to contact you by electronic and paper mail. 649 | 650 | If your software can interact with users remotely through a computer 651 | network, you should also make sure that it provides a way for users to 652 | get its source. For example, if your program is a web application, its 653 | interface could display a "Source" link that leads users to an archive 654 | of the code. There are many ways you could offer source, and different 655 | solutions will be better for different programs; see section 13 for the 656 | specific requirements. 657 | 658 | You should also get your employer (if you work as a programmer) or school, 659 | if any, to sign a "copyright disclaimer" for the program, if necessary. 660 | For more information on this, and how to apply and follow the GNU AGPL, see 661 | . 662 | --------------------------------------------------------------------------------