From 5d2ebd9f7269ae8ea2b19047089f48bdca5345a2 Mon Sep 17 00:00:00 2001 From: Axel Date: Wed, 25 May 2016 14:01:26 +0200 Subject: [PATCH] [t][c] Add recover procedure --- dfssc/cmd/recover.go | 36 +++++++++++ dfssc/cmd/root.go | 3 +- dfssc/cmd/sign.go | 14 +++++ dfssc/common/file.go | 23 ++++++- dfssc/common/file_test.go | 28 +++++++++ dfssc/sign/persist.go | 32 +++++++++- dfssc/sign/protocol.go | 2 +- dfssc/sign/recover.go | 77 ++++++++++++++++++++++++ dfsst/entities/check_request.go | 19 +----- dfsst/entities/signatureArchives.go | 14 +++++ dfsst/entities/signatureArchives_test.go | 23 +++++++ dfsst/resolve/resolve.go | 4 +- dfsst/resolve/resolve_test.go | 4 +- dfsst/server/server.go | 40 +++++++++++- dfsst/server/server_test.go | 5 -- tests/sign_test.go | 49 +++++++++++++-- 16 files changed, 336 insertions(+), 37 deletions(-) create mode 100644 dfssc/cmd/recover.go create mode 100644 dfssc/sign/recover.go diff --git a/dfssc/cmd/recover.go b/dfssc/cmd/recover.go new file mode 100644 index 0000000..27acd42 --- /dev/null +++ b/dfssc/cmd/recover.go @@ -0,0 +1,36 @@ +package cmd + +import ( + "fmt" + "os" + + "dfss/dfssc/sign" + "github.com/spf13/cobra" +) + +var recoverCmd = &cobra.Command{ + Use: "recover ", + 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.") +} diff --git a/dfssc/cmd/root.go b/dfssc/cmd/root.go index f37f40b..8c7e1e9 100644 --- a/dfssc/cmd/root.go +++ b/dfssc/cmd/root.go @@ -58,6 +58,5 @@ func init() { _ = viper.BindPFlag("timeout", RootCmd.PersistentFlags().Lookup("timeout")) // 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) } diff --git a/dfssc/cmd/sign.go b/dfssc/cmd/sign.go index f87ae1f..4011adc 100644 --- a/dfssc/cmd/sign.go +++ b/dfssc/cmd/sign.go @@ -74,6 +74,13 @@ var signCmd = &cobra.Command{ // TODO Warning, integration tests are checking Stdout 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 manager.OnProgressUpdate = signProgressFn err = manager.Sign() @@ -83,6 +90,13 @@ var signCmd = &cobra.Command{ } 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) + } }, } diff --git a/dfssc/common/file.go b/dfssc/common/file.go index f19cb76..0b81c5b 100644 --- a/dfssc/common/file.go +++ b/dfssc/common/file.go @@ -9,7 +9,6 @@ import ( // UnmarshalDFSSFile decodes a json-encoded DFSS file func UnmarshalDFSSFile(data []byte) (*contract.JSON, error) { - c := &contract.JSON{} err := json.Unmarshal(data, c) if err != nil { @@ -22,3 +21,25 @@ func UnmarshalDFSSFile(data []byte) (*contract.JSON, error) { 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 +} diff --git a/dfssc/common/file_test.go b/dfssc/common/file_test.go index 691252f..007fcd3 100644 --- a/dfssc/common/file_test.go +++ b/dfssc/common/file_test.go @@ -1,10 +1,13 @@ package common import ( + "crypto/sha512" + "encoding/json" "io/ioutil" "testing" "github.com/stretchr/testify/assert" + "gopkg.in/mgo.v2/bson" ) func TestUnmarshalDFSSFile(t *testing.T) { @@ -16,3 +19,28 @@ func TestUnmarshalDFSSFile(t *testing.T) { assert.Equal(t, "filename.pdf", c.File.Name) // 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) +} diff --git a/dfssc/sign/persist.go b/dfssc/sign/persist.go index 5d8df88..a3acfc1 100644 --- a/dfssc/sign/persist.go +++ b/dfssc/sign/persist.go @@ -6,6 +6,7 @@ import ( "io/ioutil" cAPI "dfss/dfssc/api" + "dfss/dfssc/common" "dfss/dfssp/contract" ) @@ -45,5 +46,34 @@ func (m *SignatureManager) PersistSignaturesToFile() error { 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 } diff --git a/dfssc/sign/protocol.go b/dfssc/sign/protocol.go index c0bcf09..cc0a863 100644 --- a/dfssc/sign/protocol.go +++ b/dfssc/sign/protocol.go @@ -261,7 +261,7 @@ func (m *SignatureManager) resolve() error { return nil } 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. diff --git a/dfssc/sign/recover.go b/dfssc/sign/recover.go new file mode 100644 index 0000000..e700a29 --- /dev/null +++ b/dfssc/sign/recover.go @@ -0,0 +1,77 @@ +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) +} diff --git a/dfsst/entities/check_request.go b/dfsst/entities/check_request.go index a5105f0..4272e20 100644 --- a/dfsst/entities/check_request.go +++ b/dfsst/entities/check_request.go @@ -29,13 +29,13 @@ func IsRequestValid(ctx context.Context, promises []*cAPI.Promise) (valid bool, return } - sender, err := GetSenderHashFromContext(ctx) - if err != nil { + sender := net.GetClientHash(&ctx) + if sender == nil { valid = false return } - senderIndex, err = GetIndexOfSigner(promises[0], sender) + senderIndex, err := GetIndexOfSigner(promises[0], sender) if err != nil { valid = false return @@ -76,19 +76,6 @@ func IsPromiseSignedByPlatform(promise *cAPI.Promise) (bool, bson.ObjectId, []Si 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. func GetIndexOfSigner(promise *cAPI.Promise, hash []byte) (uint32, error) { for i, h := range promise.Context.Signers { diff --git a/dfsst/entities/signatureArchives.go b/dfsst/entities/signatureArchives.go index ce3f1af..7fbffff 100644 --- a/dfsst/entities/signatureArchives.go +++ b/dfsst/entities/signatureArchives.go @@ -1,6 +1,8 @@ package entities import ( + "bytes" + "gopkg.in/mgo.v2/bson" ) @@ -105,3 +107,15 @@ func NewAbortedSigner(signerIndex, abortIndex uint32) *AbortedSigner { 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) +} diff --git a/dfsst/entities/signatureArchives_test.go b/dfsst/entities/signatureArchives_test.go index ce42e1f..067d6b2 100644 --- a/dfsst/entities/signatureArchives_test.go +++ b/dfsst/entities/signatureArchives_test.go @@ -25,3 +25,26 @@ func TestArePromisesEqual(t *testing.T) { equal = promise0.Equal(promise1) 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) +} diff --git a/dfsst/resolve/resolve.go b/dfsst/resolve/resolve.go index 1eca866..938913b 100644 --- a/dfsst/resolve/resolve.go +++ b/dfsst/resolve/resolve.go @@ -182,8 +182,8 @@ func Solve(manager *entities.ArchivesManager) (bool, []byte) { func GenerateSignedContract(archives *entities.SignatureArchives) []byte { var pseudoContract string for _, p := range archives.ReceivedPromises { - signature := "SIGNATURE FROM SIGNER " + string(archives.Signers[p.SenderKeyIndex].Hash) - signature += " ON SIGNATURE n° " + string(archives.ID) + "\n" + signature := "SIGNATURE FROM SIGNER " + fmt.Sprintf("%x", archives.Signers[p.SenderKeyIndex].Hash) + signature += " ON SIGNATURE n° " + fmt.Sprint(archives.ID) + "\n" pseudoContract += signature } diff --git a/dfsst/resolve/resolve_test.go b/dfsst/resolve/resolve_test.go index 7ee01d6..dd7116c 100644 --- a/dfsst/resolve/resolve_test.go +++ b/dfsst/resolve/resolve_test.go @@ -446,8 +446,8 @@ func TestGenerateSignedContract(t *testing.T) { var pseudoContract string for _, p := range promises { - signature := "SIGNATURE FROM SIGNER " + string(signersEntities[p.SenderKeyIndex].Hash) - signature += " ON SIGNATURE n° " + string(id) + "\n" + signature := "SIGNATURE FROM SIGNER " + fmt.Sprintf("%x", signersEntities[p.SenderKeyIndex].Hash) + signature += " ON SIGNATURE n° " + fmt.Sprint(id) + "\n" pseudoContract += signature } diff --git a/dfsst/server/server.go b/dfsst/server/server.go index 3e60ec8..999c966 100644 --- a/dfsst/server/server.go +++ b/dfsst/server/server.go @@ -226,8 +226,44 @@ func (server *ttpServer) handleContractGenerationTry(manager *entities.ArchivesM // Recover route for the TTP. func (server *ttpServer) Recover(ctx context.Context, in *tAPI.RecoverRequest) (*tAPI.TTPResponse, error) { - // TODO - return nil, nil + if !bson.IsObjectIdHex(in.SignatureUUID) { + 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. diff --git a/dfsst/server/server_test.go b/dfsst/server/server_test.go index 4d3dad9..109f596 100644 --- a/dfsst/server/server_test.go +++ b/dfsst/server/server_test.go @@ -104,8 +104,3 @@ func TestMain(m *testing.M) { os.Exit(code) } - -func TestAlert(t *testing.T) { - // TODO - // This requires the user of a real Alert message -} diff --git a/tests/sign_test.go b/tests/sign_test.go index 19cf0b0..8e1b3d8 100644 --- a/tests/sign_test.go +++ b/tests/sign_test.go @@ -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. // In this test, everything should not work fine, because client3 shutdowns way too early. 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. // In this test, everything should not work fine, because client3 shutdowns way too early. 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, // 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 stop, clients, contractPath, contractFilePath := setupSignature(t) defer stop() + stopBefore, expectedProofFile1, expectedProofFile2 := "1", 0, 0 + if !failure { + stopBefore, expectedProofFile1, expectedProofFile2 = "2", 2, 1 + } + // Configure client3 to be faulty setLastArg(clients[2], "--stopbefore", true) - setLastArg(clients[2], round, false) + setLastArg(clients[2], stopBefore, false) setLastArg(clients[2], "sign", false) // Sign! @@ -183,6 +188,40 @@ func signatureHelper(t *testing.T, round string, nbFiles int) { <-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) + 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() } -- GitLab