Commit d8ee0857 authored by Loïck Bonniot's avatar Loïck Bonniot

[p] Add JoinSignature API

parent 438c6a5f
This diff is collapsed.
......@@ -74,14 +74,15 @@ message JoinSignatureRequest {
message UserConnected {
ErrorCode errorCode = 1;
string contractUuid = 2;
message User {
bytes KeyHash = 1;
string email = 2;
string ip = 3;
uint32 port = 4;
}
User user = 3;
}
message User {
bytes keyHash = 1;
string email = 2;
string ip = 3;
uint32 port = 4;
}
// ReadySignRequest contains the contract unique identitier that is ready to be signed
message ReadySignRequest {
string contractUuid = 1;
......
......@@ -113,10 +113,9 @@ func TestInsertContract(t *testing.T) {
// Insert some contracts with missing user and test waiting contracts for this user
func TestGetWaitingForUser(t *testing.T) {
knownID := bson.NewObjectId()
dropDataset()
c1 := entities.NewContract()
c1.AddSigner(nil, "mail1", []byte{})
c1.Ready = false
......@@ -139,3 +138,22 @@ func TestGetWaitingForUser(t *testing.T) {
assert.Equal(t, nil, err)
assert.Equal(t, 2, len(contracts))
}
func TestCheckAuthorization(t *testing.T) {
dropDataset()
createDataset()
id := addTestContract()
assert.T(t, repository.CheckAuthorization(user1.CertHash, id))
assert.T(t, !repository.CheckAuthorization(user1.CertHash, bson.NewObjectId()))
assert.T(t, !repository.CheckAuthorization(user2.CertHash, id))
assert.T(t, !repository.CheckAuthorization(user2.CertHash, bson.NewObjectId()))
contract := entities.Contract{}
_ = repository.Collection.FindByID(entities.Contract{ID: id}, &contract)
contract.Ready = false
_, _ = repository.Collection.UpdateByID(contract)
// Not valid if contract is not ready
assert.T(t, !repository.CheckAuthorization(user1.CertHash, id))
}
......@@ -2,6 +2,7 @@ package contract_test
import (
"crypto/sha512"
"fmt"
"io/ioutil"
"path/filepath"
"testing"
......@@ -27,7 +28,7 @@ func createDataset() {
user1.Email = "user1@example.com"
user1.Expiration = time.Now().AddDate(1, 0, 0)
user1.Certificate = "Certificate1"
user1.CertHash = []byte{0x01}
_, _ = fmt.Sscanf("23a012afa19d5892f66ae9681afb3bb010e61c8bb4afdedd6a407fa40dbb7d4d1ad94953ca25866b6b07e25f8bf604cc94b13fb9dc1e7fa53980040db2a7f787", "%x", &user1.CertHash)
user2.Email = "user2@example.com"
user2.Expiration = time.Now().AddDate(1, 0, 0)
......
package contract
import (
n "net"
"dfss/auth"
"dfss/dfssp/api"
"dfss/dfssp/common"
"dfss/dfssp/entities"
"dfss/mgdb"
"dfss/net"
"gopkg.in/mgo.v2/bson"
)
// JoinSignature allows a client to wait for other clients connections on a specific contract.
// Firstly, every client present BEFORE the call of this function is sent to the stream.
// Then, client information is sent to the stream as it's available.
//
// Please note that the current user will also receive it's own information.
// There is no timeout, this function will shut down on stream disconnection or on error.
func JoinSignature(db *mgdb.MongoManager, rooms *common.WaitingGroupMap, in *api.JoinSignatureRequest, stream api.Platform_JoinSignatureServer) {
repository := entities.NewContractRepository(db.Get("contracts"))
ctx := stream.Context()
state, addr, _ := net.GetTLSState(&ctx)
hash := auth.GetCertificateHash(state.VerifiedChains[0][0])
if !bson.IsObjectIdHex(in.ContractUuid) {
_ = stream.Send(&api.UserConnected{
ErrorCode: &api.ErrorCode{
Code: api.ErrorCode_INVARG,
Message: "invalid contract uuid",
},
})
return
}
if !repository.CheckAuthorization(hash, bson.ObjectIdHex(in.ContractUuid)) {
_ = stream.Send(&api.UserConnected{
ErrorCode: &api.ErrorCode{
Code: api.ErrorCode_INVARG,
Message: "unauthorized signature",
},
})
return
}
// Join room
roomID := "contract_" + in.ContractUuid
channel, pendingSigners := rooms.Join(roomID)
// Send pendingSigners
for _, p := range pendingSigners {
err := sendUserToStream(&stream, in.ContractUuid, p.(*api.User))
if err != nil {
rooms.Unjoin(roomID, channel)
return
}
}
// Broadcast self identity
host, _, _ := n.SplitHostPort(addr.String())
rooms.Broadcast(roomID, &api.User{
KeyHash: hash,
Email: net.GetCN(&ctx),
Ip: host,
Port: in.Port,
})
// Listen for others
for {
select {
case user, ok := <-channel:
if !ok { // channel is closed, means that the room is closed
return
}
err := sendUserToStream(&stream, in.ContractUuid, user.(*api.User))
if err != nil {
rooms.Unjoin(roomID, channel)
return
}
case <-ctx.Done():
rooms.Unjoin(roomID, channel)
return
}
}
}
func sendUserToStream(stream *api.Platform_JoinSignatureServer, contractUUID string, user *api.User) error {
return (*stream).Send(&api.UserConnected{
ErrorCode: &api.ErrorCode{Code: api.ErrorCode_SUCCESS},
ContractUuid: contractUUID,
User: user,
})
}
package contract_test
import (
"testing"
"dfss/dfssp/api"
"dfss/dfssp/entities"
"github.com/bmizerany/assert"
"golang.org/x/net/context"
"gopkg.in/mgo.v2/bson"
)
func addTestContract() bson.ObjectId {
contract := entities.NewContract()
contract.AddSigner(&user1.ID, user1.Email, user1.CertHash)
contract.Ready = true
_, _ = manager.Get("contracts").Insert(contract)
return contract.ID
}
func TestJoinSignature(t *testing.T) {
dropDataset()
createDataset()
contractID := addTestContract()
client := clientTest(t)
stream, err := client.JoinSignature(context.Background(), &api.JoinSignatureRequest{
ContractUuid: contractID.Hex(),
Port: 5050,
})
assert.Equal(t, nil, err)
user, err := stream.Recv()
assert.Equal(t, nil, err)
assert.Equal(t, "", user.ErrorCode.Message)
assert.Equal(t, api.ErrorCode_SUCCESS, user.ErrorCode.Code)
assert.Equal(t, contractID.Hex(), user.ContractUuid)
assert.Equal(t, "test@test.com", user.User.Email)
assert.Equal(t, "127.0.0.1", user.User.Ip)
assert.Equal(t, uint32(5050), user.User.Port)
}
func TestJoinSignatureBadContract(t *testing.T) {
dropDataset()
createDataset()
client := clientTest(t)
stream, err := client.JoinSignature(context.Background(), &api.JoinSignatureRequest{
ContractUuid: bson.NewObjectId().Hex(),
Port: 5050,
})
assert.Equal(t, nil, err)
user, err := stream.Recv()
assert.Equal(t, nil, err)
assert.Equal(t, "unauthorized signature", user.ErrorCode.Message)
assert.Equal(t, api.ErrorCode_INVARG, user.ErrorCode.Code)
}
......@@ -83,3 +83,17 @@ func (r *ContractRepository) GetWaitingForUser(email string) ([]Contract, error)
}, &res)
return res, err
}
// CheckAuthorization checks that a client is allowed to sign a specific contract
func (r *ContractRepository) CheckAuthorization(signerHash []byte, contractID bson.ObjectId) bool {
count, _ := r.Collection.Collection.Find(bson.M{
"_id": contractID,
"ready": true,
"signers": bson.M{
"$elemMatch": bson.M{"hash": signerHash},
},
}).Count()
return count == 1
}
......@@ -6,6 +6,7 @@ import (
"dfss/dfssp/api"
"dfss/dfssp/authority"
"dfss/dfssp/common"
"dfss/dfssp/contract"
"dfss/dfssp/user"
"dfss/mgdb"
......@@ -17,6 +18,7 @@ import (
type platformServer struct {
Pid *authority.PlatformID
DB *mgdb.MongoManager
Rooms *common.WaitingGroupMap
CertDuration int
Verbose bool
}
......@@ -61,7 +63,17 @@ func (s *platformServer) PostContract(ctx context.Context, in *api.PostContractR
//
// Handle incoming JoinSignatureRequest messages
func (s *platformServer) JoinSignature(in *api.JoinSignatureRequest, stream api.Platform_JoinSignatureServer) error {
// TODO
ctx := stream.Context()
cn := net.GetCN(&ctx)
if len(cn) == 0 {
_ = stream.Send(&api.UserConnected{
ErrorCode: &api.ErrorCode{Code: api.ErrorCode_BADAUTH},
})
return nil
}
contract.JoinSignature(s.DB, s.Rooms, in, stream)
return nil
}
......@@ -91,6 +103,7 @@ func GetServer(keyPath, db string, certValidity int, verbose bool) *grpc.Server
api.RegisterPlatformServer(server, &platformServer{
Pid: pid,
DB: dbManager,
Rooms: common.NewWaitingGroupMap(),
CertDuration: certValidity,
Verbose: verbose,
})
......
......@@ -56,18 +56,18 @@ func Listen(addrPort string, grpcServer *grpc.Server) error {
// GetTLSState returns the current tls connection state from a grpc context.
// If you just need to check that the connected peer provides its certificate, use `GetCN`.
func GetTLSState(ctx *context.Context) (tls.ConnectionState, bool) {
func GetTLSState(ctx *context.Context) (tls.ConnectionState, net.Addr, bool) {
p, ok := peer.FromContext(*ctx)
if !ok {
return tls.ConnectionState{}, false
return tls.ConnectionState{}, nil, false
}
return p.AuthInfo.(credentials.TLSInfo).State, true
return p.AuthInfo.(credentials.TLSInfo).State, p.Addr, true
}
// GetCN returns the current common name of connected peer from grpc context.
// The returned string is empty if encountering a non-auth peer.
func GetCN(ctx *context.Context) string {
state, ok := GetTLSState(ctx)
state, _, ok := GetTLSState(ctx)
if !ok || len(state.VerifiedChains) == 0 {
return ""
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment