Commit 5d2ebd9f authored by Caro Axel's avatar Caro Axel

[t][c] Add recover procedure

parent 3ee03370
Pipeline #2308 passed with stage
package cmd
import (
"fmt"
"os"
"dfss/dfssc/sign"
"github.com/spf13/cobra"
)
var recoverCmd = &cobra.Command{
Use: "recover <f>",
Short: "try to recover signed contract from recover data file f",
Run: recover,
}
func recover(cmd *cobra.Command, args []string) {
if len(args) != 1 {
_ = cmd.Usage()
return
}
var passphrase string
_ = readPassword(&passphrase, false)
filename := args[0]
err := sign.Recover(filename, passphrase)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
return
}
fmt.Println("Successfully recovered signed contract.")
fmt.Println("Check .proof file.")
}
...@@ -58,6 +58,5 @@ func init() { ...@@ -58,6 +58,5 @@ func init() {
_ = viper.BindPFlag("timeout", RootCmd.PersistentFlags().Lookup("timeout")) _ = viper.BindPFlag("timeout", RootCmd.PersistentFlags().Lookup("timeout"))
// Bind subcommands to root // Bind subcommands to root
RootCmd.AddCommand(dfss.VersionCmd, registerCmd, authCmd, newCmd, showCmd, fetchCmd, importCmd, exportCmd, signCmd, unregisterCmd) RootCmd.AddCommand(dfss.VersionCmd, registerCmd, authCmd, newCmd, showCmd, fetchCmd, importCmd, exportCmd, signCmd, unregisterCmd, recoverCmd)
} }
...@@ -74,6 +74,13 @@ var signCmd = &cobra.Command{ ...@@ -74,6 +74,13 @@ var signCmd = &cobra.Command{
// TODO Warning, integration tests are checking Stdout // TODO Warning, integration tests are checking Stdout
fmt.Println("Everybody is ready, starting the signature", signatureUUID) fmt.Println("Everybody is ready, starting the signature", signatureUUID)
// Persisting the signatureUUID and ttp addrport in case of a crash, to be able to recover
filename, err = manager.PersistRecoverDataToFile()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
// Signature // Signature
manager.OnProgressUpdate = signProgressFn manager.OnProgressUpdate = signProgressFn
err = manager.Sign() err = manager.Sign()
...@@ -83,6 +90,13 @@ var signCmd = &cobra.Command{ ...@@ -83,6 +90,13 @@ var signCmd = &cobra.Command{
} }
fmt.Println("Signature complete! See .proof file for evidences.") fmt.Println("Signature complete! See .proof file for evidences.")
// deleting the recover data file
err = os.Remove(filename)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}, },
} }
......
...@@ -9,7 +9,6 @@ import ( ...@@ -9,7 +9,6 @@ import (
// UnmarshalDFSSFile decodes a json-encoded DFSS file // UnmarshalDFSSFile decodes a json-encoded DFSS file
func UnmarshalDFSSFile(data []byte) (*contract.JSON, error) { func UnmarshalDFSSFile(data []byte) (*contract.JSON, error) {
c := &contract.JSON{} c := &contract.JSON{}
err := json.Unmarshal(data, c) err := json.Unmarshal(data, c)
if err != nil { if err != nil {
...@@ -22,3 +21,25 @@ func UnmarshalDFSSFile(data []byte) (*contract.JSON, error) { ...@@ -22,3 +21,25 @@ func UnmarshalDFSSFile(data []byte) (*contract.JSON, error) {
return c, nil return c, nil
} }
// RecoverDataJSON : contains all the necessary information to try and recover a previously signed contract from the ttp
type RecoverDataJSON struct {
SignatureUUID string
TTPAddrport string
TTPHash []byte
}
// UnmarshalRecoverDataFile decodes a json-encoded Recover dara file
func UnmarshalRecoverDataFile(data []byte) (*RecoverDataJSON, error) {
r := &RecoverDataJSON{}
err := json.Unmarshal(data, r)
if err != nil {
return nil, err
}
if r.SignatureUUID == "" || r.TTPAddrport == "" || r.TTPHash == nil {
return nil, errors.New("Invalid recover data file")
}
return r, nil
}
package common package common
import ( import (
"crypto/sha512"
"encoding/json"
"io/ioutil" "io/ioutil"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/mgo.v2/bson"
) )
func TestUnmarshalDFSSFile(t *testing.T) { func TestUnmarshalDFSSFile(t *testing.T) {
...@@ -16,3 +19,28 @@ func TestUnmarshalDFSSFile(t *testing.T) { ...@@ -16,3 +19,28 @@ func TestUnmarshalDFSSFile(t *testing.T) {
assert.Equal(t, "filename.pdf", c.File.Name) assert.Equal(t, "filename.pdf", c.File.Name)
// a tiny test, full unmarshal is tested in dfss/dfssp/contract package // a tiny test, full unmarshal is tested in dfss/dfssp/contract package
} }
func TestUnmarshalRecoverDataFile(t *testing.T) {
bsonUUID := bson.NewObjectId()
uuid := bsonUUID.Hex()
ttpAddrport := "127.0.0.1:0000"
h := sha512.Sum512([]byte{7})
ttpHash := h[:]
recData := RecoverDataJSON{
SignatureUUID: uuid,
TTPAddrport: ttpAddrport,
TTPHash: ttpHash,
}
file, err := json.MarshalIndent(recData, "", " ")
assert.Nil(t, err)
unmarshal, err := UnmarshalRecoverDataFile(file)
assert.Nil(t, err)
assert.Equal(t, uuid, unmarshal.SignatureUUID)
assert.Equal(t, ttpAddrport, unmarshal.TTPAddrport)
assert.Equal(t, ttpHash, unmarshal.TTPHash)
}
...@@ -6,6 +6,7 @@ import ( ...@@ -6,6 +6,7 @@ import (
"io/ioutil" "io/ioutil"
cAPI "dfss/dfssc/api" cAPI "dfss/dfssc/api"
"dfss/dfssc/common"
"dfss/dfssp/contract" "dfss/dfssp/contract"
) )
...@@ -45,5 +46,34 @@ func (m *SignatureManager) PersistSignaturesToFile() error { ...@@ -45,5 +46,34 @@ func (m *SignatureManager) PersistSignaturesToFile() error {
return err return err
} }
return ioutil.WriteFile(m.mail+"-"+m.contract.UUID+".proof", proof, 0600) return ioutil.WriteFile(m.mail+"-"+m.uuid+".proof", proof, 0600)
}
// PersistRecoverDataToFile : save recover informations to disk.
// returns the file name and an error if any occured
func (m *SignatureManager) PersistRecoverDataToFile() (string, error) {
// Check content, don't write an empty file
if len(m.uuid) == 0 || len(m.ttpData.Addrport) == 0 || len(m.ttpData.Hash) == 0 {
return "", fmt.Errorf("Invalid recover data. Cannot persist file.")
}
// Fill JSON struct
recData := common.RecoverDataJSON{
SignatureUUID: m.uuid,
TTPAddrport: m.ttpData.Addrport,
TTPHash: m.ttpData.Hash,
}
file, err := json.MarshalIndent(recData, "", " ")
if err != nil {
return "", err
}
filename := m.mail + "-" + m.uuid + ".run"
err = ioutil.WriteFile(filename, file, 0600)
if err != nil {
return "", err
}
return filename, nil
} }
...@@ -261,7 +261,7 @@ func (m *SignatureManager) resolve() error { ...@@ -261,7 +261,7 @@ func (m *SignatureManager) resolve() error {
return nil return nil
} }
dAPI.DLog("contacted TTP, received signed contract") dAPI.DLog("contacted TTP, received signed contract")
return ioutil.WriteFile(m.mail+"-"+m.contract.UUID+".proof", response.Contract, 0600) return ioutil.WriteFile(m.mail+"-"+m.uuid+".proof", response.Contract, 0600)
} }
// checkPromise : verifies that the promise is valid wrt the expected promises. // checkPromise : verifies that the promise is valid wrt the expected promises.
......
package sign
import (
"errors"
"io/ioutil"
"time"
"dfss/dfssc/common"
"dfss/dfssc/security"
tAPI "dfss/dfsst/api"
"dfss/net"
"golang.org/x/net/context"
)
// Recover : performs a recover attempt using the provided recover file, and the user's passphrase to use secured connection
func Recover(filename, passphrase string) error {
json, err := readRecoveryFile(filename)
if err != nil {
return err
}
auth := security.NewAuthContainer(passphrase)
_, _, _, err = auth.LoadFiles()
if err != nil {
return err
}
ttp, err := connectToTTP(json, auth)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
defer cancel()
response, err := ttp.Recover(ctx, &tAPI.RecoverRequest{SignatureUUID: json.SignatureUUID})
if err != nil {
return err
}
return treatTTPResponse(response, auth, json.SignatureUUID)
}
// readRecoveryFile : reads the recovery file from disk
func readRecoveryFile(filename string) (*common.RecoverDataJSON, error) {
data, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
json, err := common.UnmarshalRecoverDataFile(data)
if err != nil {
return nil, err
}
return json, nil
}
// connectToTTP : connects to the ttp, using the provided recover data and authentication information
func connectToTTP(json *common.RecoverDataJSON, auth *security.AuthContainer) (tAPI.TTPClient, error) {
// TODO check that the connection spots missing TTP and returns an error quickly enough
conn, err := net.Connect(json.TTPAddrport, auth.Cert, auth.Key, auth.CA, json.TTPHash)
if err != nil {
return nil, err
}
return tAPI.NewTTPClient(conn), nil
}
// treatTTPResponse : handles the writing of the recoverd contract on the disk if it was recovered.
func treatTTPResponse(response *tAPI.TTPResponse, auth *security.AuthContainer, signatureUUID string) error {
if len(response.Contract) == 0 {
return errors.New("Contract was not successfully signed. Impossible to recover.")
}
return ioutil.WriteFile(auth.Cert.Subject.CommonName+"-"+signatureUUID+".proof", response.Contract, 0600)
}
...@@ -29,13 +29,13 @@ func IsRequestValid(ctx context.Context, promises []*cAPI.Promise) (valid bool, ...@@ -29,13 +29,13 @@ func IsRequestValid(ctx context.Context, promises []*cAPI.Promise) (valid bool,
return return
} }
sender, err := GetSenderHashFromContext(ctx) sender := net.GetClientHash(&ctx)
if err != nil { if sender == nil {
valid = false valid = false
return return
} }
senderIndex, err = GetIndexOfSigner(promises[0], sender) senderIndex, err := GetIndexOfSigner(promises[0], sender)
if err != nil { if err != nil {
valid = false valid = false
return return
...@@ -76,19 +76,6 @@ func IsPromiseSignedByPlatform(promise *cAPI.Promise) (bool, bson.ObjectId, []Si ...@@ -76,19 +76,6 @@ func IsPromiseSignedByPlatform(promise *cAPI.Promise) (bool, bson.ObjectId, []Si
return true, signatureUUID, signers return true, signatureUUID, signers
} }
// GetSenderHashFromContext : creates the sender's certificate hash from the specified context.
func GetSenderHashFromContext(ctx context.Context) ([]byte, error) {
state, _, ok := net.GetTLSState(&ctx)
if !ok || len(state.VerifiedChains) == 0 {
return nil, errors.New("Empty verified sender certificate")
}
cert := state.VerifiedChains[0][0]
hash := auth.GetCertificateHash(cert)
return hash, nil
}
// GetIndexOfSigner : determines the index of the specified signer's hash in the array of signers' hashes. // GetIndexOfSigner : determines the index of the specified signer's hash in the array of signers' hashes.
func GetIndexOfSigner(promise *cAPI.Promise, hash []byte) (uint32, error) { func GetIndexOfSigner(promise *cAPI.Promise, hash []byte) (uint32, error) {
for i, h := range promise.Context.Signers { for i, h := range promise.Context.Signers {
......
package entities package entities
import ( import (
"bytes"
"gopkg.in/mgo.v2/bson" "gopkg.in/mgo.v2/bson"
) )
...@@ -105,3 +107,15 @@ func NewAbortedSigner(signerIndex, abortIndex uint32) *AbortedSigner { ...@@ -105,3 +107,15 @@ func NewAbortedSigner(signerIndex, abortIndex uint32) *AbortedSigner {
AbortIndex: abortIndex, AbortIndex: abortIndex,
} }
} }
// ContainsSigner : determines whether or not the specified signer is one of the signers,
// and also returns the sequence id of said signer.
func (archives *SignatureArchives) ContainsSigner(hash []byte) (bool, uint32) {
for i, s := range archives.Signers {
if bytes.Equal(hash, s.Hash) {
return true, uint32(i)
}
}
return false, uint32(0)
}
...@@ -25,3 +25,26 @@ func TestArePromisesEqual(t *testing.T) { ...@@ -25,3 +25,26 @@ func TestArePromisesEqual(t *testing.T) {
equal = promise0.Equal(promise1) equal = promise0.Equal(promise1)
assert.Equal(t, equal, true) assert.Equal(t, equal, true)
} }
func TestContainsSigner(t *testing.T) {
archives := NewSignatureArchives(signatureUUIDBson, sequence, signersEntities, contractDocumentHash, seal)
badHash := []byte{}
present, index := archives.ContainsSigner(badHash)
assert.False(t, present)
assert.Equal(t, uint32(0), index)
badHash = []byte{0, 0, 7}
present, index = archives.ContainsSigner(badHash)
assert.False(t, present)
assert.Equal(t, uint32(0), index)
goodHash := signersEntities[0].Hash
present, index = archives.ContainsSigner(goodHash)
assert.True(t, present)
assert.Equal(t, uint32(0), index)
goodHash = signersEntities[1].Hash
present, index = archives.ContainsSigner(goodHash)
assert.True(t, present)
assert.Equal(t, uint32(1), index)
}
...@@ -182,8 +182,8 @@ func Solve(manager *entities.ArchivesManager) (bool, []byte) { ...@@ -182,8 +182,8 @@ func Solve(manager *entities.ArchivesManager) (bool, []byte) {
func GenerateSignedContract(archives *entities.SignatureArchives) []byte { func GenerateSignedContract(archives *entities.SignatureArchives) []byte {
var pseudoContract string var pseudoContract string
for _, p := range archives.ReceivedPromises { for _, p := range archives.ReceivedPromises {
signature := "SIGNATURE FROM SIGNER " + string(archives.Signers[p.SenderKeyIndex].Hash) signature := "SIGNATURE FROM SIGNER " + fmt.Sprintf("%x", archives.Signers[p.SenderKeyIndex].Hash)
signature += " ON SIGNATURE n° " + string(archives.ID) + "\n" signature += " ON SIGNATURE n° " + fmt.Sprint(archives.ID) + "\n"
pseudoContract += signature pseudoContract += signature
} }
......
...@@ -446,8 +446,8 @@ func TestGenerateSignedContract(t *testing.T) { ...@@ -446,8 +446,8 @@ func TestGenerateSignedContract(t *testing.T) {
var pseudoContract string var pseudoContract string
for _, p := range promises { for _, p := range promises {
signature := "SIGNATURE FROM SIGNER " + string(signersEntities[p.SenderKeyIndex].Hash) signature := "SIGNATURE FROM SIGNER " + fmt.Sprintf("%x", signersEntities[p.SenderKeyIndex].Hash)
signature += " ON SIGNATURE n° " + string(id) + "\n" signature += " ON SIGNATURE n° " + fmt.Sprint(id) + "\n"
pseudoContract += signature pseudoContract += signature
} }
......
...@@ -226,8 +226,44 @@ func (server *ttpServer) handleContractGenerationTry(manager *entities.ArchivesM ...@@ -226,8 +226,44 @@ func (server *ttpServer) handleContractGenerationTry(manager *entities.ArchivesM
// Recover route for the TTP. // Recover route for the TTP.
func (server *ttpServer) Recover(ctx context.Context, in *tAPI.RecoverRequest) (*tAPI.TTPResponse, error) { func (server *ttpServer) Recover(ctx context.Context, in *tAPI.RecoverRequest) (*tAPI.TTPResponse, error) {
// TODO if !bson.IsObjectIdHex(in.SignatureUUID) {
return nil, nil return nil, errors.New("Invalid signature uuid.")
}
bsonUUID := bson.ObjectIdHex(in.SignatureUUID)
manager := entities.NewArchivesManager(server.DB)
present, archives := manager.ContainsSignature(bsonUUID)
if !present {
return nil, errors.New("Unknown signature uuid.")
}
manager.Archives = archives
contract, err := handleRecover(ctx, manager)
if err != nil {
return nil, err
}
return &tAPI.TTPResponse{Contract: contract}, nil
}
func handleRecover(ctx context.Context, manager *entities.ArchivesManager) ([]byte, error) {
senderHash := net.GetClientHash(&ctx)
if senderHash == nil {
return []byte{}, errors.New("Bad authentication.")
}
present, senderID := manager.Archives.ContainsSigner(senderHash)
if !present {
return []byte{}, errors.New("Signer was not part of the signature.")
}
aborted := manager.HasReceivedAbortToken(senderID)
if aborted {
return []byte{}, errors.New("Signer was aborted.")
}
_, contract := manager.WasContractSigned()
return contract, nil
} }
// GetServer returns the gRPC server. // GetServer returns the gRPC server.
......
...@@ -104,8 +104,3 @@ func TestMain(m *testing.M) { ...@@ -104,8 +104,3 @@ func TestMain(m *testing.M) {
os.Exit(code) os.Exit(code)
} }
func TestAlert(t *testing.T) {
// TODO
// This requires the user of a real Alert message
}
...@@ -144,25 +144,30 @@ func checkProofFile(t *testing.T, nb int) { ...@@ -144,25 +144,30 @@ func checkProofFile(t *testing.T, nb int) {
// TestSignContractFailure tests the signature with a faulty client, when contract can't be generated. // TestSignContractFailure tests the signature with a faulty client, when contract can't be generated.
// In this test, everything should not work fine, because client3 shutdowns way too early. // In this test, everything should not work fine, because client3 shutdowns way too early.
func TestSignContractFailure(t *testing.T) { func TestSignContractFailure(t *testing.T) {
signatureHelper(t, "1", 0) signatureHelper(t, true)
} }
// TestSignContractSuccess tests the signature with a faulty client, when contract can be generated. // TestSignContractSuccess tests the signature with a faulty client, when contract can be generated.
// In this test, everything should not work fine, because client3 shutdowns way too early. // In this test, everything should not work fine, because client3 shutdowns way too early.
func TestSignContractSuccess(t *testing.T) { func TestSignContractSuccess(t *testing.T) {
signatureHelper(t, "2", 2) signatureHelper(t, false)
} }
// signatureHelper : launches a parametrized signature, with the number of rounds a client will accomplish before shutting down, // signatureHelper : launches a parametrized signature, with the number of rounds a client will accomplish before shutting down,
// and the number of proof files expected to be generated. // and the number of proof files expected to be generated.
func signatureHelper(t *testing.T, round string, nbFiles int) { func signatureHelper(t *testing.T, failure bool) {
// Setup // Setup
stop, clients, contractPath, contractFilePath := setupSignature(t) stop, clients, contractPath, contractFilePath := setupSignature(t)
defer stop() defer stop()
stopBefore, expectedProofFile1, expectedProofFile2 := "1", 0, 0
if !failure {
stopBefore, expectedProofFile1, expectedProofFile2 = "2", 2, 1
}
// Configure client3 to be faulty // Configure client3 to be faulty
setLastArg(clients[2], "--stopbefore", true) setLastArg(clients[2], "--stopbefore", true)
setLastArg(clients[2], round, false) setLastArg(clients[2], stopBefore, false)
setLastArg(clients[2], "sign", false) setLastArg(clients[2], "sign", false)
// Sign! // Sign!
...@@ -183,6 +188,40 @@ func signatureHelper(t *testing.T, round string, nbFiles int) { ...@@ -183,6 +188,40 @@ func signatureHelper(t *testing.T, round string, nbFiles int) {
<-closeChannel <-closeChannel
} }
checkProofFile(t, nbFiles) checkProofFile(t, expectedProofFile1)
filename := checkRecoverFile(t, "client3@example.com")
callRecover(newClient(clients[2]), filename)
_ = os.Remove(filename)
checkProofFile(t, expectedProofFile2)
time.Sleep(time.Second) time.Sleep(time.Second)
return
}
// checkProofFile counts the number of proof file contained in the current directory, and compares it to the nb parameter.
func checkRecoverFile(t *testing.T, mail string) string {
// Ensure that all the files are present
recoverFile := regexp.MustCompile(mail + `.*\.run`)
files, _ := ioutil.ReadDir("./")
var filename string
matches := 0
for _, file := range files {
if recoverFile.Match([]byte(file.Name())) {
matches++
filename = file.Name()
}
}
assert.Equal(t, 1, matches, "Invalid number of recover file(s)")
return filename
}
func callRecover(c *exec.Cmd, filename string) {
// We cannot use "setLastArg" because we need to update arguments
c.Args = c.Args[:(len(c.Args) - 4)]
c.Args = append(c.Args, "recover", filename)
c.Stdin = strings.NewReader("password\n")
c.Stdout = os.Stdout
c.Stderr = os.Stderr
_ = c.Run()
} }
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