trpc_announce.go - tordam - A library for peer discovery inside the Tor network
 (HTM) 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 }