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

Merge branch '186_integration_tests' into 'master'

186 integration tests

- Client now exits on error
- Platform now logs mail errors without noticing the client. We consider that a mail problem is not fatal

See merge request !32
parents 9b42fc4c 024d9bcf
Pipeline #315 passed with stages
...@@ -38,26 +38,18 @@ Unit tests: ...@@ -38,26 +38,18 @@ Unit tests:
- "grep -h -v 'mode: set' *part >> c.out" - "grep -h -v 'mode: set' *part >> c.out"
- "go tool cover -html=c.out -o coverage.html" - "go tool cover -html=c.out -o coverage.html"
ARM tests: Integration tests:
stage: test stage: test
tags: tags:
- arm - golang
- mongo
services:
- "lesterpig/mongo:latest"
script: script:
- "ln -s -f $(pwd) $GOPATH/src/dfss" - "ln -s -f $(pwd) $GOPATH/src/dfss"
- "./build/deps.sh" - "./build/deps.sh"
- "cd $GOPATH/src/dfss && go install ./..." - "cd $GOPATH/src/dfss && go install ./..."
- "go test -cover -short -v dfss/auth" - "go test -v dfss/tests"
- "go test -cover -short -v dfss/mgdb"
- "go test -cover -short -v dfss/net"
- "go test -cover -short -v dfss/dfssp/authority"
- "go test -cover -short -v dfss/dfssp/user"
- "go test -cover -short -v dfss/dfssp/contract"
- "go test -cover -short -v dfss/dfssp/templates"
- "go test -cover -short -v dfss/dfssd"
- "go test -cover -short -v dfss/dfssc/common"
- "go test -cover -short -v dfss/dfssc/security"
- "go test -cover -short -v dfss/dfssc/user"
- "go test -cover -short -v dfss/dfssc/sign"
Code lint: Code lint:
stage: test stage: test
......
...@@ -2,8 +2,19 @@ DFSS ...@@ -2,8 +2,19 @@ DFSS
==== ====
> Distributed Fair Signing System > Distributed Fair Signing System
Installation Prerequisites
------------ -------------
DFSS can run on the following platforms:
- Linux (amd64, i386 and arm)
- MacOS (amd64 and i386)
- Windows (amd64 and i386)
A running mongoDB database is required for the Platform module.
Installation (UNIX)
-------------------
This archive contains all the needed DFSS modules to run a distributed multiparty signature. This archive contains all the needed DFSS modules to run a distributed multiparty signature.
You just have to untar the archive and run the following binaries: You just have to untar the archive and run the following binaries:
...@@ -13,3 +24,55 @@ You just have to untar the archive and run the following binaries: ...@@ -13,3 +24,55 @@ You just have to untar the archive and run the following binaries:
./dfssp help # Platform ./dfssp help # Platform
./dfssd help # Demonstrator ./dfssd help # Demonstrator
``` ```
Here is a basic tutorial to setup a new DFSS environment.
### Setup platform
The first thing to do is to create the *root certificate of authentication* for the platform.
You can configure several parameters for that (check the `help` command of `dfssp`).
For instance, if we are running the plaform on the `example.com` host:
```bash
./dfssp -cn example.com -country FR -rootValidity 3650 init
```
You can then start the platform. Here we are considering a mongoDB database running on the same host.
Firstly, we have to configure several environment variables to set smtp server configuration (mails):
```bash
export DFSS_MAIL_SENDER="mailer@example.com"
export DFSS_MAIL_HOST="smtp.example.com"
export DFSS_MAIL_PORT="587"
export DFSS_MAIL_USERNAME="mailer"
export DFSS_MAIL_PASSWORD="password"
```
Then:
```bash
./dfssp start
```
### Setup clients
Each client needs the `dfssp_rootCA.pem` file in order to connect to the platform in a secure way.
Clients can then register on the platform with the following command:
```bash
./dfssc -ca path/to/dfssp_rootCA.pem -host example.com register
```
A mail will be sent to the user containing a unique token. Use this token to authenticate onto the platform:
```bash
./dfssc -ca path/to/dfssp_rootCA.pem -host example.com auth
```
When this is done, the client will have a certificate and a private key in the current directory.
It's then possible to send new contracts to the platform:
```bash
./dfssc -ca path/to/dfssp_rootCA.pem -host example.com new
```
...@@ -2,6 +2,7 @@ package main ...@@ -2,6 +2,7 @@ package main
import ( import (
"fmt" "fmt"
"os"
"dfss/dfssc/user" "dfss/dfssc/user"
) )
...@@ -16,5 +17,6 @@ func authUser() { ...@@ -16,5 +17,6 @@ func authUser() {
err := user.Authenticate(fca, fcert, addrPort, mail, token) err := user.Authenticate(fca, fcert, addrPort, mail, token)
if err != nil { if err != nil {
fmt.Println("An error occurred : ", err.Error()) fmt.Println("An error occurred : ", err.Error())
os.Exit(3)
} }
} }
...@@ -2,6 +2,7 @@ package main ...@@ -2,6 +2,7 @@ package main
import ( import (
"fmt" "fmt"
"os"
"dfss/dfssc/sign" "dfss/dfssc/sign"
) )
...@@ -12,7 +13,8 @@ func newContract() { ...@@ -12,7 +13,8 @@ func newContract() {
passphrase, filepath, comment, signers := getContractInfo() passphrase, filepath, comment, signers := getContractInfo()
err := sign.NewCreateManager(fca, fcert, fkey, addrPort, passphrase, filepath, comment, signers) err := sign.NewCreateManager(fca, fcert, fkey, addrPort, passphrase, filepath, comment, signers)
if err != nil { if err != nil {
fmt.Println(err) fmt.Fprintln(os.Stderr, err)
os.Exit(1)
} }
} }
......
...@@ -35,6 +35,7 @@ func registerUser() { ...@@ -35,6 +35,7 @@ func registerUser() {
err = readPassword(&passphrase, true) err = readPassword(&passphrase, true)
if err != nil { if err != nil {
fmt.Println("An error occurred:", err.Error()) fmt.Println("An error occurred:", err.Error())
os.Exit(1)
return return
} }
...@@ -42,9 +43,13 @@ func registerUser() { ...@@ -42,9 +43,13 @@ func registerUser() {
err = user.Register(fca, fcert, fkey, addrPort, passphrase, country, organization, unit, mail, bits) err = user.Register(fca, fcert, fkey, addrPort, passphrase, country, organization, unit, mail, bits)
if err != nil { if err != nil {
fmt.Println("An error occurred:", err.Error()) fmt.Println("An error occurred:", err.Error())
os.Exit(2)
} }
} }
// We need to use ONLY ONE reader: buffio buffers some data (= consumes from stdin)
var reader *bufio.Reader
// Get a string parameter from standard input // Get a string parameter from standard input
func readStringParam(message, def string, ptr *string) { func readStringParam(message, def string, ptr *string) {
fmt.Print(message) fmt.Print(message)
...@@ -53,7 +58,9 @@ func readStringParam(message, def string, ptr *string) { ...@@ -53,7 +58,9 @@ func readStringParam(message, def string, ptr *string) {
} }
fmt.Print(": ") fmt.Print(": ")
reader := bufio.NewReader(os.Stdin) if reader == nil {
reader = bufio.NewReader(os.Stdin)
}
value, _ := reader.ReadString('\n') value, _ := reader.ReadString('\n')
// Trim newline symbols // Trim newline symbols
......
...@@ -42,10 +42,11 @@ func checkRegisterRequest(in *api.RegisterRequest) *api.ErrorCode { ...@@ -42,10 +42,11 @@ func checkRegisterRequest(in *api.RegisterRequest) *api.ErrorCode {
// Send the verification email in response to the specified registration request // Send the verification email in response to the specified registration request
// //
// This method should only be called AFTER checking the RegisterRequest for validity // This method should only be called AFTER checking the RegisterRequest for validity
func sendVerificationMail(in *api.RegisterRequest, token string) error { func sendVerificationMail(in *api.RegisterRequest, token string) {
conn := templates.MailConn() conn := templates.MailConn()
if conn == nil { if conn == nil {
return errors.New("Couldn't connect to the dfssp mail server") log.Println("Couldn't connect to the dfssp mail server")
return
} }
defer func() { _ = conn.Close() }() defer func() { _ = conn.Close() }()
...@@ -54,7 +55,8 @@ func sendVerificationMail(in *api.RegisterRequest, token string) error { ...@@ -54,7 +55,8 @@ func sendVerificationMail(in *api.RegisterRequest, token string) error {
mail := templates.VerificationMail{Token: token} mail := templates.VerificationMail{Token: token}
content, err := templates.Get("verificationMail", mail) content, err := templates.Get("verificationMail", mail)
if err != nil { if err != nil {
return err log.Println(err)
return
} }
err = conn.Send( err = conn.Send(
...@@ -66,10 +68,9 @@ func sendVerificationMail(in *api.RegisterRequest, token string) error { ...@@ -66,10 +68,9 @@ func sendVerificationMail(in *api.RegisterRequest, token string) error {
nil, nil,
) )
if err != nil { if err != nil {
return err log.Println(err)
return
} }
return nil
} }
// Register checks if the registration request is valid, and if so, // Register checks if the registration request is valid, and if so,
...@@ -117,10 +118,7 @@ func Register(manager *mgdb.MongoManager, in *api.RegisterRequest) (*api.ErrorCo ...@@ -117,10 +118,7 @@ func Register(manager *mgdb.MongoManager, in *api.RegisterRequest) (*api.ErrorCo
} }
// Sending the email // Sending the email
err = sendVerificationMail(in, token) sendVerificationMail(in, token)
if err != nil {
return &api.ErrorCode{Code: api.ErrorCode_INTERR, Message: "Error during the sending of the email"}, err
}
return &api.ErrorCode{Code: api.ErrorCode_SUCCESS, Message: "Registration successful ; email sent"}, nil return &api.ErrorCode{Code: api.ErrorCode_SUCCESS, Message: "Registration successful ; email sent"}, nil
} }
......
package tests
import (
"fmt"
"os"
"testing"
"dfss/mgdb"
)
func TestMain(m *testing.M) {
dbURI = os.Getenv("DFSS_MONGO_URI")
if dbURI == "" {
dbURI = "mongodb://localhost/dfss-test"
}
var err error
dbManager, err = mgdb.NewManager(dbURI)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
code := m.Run()
eraseDatabase()
os.Exit(code)
}
package tests
import (
"dfss/dfssp/entities"
"dfss/mgdb"
"gopkg.in/mgo.v2/bson"
)
var dbURI string
var dbManager *mgdb.MongoManager
func eraseDatabase() {
_ = dbManager.Database.DropDatabase()
}
func getRegistrationToken(mail string) string {
var user entities.User
_ = dbManager.Get("users").Collection.Find(bson.M{
"email": mail,
}).One(&user)
if len(user.RegToken) == 0 {
return "badToken"
}
return user.RegToken
}
func getContract(file string, skip int) *entities.Contract {
var contract entities.Contract
_ = dbManager.Get("contracts").Collection.Find(bson.M{
"file.name": file,
}).Sort("_id").Skip(skip).One(&contract)
return &contract
}
package tests
// A test package, see test files.
package tests
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/bmizerany/assert"
)
// TestNewContract tries to creates new contracts on the platform.
//
// CLASSIC SCENARIO
// 1. client1 registers on the platform
// 2. client1 sends a new contract on the platform, but client2 is not here yet
// 3. client2 registers on the platform
// 4. client2 sends a new contract on the platform, and everyone is here
//
// BAD CASES
// 1. client2 sends a new contract with a wrong password
// 2. client3 sends a new contract without authentication
// 3. client1 sends a new contract with an invalid filepath
func TestNewContract(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 client1
client1, err := createClient(workingDir, ca)
assert.Equal(t, nil, err)
err = registerAndAuth(client1, "client1@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" +
"A very nice comment\n" +
"client1@example.com\n" +
"client2@example.com\n" +
"\n",
)
err = checkStderr(t, client1, "Operation succeeded with a warning message: Some users are not ready yet\n")
assert.NotEqual(t, nil, err)
// Check database
contract := getContract("contract.txt", 0)
assert.Equal(t, false, contract.Ready)
assert.Equal(t, "A very nice comment", contract.Comment)
assert.Equal(t, "6a95f6bcd6282186a7b1175fbaab4809ca5f665f7c4d55675de2399c83e67252069d741a88c766b1a79206d6dfbd5552cd7f9bc69b43bee161d1337228b4a4a8", contract.File.Hash)
assert.Equal(t, 2, len(contract.Signers))
assert.Equal(t, "client1@example.com", contract.Signers[0].Email)
assert.Equal(t, "client2@example.com", contract.Signers[1].Email)
assert.T(t, len(contract.Signers[0].Hash) > 0)
assert.T(t, len(contract.Signers[1].Hash) == 0)
// Register second signer
client2, err := createClient(workingDir, ca)
assert.Equal(t, nil, err)
err = registerAndAuth(client2, "client2@example.com", "password2", "", true, true)
assert.Equal(t, nil, err)
// Check database²
contract = getContract("contract.txt", 0)
assert.Equal(t, true, contract.Ready)
assert.T(t, len(contract.Signers[0].Hash) > 0)
assert.T(t, len(contract.Signers[1].Hash) > 0)
// Create a second contract
client2 = newClient(client2)
setLastArg(client2, "new", true)
client2.Stdin = strings.NewReader(
"password2\n" +
filepath.Join("testdata", "contract.txt") + "\n" +
"Another comment with some accents héhé\n" +
"client1@example.com\n" +
"client2@example.com\n" +
"\n",
)
err = checkStderr(t, client2, "")
assert.Equal(t, nil, err)
// Check database³
contract = getContract("contract.txt", 1)
assert.Equal(t, true, contract.Ready)
assert.Equal(t, "Another comment with some accents héhé", contract.Comment)
assert.T(t, len(contract.Signers[0].Hash) > 0)
assert.T(t, len(contract.Signers[1].Hash) > 0)
// Bad case: wrong password
client2 = newClient(client2)
setLastArg(client2, "new", true)
client2.Stdin = strings.NewReader(
"wrongPwd\n" +
filepath.Join("testdata", "contract.txt") + "\n" +
"\n" +
"client1@example.com\n" +
"client2@example.com\n" +
"\n",
)
err = checkStderr(t, client2, "x509: decryption password incorrect\n")
assert.NotEqual(t, nil, err)
// Bad case: no authentication
client3, err := createClient(workingDir, ca)
setLastArg(client3, "new", false)
client3.Stdin = strings.NewReader(
"\n" +
filepath.Join("testdata", "contract.txt") + "\n" +
"\n" +
"client1@example.com\n" +
"\n",
)
err = client3.Run()
assert.NotEqual(t, nil, err)
// Bad case: bad filepath
client1 = newClient(client1)
setLastArg(client1, "new", true)
client1.Stdin = strings.NewReader(
"password\n" +
"invalidFile\n" +
"client1@example.com\n" +
"\n",
)
err = checkStderr(t, client1, "open invalidFile: no such file or directory\n")
assert.NotEqual(t, nil, err)
// Check number of stored contracts
assert.Equal(t, 2, dbManager.Get("contracts").Count())
}
package tests
import (
"io/ioutil"
"os"
"os/exec"
"strings"
"testing"
"time"
"github.com/bmizerany/assert"
)
// TestRegisterAuth tries to register and auth several users.
//
// GOOD CASES
// client1 : test@example.com with password
// client2 : test@example.com without password
//
// BAD CASES
// client3 : test@example.com
// client4 : wrong mail
// client5 : wrong key size
// client6 : wrong mail during auth
// client7 : wrong token during auth
//
// TODO Add expired accounts test
// TODO Add Stderr test
func TestRegisterAuth(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 client1
client1, err := createClient(workingDir, ca)
assert.Equal(t, nil, err)
err = registerAndAuth(client1, "test@example.com", "password", "", true, true)
assert.Equal(t, nil, err)
// Register client2
client2, err := createClient(workingDir, ca)
assert.Equal(t, nil, err)
err = registerAndAuth(client2, "test2@example.com", "", "2048", true, true)
assert.Equal(t, nil, err)
// Register client3
client3, err := createClient(workingDir, ca)
assert.Equal(t, nil, err)
err = registerAndAuth(client3, "test@example.com", "", "", true, true)
assert.NotEqual(t, nil, err)
// Register client4
client4, err := createClient(workingDir, ca)
assert.Equal(t, nil, err)
err = registerAndAuth(client4, "test wrong mail", "", "", true, true)
assert.NotEqual(t, nil, err)
// Register client5
client5, err := createClient(workingDir, ca)
assert.Equal(t, nil, err)
err = registerAndAuth(client5, "wrong@key.fr", "", "1024", true, true)
assert.NotEqual(t, nil, err)
// Register client6
client6, err := createClient(workingDir, ca)
assert.Equal(t, nil, err)
err = registerAndAuth(client6, "bad@auth.com", "", "", false, true)
assert.NotEqual(t, nil, err)
// Register client7
client7, err := createClient(workingDir, ca)
assert.Equal(t, nil, err)
err = registerAndAuth(client7, "bad@auth2.com", "", "", true, false)
assert.NotEqual(t, nil, err)
}
func registerAndAuth(client *exec.Cmd, mail, password, keySize string, authMail, authToken bool) error {
setLastArg(client, "register", false)
client.Stdin = strings.NewReader(
mail + "\n" +
"FR\n" +
"TEST\n" +
"TEST\n" +
keySize + "\n" +
password + "\n" +
password + "\n",
)
err := client.Run()
if err != nil {
return err
}
if !authMail { // simulates wrong mail
mail = "very@badmail.com"
}