Skip to content

Commit

Permalink
Add hasing with a secret salt of every file.
Browse files Browse the repository at this point in the history
Salt is part of LegalHold object, and each file is preceeded by the salt
when doing the SHA512 hash. File name and hash is written to
`hashes.csv` file.

Fixes #11
  • Loading branch information
grundleborg committed Feb 13, 2024
1 parent cefdea1 commit 39089cb
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 1 deletion.
90 changes: 89 additions & 1 deletion server/legalhold/legal_hold.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ package legalhold

import (
"bytes"
"crypto/sha512"
"encoding/csv"
"encoding/json"
"fmt"
"io"
"strings"

"github.com/gocarina/gocsv"
Expand Down Expand Up @@ -177,6 +180,18 @@ func (ex *Execution) WritePostsBatchToFile(channelID string, posts []model.Legal
csvReader := strings.NewReader(csvContent)

_, err = ex.fileBackend.WriteFile(csvReader, path)
if err != nil {
return err
}

hashReader := strings.NewReader(csvContent)

h, err := hash(ex.LegalHold.Secret, hashReader)
if err != nil {
return err
}

err = ex.WriteFileHash(path, h)

return err
}
Expand Down Expand Up @@ -206,9 +221,20 @@ func (ex *Execution) ExportFiles(channelID string, batchCreateAt int64, batchPos
if err != nil {
return err
}

hashReader, err := ex.fileBackend.Reader(fileInfo.Path)
if err != nil {
return err
}

h, err := hash(ex.LegalHold.Secret, hashReader)
if err != nil {
return err
}

err = ex.WriteFileHash(fileInfo.Path, h)
}

// Write the
return nil
}

Expand Down Expand Up @@ -291,9 +317,55 @@ func (ex *Execution) UpdateIndexes() error {
reader := bytes.NewReader(data)

_, err = ex.fileBackend.WriteFile(reader, filePath)
if err != nil {
return err
}

hashReader := bytes.NewReader(data)
if err != nil {
return err
}

h, err := hash(ex.LegalHold.Secret, hashReader)
if err != nil {
return err
}

err = ex.WriteFileHash(filePath, h)

return err
}

func (ex *Execution) WriteFileHash(path, hash string) error {
hashesFilePath := fmt.Sprintf("%s/hashes.csv", ex.basePath())

var buf bytes.Buffer
writer := csv.NewWriter(&buf)
err := writer.Write([]string{path, hash})
if err != nil {
return err
}
writer.Flush()

lineReader := bytes.NewReader(buf.Bytes())

if exists, err := ex.fileBackend.FileExists(hashesFilePath); err != nil {
return err
} else if !exists {
_, err = ex.fileBackend.WriteFile(lineReader, hashesFilePath)
if err != nil {
return err
}
} else {
_, err = ex.fileBackend.AppendFile(lineReader, hashesFilePath)
if err != nil {
return err
}
}

return nil
}

// basePath returns the base file storage path for this Execution.
func (ex *Execution) basePath() string {
return ex.LegalHold.BasePath()
Expand Down Expand Up @@ -333,3 +405,19 @@ func (ex *Execution) filePath(channelID string, batchCreateAt int64, batchPostID
fileName,
)
}

func hash(secret string, reader io.Reader) (string, error) {
hasher := sha512.New()

_, err := hasher.Write([]byte(secret))
if err != nil {
return "", err
}

_, err = io.Copy(hasher, reader)
if err != nil {
return "", err
}

return fmt.Sprintf("%x", hasher.Sum(nil)), nil
}
55 changes: 55 additions & 0 deletions server/legalhold/legal_hold_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package legalhold

import (
"bytes"
"testing"

mattermostModel "github.com/mattermost/mattermost-server/v6/model"
Expand Down Expand Up @@ -49,3 +50,57 @@ func TestApp_LegalHoldExecution_Execute(t *testing.T) {

// TODO: Do some proper assertions here to really test the functionality.
}

func TestLegalHold_Hash(t *testing.T) {
testCases := []struct {
name string
input string
secret string
expectedOutput string
expectedError error
}{
{
name: "empty input",
input: "",
secret: "foo",
expectedOutput: "f7fbba6e0636f890e56fbbf3283e524c6fa3204ae298382d624741d0dc6638326e282c41be5e4254d8820772c5518a2c5a8c0c7f7eda19594a7eb539453e1ed7",
expectedError: nil,
},
{
name: "valid input",
input: "Hello, World!",
secret: "foo",
expectedOutput: "1964d4b69d3631a6ff90143c75ae1fb5c5c6045600c0dc52f1db1e2155028b56159d5d281479221f4d38fee22239dab46528424c2b122b62c97e75f01f409f4d",
expectedError: nil,
},
{
name: "valid input",
input: "Hello, World!",
secret: "",
expectedOutput: "374d794a95cdcfd8b35993185fef9ba368f160d8daf432d08ba9f1ed1e5abe6cc69291e0fa2fe0006a52570ef18c19def4e617c33ce52ef0a6e5fbe318cb0387",
expectedError: nil,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
reader := bytes.NewReader([]byte(tc.input))
result, err := hash(tc.secret, reader)

if err != nil {
if tc.expectedError == nil {
t.Errorf("hash() with args %v : Unexpected error %v", tc.input, err)
} else if err.Error() != tc.expectedError.Error() {
t.Errorf("hash() with args %v : expected %v, got %v",
tc.input, tc.expectedError, err)
}
} else {
if tc.expectedOutput != result {
t.Errorf("hash() with args %v : expected %v, got %v",
tc.input, tc.expectedOutput, result)
}
}

})
}
}
2 changes: 2 additions & 0 deletions server/model/legal_hold.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type LegalHold struct {
EndsAt int64 `json:"ends_at"`
LastExecutionEndedAt int64 `json:"last_execution_ended_at"`
ExecutionLength int64 `json:"execution_length"`
Secret string `json:"secret"`
}

// DeepCopy creates a deep copy of the LegalHold.
Expand All @@ -39,6 +40,7 @@ func (lh *LegalHold) DeepCopy() LegalHold {
EndsAt: lh.EndsAt,
LastExecutionEndedAt: lh.LastExecutionEndedAt,
ExecutionLength: lh.ExecutionLength,
Secret: lh.Secret,
}

if len(lh.UserIDs) > 0 {
Expand Down
1 change: 1 addition & 0 deletions server/store/kvstore/legal_hold.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ func (kvs Impl) CreateLegalHold(lh model.LegalHold) (*model.LegalHold, error) {

lh.CreateAt = mattermostModel.GetMillis()
lh.UpdateAt = lh.CreateAt
lh.Secret = mattermostModel.NewId()

key := fmt.Sprintf("%s%s", legalHoldPrefix, lh.ID)

Expand Down

0 comments on commit 39089cb

Please sign in to comment.