147 lines
4.5 KiB
Go
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
|
|
}
|