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

import (
	"errors"
	"fmt"
	"strconv"
7
	"sync"
8

Caro Axel's avatar
Caro Axel committed
9 10
	"dfss"
	cAPI "dfss/dfssc/api"
11
	"dfss/dfssc/common"
12
	"dfss/dfssc/security"
13
	dAPI "dfss/dfssd/api"
Caro Axel's avatar
Caro Axel committed
14
	pAPI "dfss/dfssp/api"
15
	"dfss/dfssp/contract"
Caro Axel's avatar
Caro Axel committed
16 17
	tAPI "dfss/dfsst/api"
	"dfss/dfsst/entities"
18
	"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 {
Caro Axel's avatar
Caro Axel committed
29 30 31 32 33
	auth           *security.AuthContainer
	contract       *contract.JSON // contains the contractUUID, the list of the signers' hashes, the hash of the contract
	platform       pAPI.PlatformClient
	platformConn   *grpc.ClientConn
	ttp            tAPI.TTPClient
34
	ttpData        *pAPI.LaunchSignature_TTP
Caro Axel's avatar
Caro Axel committed
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
	peersConn      map[string]*grpc.ClientConn
	peers          map[string]*cAPI.ClientClient
	hashToID       map[string]uint32
	nbReady        int
	cServer        *grpc.Server
	cServerIface   clientServer
	sequence       []uint32
	lastValidIndex int // the last index at which we sent a promise
	currentIndex   int
	myID           uint32
	uuid           string
	keyHash        [][]byte
	mail           string
	archives       *Archives
	seal           []byte
	cancelled      bool
	finished       bool
Loïck Bonniot's avatar
Loïck Bonniot committed
52 53 54 55

	// Callbacks
	OnSignerStatusUpdate func(mail string, status SignerStatus, data string)
	OnProgressUpdate     func(current int, end int)
56
	Cancel               chan interface{}
57 58
}

59
// Archives stores the received and sent messages, as evidence if needed
60
type Archives struct {
Caro Axel's avatar
Caro Axel committed
61
	receivedPromises   []*cAPI.Promise // TODO: improve by using a map
62
	sentSignatures     []*cAPI.Signature
63
	receivedSignatures []*cAPI.Signature
64
	mutex              sync.Mutex
65 66 67
}

// NewSignatureManager populates a SignatureManager and connects to the platform.
ElyKar's avatar
ElyKar committed
68
func NewSignatureManager(passphrase string, c *contract.JSON) (*SignatureManager, error) {
69
	m := &SignatureManager{
ElyKar's avatar
ElyKar committed
70 71
		auth:     security.NewAuthContainer(passphrase),
		contract: c,
72
		archives: &Archives{
73
			receivedPromises:   make([]*cAPI.Promise, 0),
74
			sentSignatures:     make([]*cAPI.Signature, 0),
75
			receivedSignatures: make([]*cAPI.Signature, 0),
76
		},
77
		Cancel: make(chan interface{}),
78
	}
Caro Axel's avatar
Caro Axel committed
79
	var err error
80
	_, _, _, err = m.auth.LoadFiles()
81 82 83 84
	if err != nil {
		return nil, err
	}

85
	m.mail = m.auth.Cert.Subject.CommonName
86
	dAPI.SetIdentifier(m.mail)
87

Caro Axel's avatar
Caro Axel committed
88
	m.cServer = m.GetServer()
89
	go func() { _ = net.Listen("0.0.0.0:"+strconv.Itoa(viper.GetInt("local_port")), m.cServer) }()
Caro Axel's avatar
Caro Axel committed
90

Caro Axel's avatar
Caro Axel committed
91
	connp, err := net.Connect(viper.GetString("platform_addrport"), m.auth.Cert, m.auth.Key, m.auth.CA, nil)
92 93 94 95
	if err != nil {
		return nil, err
	}

Caro Axel's avatar
Caro Axel committed
96 97 98
	m.platform = pAPI.NewPlatformClient(connp)
	m.platformConn = connp

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

Caro Axel's avatar
Caro Axel committed
107 108 109 110
	// Initialize TTP AuthContainer
	// This is needed to use platform seal verification client-side
	entities.AuthContainer = m.auth

111 112 113 114 115
	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 {
116 117 118 119 120
	localIps, err := net.ExternalInterfaceAddr()
	if err != nil {
		return err
	}

Caro Axel's avatar
Caro Axel committed
121
	stream, err := m.platform.JoinSignature(context.Background(), &pAPI.JoinSignatureRequest{
122
		ContractUuid: m.contract.UUID,
ElyKar's avatar
ElyKar committed
123
		Port:         uint32(viper.GetInt("local_port")),
124
		Ip:           localIps,
125 126
	})
	if err != nil {
127 128
		m.finished = true
		m.closeConnections()
129 130 131
		return err
	}

132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
	c := make(chan error)
	go connectToPeersLoop(m, stream, c)

	select {
	case err = <-c:
		if err != nil {
			m.finished = true
			m.closeConnections()
		}
		return err
	case <-m.Cancel:
		m.cancelled = true
		m.closeConnections()
		return errors.New("Signature cancelled")
	}
}

func connectToPeersLoop(m *SignatureManager, stream pAPI.Platform_JoinSignatureClient, c chan error) {
	for !m.cancelled {
151 152
		userConnected, err := stream.Recv()
		if err != nil {
153 154
			c <- err
			return
155 156
		}
		errorCode := userConnected.GetErrorCode()
Caro Axel's avatar
Caro Axel committed
157
		if errorCode.Code != pAPI.ErrorCode_SUCCESS {
158 159
			c <- errors.New(errorCode.Message)
			return
160 161 162
		}
		ready, err := m.addPeer(userConnected.User)
		if err != nil {
163
			continue // Unable to connect to this user, ignore it for the moment
164 165
		}
		if ready {
166 167
			c <- nil
			return
168 169 170 171 172
		}
	}
}

// addPeer stores a peer from the platform and tries to establish a connection to this peer.
Caro Axel's avatar
Caro Axel committed
173
func (m *SignatureManager) addPeer(user *pAPI.User) (ready bool, err error) {
174 175 176 177
	if user == nil {
		err = errors.New("unexpected user format")
		return
	}
178 179 180
	if _, ok := m.peers[user.Email]; !ok {
		return // Ignore if unknown
	}
181

182 183 184 185 186 187 188 189 190 191
	var conn *grpc.ClientConn
	for _, ip := range user.Ip {
		addrPort := ip + ":" + strconv.Itoa(int(user.Port))
		m.OnSignerStatusUpdate(user.Email, StatusConnecting, addrPort)

		// This is an certificate authentificated TLS connection
		conn, err = net.Connect(addrPort, m.auth.Cert, m.auth.Key, m.auth.CA, user.KeyHash)
		if err == nil {
			break
		}
192 193 194 195 196

		if m.cancelled {
			err = errors.New("Signature cancelled")
			break
		}
197
	}
198

Caro Axel's avatar
Caro Axel committed
199
	if err != nil {
Loïck Bonniot's avatar
Loïck Bonniot committed
200
		m.OnSignerStatusUpdate(user.Email, StatusError, err.Error())
Caro Axel's avatar
Caro Axel committed
201 202 203 204 205
		return false, err
	}

	// Sending Hello message
	client := cAPI.NewClientClient(conn)
206
	lastConnection := m.peers[user.Email]
Caro Axel's avatar
Caro Axel committed
207
	m.peers[user.Email] = &client
208 209 210
	// The connection is encapsulated into the interface, so we
	// need to create another way to access it
	m.peersConn[user.Email] = conn
211

212
	ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
213 214
	defer cancel()
	msg, err := client.Discover(ctx, &cAPI.Hello{Version: dfss.Version})
Caro Axel's avatar
Caro Axel committed
215
	if err != nil {
Loïck Bonniot's avatar
Loïck Bonniot committed
216
		m.OnSignerStatusUpdate(user.Email, StatusError, err.Error())
Caro Axel's avatar
Caro Axel committed
217 218
		return false, err
	}
219

Caro Axel's avatar
Caro Axel committed
220
	// Printing answer: application version
221
	// TODO check certificate
Loïck Bonniot's avatar
Loïck Bonniot committed
222
	m.OnSignerStatusUpdate(user.Email, StatusConnected, msg.Version)
223 224

	// Check if we have any other peer to connect to
225 226 227 228
	if lastConnection == nil {
		m.nbReady++
		if m.nbReady == len(m.contract.Signers)-1 {
			return true, nil
229 230 231
		}
	}

232
	return false, nil
233
}
Loïck Bonniot's avatar
Loïck Bonniot committed
234 235 236

// 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) {
237
	ctx, cancel := context.WithTimeout(context.Background(), 10*viper.GetDuration("timeout"))
Loïck Bonniot's avatar
Loïck Bonniot committed
238
	defer cancel()
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254

	c := make(chan *pAPI.LaunchSignature)
	go func() {
		launch, _ := m.platform.ReadySign(ctx, &pAPI.ReadySignRequest{
			ContractUuid: m.contract.UUID,
		})
		c <- launch
	}()

	var launch *pAPI.LaunchSignature
	select {
	case launch = <-c: // OK
	case <-m.Cancel:
		m.cancelled = true
		m.closeConnections()
		err = errors.New("Signature cancelled")
Loïck Bonniot's avatar
Loïck Bonniot committed
255 256 257 258
		return
	}

	errorCode := launch.GetErrorCode()
Loïck Bonniot's avatar
Loïck Bonniot committed
259
	if errorCode.Code != pAPI.ErrorCode_SUCCESS {
Loïck Bonniot's avatar
Loïck Bonniot committed
260
		err = errors.New(errorCode.Code.String() + " " + errorCode.Message)
261 262
		m.finished = true
		m.closeConnections()
Loïck Bonniot's avatar
Loïck Bonniot committed
263 264 265
		return
	}

266 267 268
	// 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")
269 270
		m.finished = true
		m.closeConnections()
271 272 273 274 275 276
		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")
277 278
			m.finished = true
			m.closeConnections()
279 280 281 282
			return
		}
	}

