tordam

A library for peer discovery inside the Tor network
git clone https://git.parazyd.org/tordam
Log | Files | Refs | README | LICENSE

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 }