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

[c] Add the beginning of signature

parent d8ee0857
......@@ -8,11 +8,12 @@ import (
)
var (
verbose bool
fca string // Path to the CA
fcert string // Path to the certificate
fkey string // Path to the private key
addrPort string // Address and port of the platform
verbose bool
fca string // Path to the CA
fcert string // Path to the certificate
fkey string // Path to the private key
addrPort string // Address and port of the platform
localPort int // Port to open for P2P communication
)
func init() {
......@@ -22,6 +23,7 @@ func init() {
flag.StringVar(&fcert, "cert", "cert.pem", "Path to the user certificate")
flag.StringVar(&fkey, "key", "key.pem", "Path to the private key")
flag.StringVar(&addrPort, "host", "localhost:9000", "Host of the DFSS platform")
flag.IntVar(&localPort, "port", 9005, "Port to use for P2P communication between clients")
flag.Usage = func() {
fmt.Println("DFSS client command line v" + dfss.Version)
......@@ -39,6 +41,7 @@ func init() {
fmt.Println(" show <c> print contract information from file c")
fmt.Println(" export <c> export certificate and private key of the user to file c")
fmt.Println(" import <c> import private key and certificate from file c")
fmt.Println(" sign <c> sign contract from file c")
fmt.Println("\nFlags:")
flag.PrintDefaults()
......@@ -67,6 +70,8 @@ func main() {
exportConf(flag.Arg(1))
case "import":
importConf(flag.Arg(1))
case "sign":
signContract(flag.Arg(1))
default:
flag.Usage()
}
......
package security
import (
"crypto/rsa"
"crypto/x509"
)
// AuthContainer contains common information for TLS authentication.
// Files are not loaded from the beginning, call LoadFiles to load them.
type AuthContainer struct {
FileCA string
FileCert string
FileKey string
AddrPort string
Passphrase string
CA *x509.Certificate
Cert *x509.Certificate
Key *rsa.PrivateKey
}
// NewAuthContainer is a shortcut to build a AuthContainer
func NewAuthContainer(fileCA, fileCert, fileKey, addrPort, passphrase string) *AuthContainer {
return &AuthContainer{
FileCA: fileCA,
FileCert: fileCert,
FileKey: fileKey,
AddrPort: addrPort,
Passphrase: passphrase,
}
}
// LoadFiles tries to load the required certificates and key for TLS authentication
func (a *AuthContainer) LoadFiles() (ca *x509.Certificate, cert *x509.Certificate, key *rsa.PrivateKey, err error) {
ca, err = GetCertificate(a.FileCA)
if err != nil {
return
}
cert, err = GetCertificate(a.FileCert)
if err != nil {
return
}
key, err = GetPrivateKey(a.FileKey, a.Passphrase)
a.CA = ca
a.Cert = cert
a.Key = key
return
}
......@@ -7,6 +7,7 @@ import (
"text/template"
"dfss/dfssc/common"
"dfss/dfssp/contract"
)
const contractShowTemplate = `UUID : {{.UUID}}
......@@ -21,24 +22,24 @@ Signers :
{{range .Signers}} - {{.Email}}
{{end}}`
func showContract(filename string) {
func showContract(filename string) *contract.JSON {
data, err := ioutil.ReadFile(filename)
if err != nil {
fmt.Println("Cannot open file:", err)
return
return nil
}
c, err := common.UnmarshalDFSSFile(data)
if err != nil {
fmt.Println("Corrupted file:", err)
return
return nil
}
tmpl, err := template.New("contract").Parse(contractShowTemplate)
if err != nil {
fmt.Println("Internal error:", err)
return
return nil
}
b := new(bytes.Buffer)
......@@ -48,5 +49,5 @@ func showContract(filename string) {
}
fmt.Print(b.String())
return c
}
package main
import (
"fmt"
"os"
"dfss/dfssc/sign"
)
func signContract(filename string) {
fmt.Println("You are going to sign the following contract:")
contract := showContract(filename)
if contract == nil {
os.Exit(1)
}
var passphrase string
_ = readPassword(&passphrase, false)
// Preparation
manager, err := sign.NewSignatureManager(fca, fcert, fkey, addrPort, passphrase, localPort, contract)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(2)
}
err = manager.ConnectToPeers()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(3)
}
// Confirmation
var ready string
readStringParam("Do you REALLY want to sign "+contract.File.Name+"? Type 'yes' to confirm", "", &ready)
if ready != "yes" {
os.Exit(4)
}
// Ignition
// Signature
}
......@@ -14,32 +14,22 @@ import (
)
// CreateManager handles the creation of a new contract.
//
// TODO create a specific structure containing crypto information
type CreateManager struct {
fileCA string
fileCert string
fileKey string
addrPort string
passphrase string
filepath string
comment string
signers []string
hash []byte
filename string
auth *security.AuthContainer
filepath string
comment string
signers []string
hash []byte
filename string
}
// NewCreateManager tries to create a contract on the platform and returns an error or nil
func NewCreateManager(fileCA, fileCert, fileKey, addrPort, passphrase, filepath, comment string, signers []string) error {
m := &CreateManager{
fileCA: fileCA,
fileCert: fileCert,
fileKey: fileKey,
addrPort: addrPort,
passphrase: passphrase,
filepath: filepath,
comment: comment,
signers: signers,
auth: security.NewAuthContainer(fileCA, fileCert, fileKey, addrPort, passphrase),
filepath: filepath,
comment: comment,
signers: signers,
}
err := m.computeFile()
......@@ -71,22 +61,13 @@ func (m *CreateManager) computeFile() error {
// sendRequest sends a new contract request for the platform and send it
func (m *CreateManager) sendRequest() (*api.ErrorCode, error) {
ca, err := security.GetCertificate(m.fileCA)
if err != nil {
return nil, err
}
cert, err := security.GetCertificate(m.fileCert)
if err != nil {
return nil, err
}
key, err := security.GetPrivateKey(m.fileKey, m.passphrase)
ca, cert, key, err := m.auth.LoadFiles()
if err != nil {
return nil, err
}
conn, err := net.Connect(m.addrPort, cert, key, ca)
conn, err := net.Connect(m.auth.AddrPort, cert, key, ca)
if err != nil {
return nil, err
}
......
package sign
import (
"errors"
"fmt"
"strconv"
"dfss/dfssc/security"
"dfss/dfssp/api"
"dfss/dfssp/contract"
"dfss/net"
"golang.org/x/net/context"
)
// SignatureManager handles the signature of a contract.
type SignatureManager struct {
auth *security.AuthContainer
localPort int
contract *contract.JSON
platform api.PlatformClient
peers map[string]*api.User
}
// NewSignatureManager populates a SignatureManager and connects to the platform.
func NewSignatureManager(fileCA, fileCert, fileKey, addrPort, passphrase string, port int, c *contract.JSON) (*SignatureManager, error) {
m := &SignatureManager{
auth: security.NewAuthContainer(fileCA, fileCert, fileKey, addrPort, passphrase),
localPort: port,
contract: c,
}
ca, cert, key, err := m.auth.LoadFiles()
if err != nil {
return nil, err
}
conn, err := net.Connect(m.auth.AddrPort, cert, key, ca)
if err != nil {
return nil, err
}
m.platform = api.NewPlatformClient(conn)
m.peers = make(map[string]*api.User)
for _, u := range c.Signers {
m.peers[u.Email] = nil
}
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 {
stream, err := m.platform.JoinSignature(context.Background(), &api.JoinSignatureRequest{
ContractUuid: m.contract.UUID,
Port: uint32(m.localPort),
})
if err != nil {
return err
}
for {
userConnected, err := stream.Recv()
if err != nil {
return err
}
errorCode := userConnected.GetErrorCode()
if errorCode.Code != api.ErrorCode_SUCCESS {
return errors.New(errorCode.Message)
}
ready, err := m.addPeer(userConnected.User)
if err != nil {
return err
}
if ready {
break
}
}
return nil
}
// addPeer stores a peer from the platform and tries to establish a connection to this peer.
func (m *SignatureManager) addPeer(user *api.User) (ready bool, err error) {
if user == nil {
err = errors.New("unexpected user format")
return
}
m.peers[user.Email] = user
fmt.Println("Trying to connect with", user.Email, "/", user.Ip+":"+strconv.Itoa(int(user.Port)))
// TODO do the connection
// Check if we have any other peer to connect to
for _, u := range m.peers {
if u == nil {
return
}
}
ready = true
return
}
......@@ -2,6 +2,7 @@ package contract
import (
n "net"
"time"
"dfss/auth"
"dfss/dfssp/api"
......@@ -19,28 +20,11 @@ import (
// 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",
},
})
if !checkJoinSignatureRequest(db, &stream, in.ContractUuid, hash) {
return
}
......@@ -70,7 +54,7 @@ func JoinSignature(db *mgdb.MongoManager, rooms *common.WaitingGroupMap, in *api
for {
select {
case user, ok := <-channel:
if !ok { // channel is closed, means that the room is closed
if !ok { // Channel is closed, means that the room is closed
return
}
err := sendUserToStream(&stream, in.ContractUuid, user.(*api.User))
......@@ -78,13 +62,40 @@ func JoinSignature(db *mgdb.MongoManager, rooms *common.WaitingGroupMap, in *api
rooms.Unjoin(roomID, channel)
return
}
case <-ctx.Done():
case <-ctx.Done(): // Disconnect
rooms.Unjoin(roomID, channel)
return
case <-time.After(time.Hour): // Timeout
rooms.Unjoin(roomID, channel)
return
}
}
}
func checkJoinSignatureRequest(db *mgdb.MongoManager, stream *api.Platform_JoinSignatureServer, contractUUID string, clientHash []byte) bool {
if !bson.IsObjectIdHex(contractUUID) {
_ = (*stream).Send(&api.UserConnected{
ErrorCode: &api.ErrorCode{
Code: api.ErrorCode_INVARG,
Message: "invalid contract uuid",
},
})
return false
}
repository := entities.NewContractRepository(db.Get("contracts"))
if !repository.CheckAuthorization(clientHash, bson.ObjectIdHex(contractUUID)) {
_ = (*stream).Send(&api.UserConnected{
ErrorCode: &api.ErrorCode{
Code: api.ErrorCode_INVARG,
Message: "unauthorized signature",
},
})
return false
}
return true
}
func sendUserToStream(stream *api.Platform_JoinSignatureServer, contractUUID string, user *api.User) error {
return (*stream).Send(&api.UserConnected{
ErrorCode: &api.ErrorCode{Code: api.ErrorCode_SUCCESS},
......
package tests
import (
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
"dfss/dfssp/contract"
"github.com/bmizerany/assert"
)
func TestSignContract(t *testing.T) {
// Cleanup
eraseDatabase()
// Start the platform
workingDir, err := ioutil.TempDir("", "dfss_")
assert.Equal(t, nil, err)
platform, ca, err := startPlatform(workingDir)
assert.Equal(t, nil, err)
defer func() {
_ = platform.Process.Kill()
_ = os.RemoveAll(workingDir)
}()
time.Sleep(2 * time.Second)
// Register clients
clients := make([]*exec.Cmd, 3)
client1, err := createClient(workingDir, ca)
assert.Equal(t, nil, err)
err = registerAndAuth(client1, "client1@example.com", "password", "", true, true)
assert.Equal(t, nil, err)
client2, err := createClient(workingDir, ca)
assert.Equal(t, nil, err)
err = registerAndAuth(client2, "client2@example.com", "password", "", true, true)
assert.Equal(t, nil, err)
client3, err := createClient(workingDir, ca)
assert.Equal(t, nil, err)
err = registerAndAuth(client3, "client3@example.com", "password", "", true, true)
assert.Equal(t, nil, err)
// Create contract
client1 = newClient(client1)
setLastArg(client1, "new", true)
client1.Stdin = strings.NewReader(
"password\n" +
filepath.Join("testdata", "contract.txt") + "\n" +
"\n" +
"client1@example.com\n" +
"client2@example.com\n" +
"client3@example.com\n" +
"\n",
)
err = checkStderr(t, client1, "")
assert.Equal(t, nil, err)
// Get contract file
contractEntity := getContract("contract.txt", 0)
contractData, err := contract.GetJSON(contractEntity, nil)
assert.Equal(t, nil, err)
contractPath := filepath.Join(workingDir, "c.dfss")
err = ioutil.WriteFile(contractPath, contractData, os.ModePerm)
assert.Equal(t, nil, err)
// Sign!
clients[0] = newClient(client1)
clients[1] = newClient(client2)
clients[2] = newClient(client3)
closeChannel := make(chan error, 3)
for i := 0; i < 3; i++ {
setLastArg(clients[i], "sign", true)
setLastArg(clients[i], contractPath, false)
go func(c *exec.Cmd, i int) {
time.Sleep(time.Duration(i*2) * time.Second)
c.Stdin = strings.NewReader("password\nyes\n")
closeChannel <- c.Run()
}(clients[i], i)
}
for i := 0; i < 3; i++ {
assert.Equal(t, nil, <-closeChannel)
}
}
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