starter.go 6.72 KB
Newer Older
1 2 3 4 5
package sign

import (
	"errors"
	"fmt"
6
	"log"
7
	"strconv"
8
	"sync"
Loïck Bonniot's avatar
Loïck Bonniot committed
9
	"time"
10

Caro Axel's avatar
Caro Axel committed
11 12
	"dfss"
	cAPI "dfss/dfssc/api"
13
	"dfss/dfssc/common"
14
	"dfss/dfssc/security"
15
	dAPI "dfss/dfssd/api"
Caro Axel's avatar
Caro Axel committed
16
	pAPI "dfss/dfssp/api"
17 18
	"dfss/dfssp/contract"
	"dfss/net"
19
	"github.com/spf13/viper"
20
	"golang.org/x/net/context"
Caro Axel's avatar
Caro Axel committed
21
	"google.golang.org/grpc"
22 23
)

24 25 26
// Limit the buffer size of the channels
const chanBufferSize = 100

27 28
// SignatureManager handles the signature of a contract.
type SignatureManager struct {
29 30 31
	auth         *security.AuthContainer
	contract     *contract.JSON // contains the contractUUID, the list of the signers' hashes, the hash of the contract
	platform     pAPI.PlatformClient
32 33
	platformConn *grpc.ClientConn
	peersConn    map[string]*grpc.ClientConn
34
	peers        map[string]*cAPI.ClientClient
35
	hashToID     map[string]uint32
36 37
	nbReady      int
	cServer      *grpc.Server
38
	cServerIface clientServer
39 40 41 42 43 44
	sequence     []uint32
	currentIndex int
	uuid         string
	keyHash      [][]byte
	mail         string
	archives     *Archives
Loïck Bonniot's avatar
Loïck Bonniot committed
45 46 47 48

	// Callbacks
	OnSignerStatusUpdate func(mail string, status SignerStatus, data string)
	OnProgressUpdate     func(current int, end int)
49 50
}

51
// Archives stores the received and sent messages, as evidence if needed
52 53
type Archives struct {
	sentPromises       []*cAPI.Promise
54
	receivedPromises   []*cAPI.Promise
55
	sentSignatures     []*cAPI.Signature
56
	receivedSignatures []*cAPI.Signature
57
	mutex              sync.Mutex
58 59 60
}

// NewSignatureManager populates a SignatureManager and connects to the platform.
ElyKar's avatar
ElyKar committed
61
func NewSignatureManager(passphrase string, c *contract.JSON) (*SignatureManager, error) {
62
	m := &SignatureManager{
ElyKar's avatar
ElyKar committed
63 64
		auth:     security.NewAuthContainer(passphrase),
		contract: c,
65 66
		archives: &Archives{
			sentPromises:       make([]*cAPI.Promise, 0),
67
			receivedPromises:   make([]*cAPI.Promise, 0),
68
			sentSignatures:     make([]*cAPI.Signature, 0),
69
			receivedSignatures: make([]*cAPI.Signature, 0),
70
		},
71
	}
Caro Axel's avatar
Caro Axel committed
72
	var err error
73
	_, _, _, err = m.auth.LoadFiles()
74 75 76 77
	if err != nil {
		return nil, err
	}

78
	m.mail = m.auth.Cert.Subject.CommonName
79
	dAPI.SetIdentifier(m.mail)
80

Caro Axel's avatar
Caro Axel committed
81
	m.cServer = m.GetServer()
ElyKar's avatar
ElyKar committed
82
	go func() { log.Fatalln(net.Listen("0.0.0.0:"+strconv.Itoa(viper.GetInt("local_port")), m.cServer)) }()
Caro Axel's avatar
Caro Axel committed
83

84
	conn, err := net.Connect(viper.GetString("platform_addrport"), m.auth.Cert, m.auth.Key, m.auth.CA, nil)
85 86 87 88
	if err != nil {
		return nil, err
	}

Caro Axel's avatar
Caro Axel committed
89
	m.platform = pAPI.NewPlatformClient(conn)
90
	m.platformConn = conn
91

92
	m.peersConn = make(map[string]*grpc.ClientConn)
Caro Axel's avatar
Caro Axel committed
93
	m.peers = make(map[string]*cAPI.ClientClient)
94
	for _, u := range c.Signers {
95
		if u.Email != m.auth.Cert.Subject.CommonName {
96 97
			m.peers[u.Email] = nil
		}
98 99 100 101 102 103 104
	}

	return m, nil
}

// ConnectToPeers tries to fetch the list of users for this contract, and tries to establish a connection to each peer.
func (m *SignatureManager) ConnectToPeers() error {
Caro Axel's avatar
Caro Axel committed
105
	stream, err := m.platform.JoinSignature(context.Background(), &pAPI.JoinSignatureRequest{
106
		ContractUuid: m.contract.UUID,
ElyKar's avatar
ElyKar committed
107
		Port:         uint32(viper.GetInt("local_port")),
108 109 110 111 112 113 114 115 116 117 118
	})
	if err != nil {
		return err
	}

	for {
		userConnected, err := stream.Recv()
		if err != nil {
			return err
		}
		errorCode := userConnected.GetErrorCode()
Caro Axel's avatar
Caro Axel committed
119
		if errorCode.Code != pAPI.ErrorCode_SUCCESS {
120 121 122 123
			return errors.New(errorCode.Message)
		}
		ready, err := m.addPeer(userConnected.User)
		if err != nil {
124
			continue // Unable to connect to this user, ignore it for the moment
125 126 127 128 129 130 131 132 133 134
		}
		if ready {
			break
		}
	}

	return nil
}

