safing-jess/filesig/helpers.go
2022-10-11 12:48:24 +02:00

147 lines
4.5 KiB
Go

package filesig
import (
"errors"
"fmt"
"io/fs"
"os"
"strings"
"github.com/safing/jess"
"github.com/safing/jess/hashtools"
"github.com/safing/jess/lhash"
)
// SignFile signs a file and replaces the signature file with a new one.
// If the dataFilePath is "-", the file data is read from stdin.
// Existing jess signatures in the signature file are removed.
func SignFile(dataFilePath, signatureFilePath string, metaData map[string]string, envelope *jess.Envelope, trustStore jess.TrustStore) (fileData *FileData, err error) {
// Load encryption suite.
if err := envelope.LoadSuite(); err != nil {
return nil, err
}
// Extract the used hashing algorithm from the suite.
var hashTool *hashtools.HashTool
for _, toolID := range envelope.Suite().Tools {
if strings.Contains(toolID, "(") {
hashToolID := strings.Trim(strings.Split(toolID, "(")[1], "()")
hashTool, _ = hashtools.Get(hashToolID)
break
}
}
if hashTool == nil {
return nil, errors.New("suite not suitable for file signing")
}
// Hash the data file.
var fileHash *lhash.LabeledHash
if dataFilePath == "-" {
fileHash, err = hashTool.LabeledHasher().DigestFromReader(os.Stdin)
} else {
fileHash, err = hashTool.LabeledHasher().DigestFile(dataFilePath)
}
if err != nil {
return nil, fmt.Errorf("failed to hash file: %w", err)
}
// Sign the file data.
signature, fileData, err := SignFileData(fileHash, metaData, envelope, trustStore)
if err != nil {
return nil, fmt.Errorf("failed to sign file: %w", err)
}
sigFileData, err := os.ReadFile(signatureFilePath)
var newSigFileData []byte
switch {
case err == nil:
// Add signature to existing file.
newSigFileData, err = AddToSigFile(signature, sigFileData, true)
if err != nil {
return nil, fmt.Errorf("failed to add signature to file: %w", err)
}
case errors.Is(err, fs.ErrNotExist):
// Make signature section for saving to disk.
newSigFileData, err = MakeSigFileSection(signature)
if err != nil {
return nil, fmt.Errorf("failed to format signature for file: %w", err)
}
default:
return nil, fmt.Errorf("failed to open existing signature file: %w", err)
}
// Write the signature to file.
if err := os.WriteFile(signatureFilePath, newSigFileData, 0o0644); err != nil { //nolint:gosec
return nil, fmt.Errorf("failed to write signature to file: %w", err)
}
return fileData, nil
}
// VerifyFile verifies the given files and returns the verified file data.
// If the dataFilePath is "-", the file data is read from stdin.
// If an error is returned, there was an error in at least some part of the process.
// Any returned file data struct must be checked for an verification error.
func VerifyFile(dataFilePath, signatureFilePath string, metaData map[string]string, trustStore jess.TrustStore) (verifiedFileData []*FileData, err error) {
var lastErr error
// Read signature from file.
sigFileData, err := os.ReadFile(signatureFilePath)
if err != nil {
return nil, fmt.Errorf("failed to read signature file: %w", err)
}
// Extract all signatures.
sigs, err := ParseSigFile(sigFileData)
switch {
case len(sigs) == 0 && err != nil:
return nil, fmt.Errorf("failed to parse signature file: %w", err)
case len(sigs) == 0:
return nil, errors.New("no signatures found in signature file")
case err != nil:
lastErr = fmt.Errorf("failed to parse signature file: %w", err)
}
// Verify all signatures.
goodFileData := make([]*FileData, 0, len(sigs))
var badFileData []*FileData
for _, sigLetter := range sigs {
// Verify signature.
fileData, err := VerifyFileData(sigLetter, metaData, trustStore)
if err != nil {
lastErr = err
if fileData != nil {
fileData.verificationError = err
badFileData = append(badFileData, fileData)
}
continue
}
// Hash the file.
var fileHash *lhash.LabeledHash
if dataFilePath == "-" {
fileHash, err = fileData.FileHash().Algorithm().DigestFromReader(os.Stdin)
} else {
fileHash, err = fileData.FileHash().Algorithm().DigestFile(dataFilePath)
}
if err != nil {
lastErr = err
fileData.verificationError = err
badFileData = append(badFileData, fileData)
continue
}
// Check if the hash matches.
if !fileData.FileHash().Equal(fileHash) {
lastErr = errors.New("signature invalid: file was modified")
fileData.verificationError = lastErr
badFileData = append(badFileData, fileData)
continue
}
// Add verified file data to list for return.
goodFileData = append(goodFileData, fileData)
}
return append(goodFileData, badFileData...), lastErr
}