rpc_announce.go (5533B)
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 "strings" 26 "time" 27 ) 28 29 // Ann is the struct for the announce JSON-RPC endpoint. 30 type Ann struct{} 31 32 // Init takes three parameters: 33 // - onion: onionaddress:port where the peer and tordam can be reached 34 // - pubkey: ed25519 public signing key in base64 35 // - portmap: List of ports available for communication 36 // - (optional) revoke: Revocation key for updating peer info 37 // { 38 // "jsonrpc":"2.0", 39 // "id": 1, 40 // "method": "ann.Init", 41 // "params": ["unlikelynameforan.onion:49371", "214=", "69:420,323:2354"] 42 // } 43 // Returns: 44 // - nonce: A random nonce which is to be signed by the client 45 // - revoke: A key which can be used to revoke key and portman and reannounce the peer 46 // { 47 // "jsonrpc":"2.0", 48 // "id":1, 49 // "result": ["somenonce", "somerevokekey"] 50 // } 51 // On any kind of failure returns an error and the reason. 52 func (Ann) Init(ctx context.Context, vals []string) ([]string, error) { 53 if len(vals) != 3 && len(vals) != 4 { 54 return nil, errors.New("invalid parameters") 55 } 56 57 onion := vals[0] 58 pubkey := vals[1] 59 portmap := strings.Split(vals[2], ",") 60 61 if err := ValidateOnionInternal(onion); err != nil { 62 rpcWarn("ann.Init", err.Error()) 63 return nil, err 64 } 65 66 rpcInfo("ann.Init", "got request for", onion) 67 68 var peer Peer 69 reallySeen := false 70 peer, ok := Peers[onion] 71 if ok { 72 // We have seen this peer 73 if peer.Pubkey != nil || peer.PeerRevoke != "" { 74 reallySeen = true 75 } 76 } 77 78 if reallySeen { 79 // Peer announced to us before 80 if len(vals) != 4 { 81 rpcWarn("ann.Init", "no revocation key provided") 82 return nil, errors.New("no revocation key provided") 83 } 84 revoke := vals[3] 85 if strings.Compare(revoke, peer.PeerRevoke) != 0 { 86 rpcWarn("ann.Init", "revocation key doesn't match") 87 return nil, errors.New("revocation key doesn't match") 88 } 89 } 90 91 pk, err := base64.StdEncoding.DecodeString(pubkey) 92 if err != nil { 93 rpcWarn("ann.Init", "got invalid base64 public key") 94 return nil, errors.New("invalid base64 public key") 95 } else if len(pk) != 32 { 96 rpcWarn("ann.Init", "got invalid pubkey (len != 32)") 97 return nil, errors.New("invalid public key") 98 } 99 100 if err := ValidatePortmap(portmap); err != nil { 101 rpcWarn("ann.Init", err.Error()) 102 return nil, err 103 } 104 105 nonce, err := RandomGarbage(32) 106 if err != nil { 107 rpcInternalErr("ann.Init", err.Error()) 108 return nil, errors.New("internal error") 109 } 110 111 newrevoke, err := RandomGarbage(128) 112 if err != nil { 113 rpcInternalErr("ann.Init", err.Error()) 114 return nil, errors.New("internal error") 115 } 116 117 peer.Pubkey = pk 118 peer.Portmap = portmap 119 peer.Nonce = nonce 120 peer.PeerRevoke = newrevoke 121 peer.LastSeen = time.Now().Unix() 122 peer.Trusted = 0 123 Peers[onion] = peer 124 125 return []string{nonce, newrevoke}, nil 126 } 127 128 // Validate takes two parameters: 129 // - onion: onionaddress:port where the peer and tordam can be reached 130 // - signature: base64 signature of the previously obtained nonce 131 // { 132 // "jsonrpc":"2.0", 133 // "id":2, 134 // "method": "ann.Announce", 135 // "params": ["unlikelynameforan.onion:49371", "deadbeef=="] 136 // } 137 // Returns: 138 // - peers: A list of known validated peers (max. 50) 139 // { 140 // "jsonrpc":"2.0", 141 // "id":2, 142 // "result": ["unlikelynameforan.onion:69", "yetanother.onion:420"] 143 // } 144 // On any kind of failure returns an error and the reason. 145 func (Ann) Validate(ctx context.Context, vals []string) ([]string, error) { 146 if len(vals) != 2 { 147 return nil, errors.New("invalid parameters") 148 } 149 150 onion := vals[0] 151 signature := vals[1] 152 153 if err := ValidateOnionInternal(onion); err != nil { 154 rpcWarn("ann.Validate", err.Error()) 155 return nil, err 156 } 157 158 rpcInfo("ann.Validate", "got request for", onion) 159 160 peer, ok := Peers[onion] 161 if !ok { 162 rpcWarn("ann.Validate", onion, "not in peer map") 163 return nil, errors.New("this onion was not seen before") 164 } 165 166 if peer.Pubkey == nil || peer.Nonce == "" { 167 rpcWarn("ann.Validate", onion, "tried to validate before init") 168 return nil, errors.New("tried to validate before init") 169 } 170 171 sig, err := base64.StdEncoding.DecodeString(signature) 172 if err != nil { 173 rpcWarn("ann.Validate", "invalid base64 signature string") 174 return nil, errors.New("invalid base64 signature string") 175 } 176 177 if !ed25519.Verify(peer.Pubkey, []byte(peer.Nonce), sig) { 178 rpcWarn("ann.Validate", "signature verification failed") 179 // delete(Peers, onion) 180 return nil, errors.New("signature verification failed") 181 } 182 183 rpcInfo("ann.Validate", "validation success for", onion) 184 185 var ret []string 186 for addr, data := range Peers { 187 if data.Trusted > 0 { 188 ret = append(ret, addr) 189 } 190 } 191 192 peer.Nonce = "" 193 peer.Trusted = 1 194 peer.LastSeen = time.Now().Unix() 195 Peers[onion] = peer 196 197 rpcInfo("ann.Validate", "sending back list of peers to", onion) 198 return ret, nil 199 }