tor-dam

tor distributed announce mechanism (not a dht)
git clone https://git.parazyd.org/tor-dam
Log | Files | Refs | README | LICENSE

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 }