// addPeer stores a peer from the platform and tries to establish a connection to this peer.
Caro Axel's avatar
Caro Axel committed
135
func (m *SignatureManager) addPeer(user *pAPI.User) (ready bool, err error) {
136 137 138 139
	if user == nil {
		err = errors.New("unexpected user format")
		return
	}
140 141 142
	if _, ok := m.peers[user.Email]; !ok {
		return // Ignore if unknown
	}
143

Caro Axel's avatar
Caro Axel committed
144
	addrPort := user.Ip + ":" + strconv.Itoa(int(user.Port))
Loïck Bonniot's avatar
Loïck Bonniot committed
145
	m.OnSignerStatusUpdate(user.Email, StatusConnecting, addrPort)
146

147 148
	// This is an certificate authentificated TLS connection
	conn, err := net.Connect(addrPort, m.auth.Cert, m.auth.Key, m.auth.CA, user.KeyHash)
Caro Axel's avatar
Caro Axel committed
149
	if err != nil {
Loïck Bonniot's avatar
Loïck Bonniot committed
150
		m.OnSignerStatusUpdate(user.Email, StatusError, err.Error())
Caro Axel's avatar
Caro Axel committed
151 152 153 154 155
		return false, err
	}

	// Sending Hello message
	client := cAPI.NewClientClient(conn)
156
	lastConnection := m.peers[user.Email]
Caro Axel's avatar
Caro Axel committed
157
	m.peers[user.Email] = &client
158 159 160
	// The connection is encapsulated into the interface, so we
	// need to create another way to access it
	m.peersConn[user.Email] = conn
161 162 163 164

	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
	defer cancel()
	msg, err := client.Discover(ctx, &cAPI.Hello{Version: dfss.Version})
Caro Axel's avatar
Caro Axel committed
165
	if err != nil {
Loïck Bonniot's avatar
Loïck Bonniot committed
166
		m.OnSignerStatusUpdate(user.Email, StatusError, err.Error())
Caro Axel's avatar
Caro Axel committed
167 168
		return false, err
	}
169

Caro Axel's avatar
Caro Axel committed
170
	// Printing answer: application version
171
	// TODO check certificate
Loïck Bonniot's avatar
Loïck Bonniot committed
172
	m.OnSignerStatusUpdate(user.Email, StatusConnected, msg.Version)
173 174

	// Check if we have any other peer to connect to
175 176 177 178
	if lastConnection == nil {
		m.nbReady++
		if m.nbReady == len(m.contract.Signers)-1 {
			return true, nil
179 180 181
		}
	}

182
	return false, nil
183
}
Loïck Bonniot's avatar
Loïck Bonniot committed
184 185 186

// SendReadySign sends the READY signal to the platform, and wait (potentially a long time) for START signal.
func (m *SignatureManager) SendReadySign() (signatureUUID string, err error) {
187
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
Loïck Bonniot's avatar
Loïck Bonniot committed
188
	defer cancel()
Loïck Bonniot's avatar
Loïck Bonniot committed
189
	launch, err := m.platform.ReadySign(ctx, &pAPI.ReadySignRequest{
Loïck Bonniot's avatar
Loïck Bonniot committed
190 191 192 193 194 195 196
		ContractUuid: m.contract.UUID,
	})
	if err != nil {
		return
	}

	errorCode := launch.GetErrorCode()
Loïck Bonniot's avatar
Loïck Bonniot committed
197
	if errorCode.Code != pAPI.ErrorCode_SUCCESS {
Loïck Bonniot's avatar
Loïck Bonniot committed
198 199 200 201
		err = errors.New(errorCode.Code.String() + " " + errorCode.Message)
		return
	}

202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
	// Check signers from platform data
	if len(m.contract.Signers) != len(launch.KeyHash) {
		err = errors.New("Corrupted DFSS file: bad number of signers, unable to sign safely")
		return
	}

	for i, s := range m.contract.Signers {
		if s.Hash != fmt.Sprintf("%x", launch.KeyHash[i]) {
			err = errors.New("Corrupted DFSS file: signer " + s.Email + " has an invalid hash, unable to sign safely")
			return
		}
	}

	m.sequence = launch.Sequence
	m.uuid = launch.SignatureUuid
217
	m.keyHash = launch.KeyHash
218
	signatureUUID = m.uuid
Loïck Bonniot's avatar
Loïck Bonniot committed
219 220
	return
}
221

222
// Initialize computes the values needed for the start of the signing
223
func (m *SignatureManager) Initialize() (uint32, int, error) {
224 225
	myID, err := m.FindID()
	if err != nil {
226
		return 0, 0, err
227 228
	}

229
	m.currentIndex, err = common.FindNextIndex(m.sequence, myID, -1)
230
	if err != nil {
231
		return 0, 0, err
232 233
	}

234
	nextIndex, err := common.FindNextIndex(m.sequence, myID, m.currentIndex)
235
	if err != nil {
236
		return 0, 0, err
237 238
	}

239
	return myID, nextIndex, nil
240 241 242 243
}

// FindID finds the sequence id for the user's email and the contract to sign
func (m *SignatureManager) FindID() (uint32, error) {
244 245
	signers := m.contract.Signers
	for id, signer := range signers {
246
		if signer.Email == m.mail {
247 248 249 250 251
			return uint32(id), nil
		}
	}
	return 0, errors.New("Mail couldn't be found amongst signers")
}