283 284 285
	// Connect to TTP, if any
	err = m.connectToTTP(launch.Ttp)

286 287
	m.sequence = launch.Sequence
	m.uuid = launch.SignatureUuid
288
	m.keyHash = launch.KeyHash
289
	m.seal = launch.Seal
290
	signatureUUID = m.uuid
Loïck Bonniot's avatar
Loïck Bonniot committed
291 292
	return
}
293

294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
// connectToTTP : tries to open a connection with the ttp specified in the contract.
func (m *SignatureManager) connectToTTP(ttp *pAPI.LaunchSignature_TTP) error {
	if ttp == nil {
		m.ttpData = &pAPI.LaunchSignature_TTP{
			Addrport: "",
			Hash:     []byte{},
		}
		return nil
	}

	// TODO check that the connection spots missing TTP and returns an error quickly enough
	conn, err := net.Connect(ttp.Addrport, m.auth.Cert, m.auth.Key, m.auth.CA, ttp.Hash)
	if err != nil {
		return err
	}

	m.ttpData = ttp
	m.ttp = tAPI.NewTTPClient(conn)
	return nil
}

315
// Initialize computes the values needed for the start of the signing
Caro Axel's avatar
Caro Axel committed
316
func (m *SignatureManager) Initialize() (int, error) {
317 318
	myID, err := m.FindID()
	if err != nil {
Caro Axel's avatar
Caro Axel committed
319
		return 0, err
320
	}
Caro Axel's avatar
Caro Axel committed
321
	m.myID = myID
322

323
	m.currentIndex, err = common.FindNextIndex(m.sequence, myID, -1)
324
	if err != nil {
Caro Axel's avatar
Caro Axel committed
325
		return 0, err
326 327
	}

328
	nextIndex, err := common.FindNextIndex(m.sequence, myID, m.currentIndex)
329
	if err != nil {
Caro Axel's avatar
Caro Axel committed
330
		return 0, err
331 332
	}

Caro Axel's avatar
Caro Axel committed
333 334 335
	m.lastValidIndex = 0

	return nextIndex, nil
336 337 338 339
}

// FindID finds the sequence id for the user's email and the contract to sign
func (m *SignatureManager) FindID() (uint32, error) {
340 341
	signers := m.contract.Signers
	for id, signer := range signers {
342
		if signer.Email == m.mail {
343 344 345 346 347
			return uint32(id), nil
		}
	}
	return 0, errors.New("Mail couldn't be found amongst signers")
}
348 349 350 351 352

// IsTerminated returns true if the signature is cancelled or finished
func (m *SignatureManager) IsTerminated() bool {
	return m.cancelled || m.finished
}