rpc_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 }