node.go 4.59 KB
Newer Older
1
2
3
package main

import (
Aviv Eyal's avatar
Aviv Eyal committed
4
5
6
7
	"log"
	"time"
	"bufio"

Aviv Eyal's avatar
Aviv Eyal committed
8
	p2p "github.com/avive/go-libp2p/examples/multipro/pb"
Aviv Eyal's avatar
Aviv Eyal committed
9
	"github.com/gogo/protobuf/proto"
10
	host "gx/ipfs/QmRS46AyqtpJBsf1zmQdeizSDEzo1qkWR7rdEuPFAv8237/go-libp2p-host"
Aviv Eyal's avatar
Aviv Eyal committed
11
	peer "gx/ipfs/QmXYjuNuxVzXKJCfWasQk1RqkhVLDM9jtUKhqc2WPQmFSB/go-libp2p-peer"
12
	crypto "gx/ipfs/QmaPbCnUMBohSGo3KnxEa2bHqyJVVeEEcwtqJAYxerieBo/go-libp2p-crypto"
Aviv Eyal's avatar
Aviv Eyal committed
13
14
	protobufCodec "github.com/multiformats/go-multicodec/protobuf"
	inet "gx/ipfs/QmbD5yKbXahNvoMqzeuNyKQA9vAs9fUvJg2GXeWU1fVqY5/go-libp2p-net"
15
16
)

Aviv Eyal's avatar
Aviv Eyal committed
17
18
19
// node client version
const clientVersion = "go-p2p-node/0.0.1"

Aviv Eyal's avatar
Aviv Eyal committed
20
21
22
23
24
25
26
27
// Node type - a p2p host implementing one or more p2p protocols
type Node struct {
	host.Host     // lib-p2p host
	*PingProtocol // ping protocol impl
	*EchoProtocol // echo protocol impl
	// add other protocols here...
}

Aviv Eyal's avatar
Aviv Eyal committed
28
// Create a new node with its implemented protocols
Aviv Eyal's avatar
Aviv Eyal committed
29
30
31
32
33
34
35
func NewNode(host host.Host, done chan bool) *Node {
	node := &Node{Host: host}
	node.PingProtocol = NewPingProtocol(node, done)
	node.EchoProtocol = NewEchoProtocol(node, done)
	return node
}

Aviv Eyal's avatar
Aviv Eyal committed
36
37
38
// Authenticate incoming p2p message
// message: a protobufs go data object
// data: common p2p message data
39
func (n *Node) authenticateMessage(message proto.Message, data *p2p.MessageData) bool {
40

Aviv Eyal's avatar
Aviv Eyal committed
41
	// store a temp ref to signature and remove it from message data
Aviv Eyal's avatar
Aviv Eyal committed
42
	// sign is a string to allow easy reset to zero-value (empty string)
43
44
45
	sign := data.Sign
	data.Sign = ""

Aviv Eyal's avatar
Aviv Eyal committed
46
	// marshall data without the signature to protobufs3 binary format
47
48
	bin, err := proto.Marshal(message)
	if err != nil {
Aviv Eyal's avatar
Aviv Eyal committed
49
		log.Println(err, "failed to marshal pb message")
50
51
52
53
54
55
		return false
	}

	// restore sig in message data (for possible future use)
	data.Sign = sign

Aviv Eyal's avatar
Aviv Eyal committed
56
	// restore peer id binary format from base58 encoded node id data
57
58
	peerId, err := peer.IDB58Decode(data.NodeId)
	if err != nil {
Aviv Eyal's avatar
Aviv Eyal committed
59
		log.Println(err, "Failed to decode node id from base58")
60
61
62
		return false
	}

Aviv Eyal's avatar
Aviv Eyal committed
63
64
	// verify the data was authored by the signing peer identified by the public key
	// and signature included in the message
65
	return n.verifyData(bin, []byte(sign), peerId, data.NodePubKey)
66
67
}

Aviv Eyal's avatar
Aviv Eyal committed
68
// sign an outgoing p2p message payload
69
func (n *Node) signProtoMessage(message proto.Message) ([]byte, error) {
Aviv Eyal's avatar
Aviv Eyal committed
70
71
72
73
74
75
76
	data, err := proto.Marshal(message)
	if err != nil {
		return nil, err
	}
	return n.signData(data)
}

Aviv Eyal's avatar
Aviv Eyal committed
77
// sign binary data using the local node's private key
78
func (n *Node) signData(data []byte) ([]byte, error) {
Aviv Eyal's avatar
Aviv Eyal committed
79
80
81
	key := n.Peerstore().PrivKey(n.ID())
	res, err := key.Sign(data)
	return res, err
82
83
}

Aviv Eyal's avatar
Aviv Eyal committed
84
85
86
87
88
// Verify incoming p2p message data integrity
// data: data to verify
// signature: author signature provided in the message payload
// peerId: author peer id from the message payload
// pubKeyData: author public key from the message payload
89
func (n *Node) verifyData(data []byte, signature []byte, peerId peer.ID, pubKeyData []byte) bool {
90
91
92
93
94
95
96
97
	key, err := crypto.UnmarshalPublicKey(pubKeyData)
	if err != nil {
		log.Println(err, "Failed to extract key from message key data")
		return false
	}

	// extract node id from the provided public key
	idFromKey, err := peer.IDFromPublicKey(key)
98

99
100
101
102
	if err != nil {
		log.Println(err, "Failed to extract peer id from public key")
		return false
	}
103

Aviv Eyal's avatar
Aviv Eyal committed
104
	// verify that message author node id matches the provided node public key
105
106
	if idFromKey != peerId {
		log.Println(err, "Node id and provided public key mismatch")
Aviv Eyal's avatar
Aviv Eyal committed
107
108
109
110
111
		return false
	}

	res, err := key.Verify(data, signature)
	if err != nil {
112
		log.Println(err, "Error authenticating data")
Aviv Eyal's avatar
Aviv Eyal committed
113
114
115
116
		return false
	}

	return res
117
}
Aviv Eyal's avatar
Aviv Eyal committed
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136

// helper method - generate message data shared between all node's p2p protocols
// messageId: unique for requests, copied from request for responses
func (n *Node) NewMessageData(messageId string, gossip bool) *p2p.MessageData {
	// Add protobufs bin data for message author public key
	// this is useful for authenticating  messages forwarded by a node authored by another node
	nodePubKey, err := n.Peerstore().PubKey(n.ID()).Bytes()

	if err != nil {
		panic("Failed to get public key for sender from local peer store.")
	}

	return &p2p.MessageData{ClientVersion: clientVersion,
		NodeId:     peer.IDB58Encode(n.ID()),
		NodePubKey: nodePubKey,
		Timestamp:  time.Now().Unix(),
		Id:         messageId,
		Gossip:     gossip}
}
Aviv Eyal's avatar
Aviv Eyal committed
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151

// helper method - writes a protobuf go data object to a network stream
// data: reference of protobuf go data object to send (not the object itself)
// s: network stream to write the data to
func sendProtoMessage(data proto.Message, s inet.Stream) bool {
	writer := bufio.NewWriter(s)
	enc := protobufCodec.Multicodec(nil).Encoder(writer)
	err := enc.Encode(data)
	if err != nil {
		log.Println(err)
		return false
	}
	writer.Flush()
	return true
}