trpc_announce.go - tordam - A library for peer discovery inside the Tor network
HTML git clone https://git.parazyd.org/tordam
DIR Log
DIR Files
DIR Refs
DIR README
DIR LICENSE
---
trpc_announce.go (5400B)
---
1 // Copyright (c) 2017-2021 Ivan Jelincic <parazyd@dyne.org>
2 //
3 // This file is part of tordam
4 //
5 // This program is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU Affero General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU Affero General Public License for more details.
14 //
15 // You should have received a copy of the GNU Affero General Public License
16 // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
18 package tordam
19
20 import (
21 "context"
22 "crypto/ed25519"
23 "encoding/base64"
24 "errors"
25 "fmt"
26 "strings"
27 "time"
28 )
29
30 // Ann is the struct for the JSON-RPC announce endpoint.
31 type Ann struct{}
32
33 // Init takes three parameters:
34 // - onion: onionaddress:port where the peer and tordam can be reached
35 // - pubkey: ed25519 public signing key in base64
36 // - portmap: List of ports available for communication
37 // - (optional) revoke: Revocation key for updating peer info
38 // {
39 // "jsonrpc":"2.0",
40 // "id": 1,
41 // "method": "ann.Init",
42 // "params": ["unlikelynameforan.onion:49371", "214=", "69:420,323:2354"]
43 // }
44 // Returns:
45 // - nonce: A random nonce which is to be signed by the client
46 // - revoke: A key which can be used to revoke key and portmap and reannounce the peer
47 // {
48 // "jsonrpc":"2.0",
49 // "id":1,
50 // "result": ["somenonce", "somerevokekey"]
51 // }
52 // On any kind of failure returns an error and the reason.
53 func (Ann) Init(ctx context.Context, vals []string) ([]string, error) {
54 if len(vals) != 3 && len(vals) != 4 {
55 return nil, errors.New("invalid parameters")
56 }
57
58 onion := vals[0]
59 pubkey := vals[1]
60 portmap := strings.Split(vals[2], ",")
61
62 if err := ValidateOnionInternal(onion); err != nil {
63 rpcWarn(err.Error())
64 return nil, err
65 }
66
67 rpcInfo(fmt.Sprintf("got request for %s", onion))
68
69 var peer Peer
70 reallySeen := false
71 peer, ok := Peers[onion]
72 if ok {
73 // We have seen this peer
74 if peer.Pubkey != nil || peer.PeerRevoke != "" {
75 reallySeen = true
76 }
77 }
78
79 if reallySeen {
80 // Peer announced to us before
81 if len(vals) != 4 {
82 rpcWarn("no revocation key provided")
83 return nil, errors.New("no revocation key provided")
84 }
85 revoke := vals[3]
86 if strings.Compare(revoke, peer.PeerRevoke) != 0 {
87 rpcWarn("revocation key doesn't match")
88 return nil, errors.New("revocation key doesn't match")
89 }
90 }
91
92 pk, err := base64.StdEncoding.DecodeString(pubkey)
93 if err != nil {
94 rpcWarn("got invalid base64 public key")
95 return nil, errors.New("invalid base64 public key")
96 } else if len(pk) != 32 {
97 rpcWarn("got invalid pubkey (len != 32)")
98 return nil, errors.New("invalid public key")
99 }
100
101 if err := ValidatePortmap(portmap); err != nil {
102 rpcWarn(err.Error())
103 return nil, err
104 }
105
106 nonce, err := RandomGarbage(32)
107 if err != nil {
108 rpcInternalErr(err.Error())
109 return nil, errors.New("internal error")
110 }
111
112 newrevoke, err := RandomGarbage(128)
113 if err != nil {
114 rpcInternalErr(err.Error())
115 return nil, errors.New("internal error")
116 }
117
118 peer.Pubkey = pk
119 peer.Portmap = portmap
120 peer.Nonce = nonce
121 peer.PeerRevoke = newrevoke
122 peer.LastSeen = time.Now().Unix()
123 peer.Trusted = 0
124 Peers[onion] = peer
125
126 return []string{nonce, newrevoke}, nil
127 }
128
129 // Validate takes two parameters:
130 // - onion: onionaddress:port where the peer and tordam can be reached
131 // - signature: base64 signature of the previously obtained nonce
132 // {
133 // "jsonrpc":"2.0",
134 // "id":2,
135 // "method": "ann.Announce",
136 // "params": ["unlikelynameforan.onion:49371", "deadbeef=="]
137 // }
138 // Returns:
139 // - peers: A list of known validated peers (max. 50)
140 // {
141 // "jsonrpc":"2.0",
142 // "id":2,
143 // "result": ["unlikelynameforan.onion:69", "yetanother.onion:420"]
144 // }
145 // On any kind of failure returns an error and the reason.
146 func (Ann) Validate(ctx context.Context, vals []string) ([]string, error) {
147 if len(vals) != 2 {
148 return nil, errors.New("invalid parameters")
149 }
150
151 onion := vals[0]
152 signature := vals[1]
153
154 if err := ValidateOnionInternal(onion); err != nil {
155 rpcWarn(err.Error())
156 return nil, err
157 }
158
159 rpcInfo(fmt.Sprintf("got request for %s", onion))
160
161 peer, ok := Peers[onion]
162 if !ok {
163 rpcWarn(fmt.Sprintf("%s not in peer map", onion))
164 return nil, errors.New("this onion was not seen before")
165 }
166
167 if peer.Pubkey == nil || peer.Nonce == "" {
168 rpcWarn(fmt.Sprintf("%s tried to validate before init", onion))
169 return nil, errors.New("tried to validate before init")
170 }
171
172 sig, err := base64.StdEncoding.DecodeString(signature)
173 if err != nil {
174 rpcWarn("invalid base64 signature string")
175 return nil, errors.New("invalid base64 signature string")
176 }
177
178 if !ed25519.Verify(peer.Pubkey, []byte(peer.Nonce), sig) {
179 rpcWarn("signature verification failed")
180 // delete(Peers, onion)
181 return nil, errors.New("signature verification failed")
182 }
183
184 rpcInfo(fmt.Sprintf("validation success for %s", onion))
185
186 var ret []string
187 for addr, data := range Peers {
188 if data.Trusted > 0 {
189 ret = append(ret, addr)
190 }
191 }
192
193 peer.Nonce = ""
194 peer.Trusted = 1
195 peer.LastSeen = time.Now().Unix()
196 Peers[onion] = peer
197
198 rpcInfo(fmt.Sprintf("sending back list of peers to %s", onion))
199 return ret, nil
200 }