safing-portmaster/cmds/updatemgr/sign.go
2025-01-21 09:21:56 +01:00

180 lines
4.4 KiB
Go

package main
import (
"errors"
"fmt"
"os"
"strings"
"github.com/spf13/cobra"
"github.com/safing/jess"
"github.com/safing/jess/filesig"
"github.com/safing/jess/truststores"
"github.com/safing/portmaster/service/updates"
)
func init() {
rootCmd.AddCommand(signCmd)
// Required argument: envelope
signCmd.Flags().StringVarP(&envelopeName, "envelope", "", "",
"specify envelope name used for signing",
)
_ = signCmd.MarkFlagRequired("envelope")
// Optional arguments: verbose, tsdir, tskeyring
signCmd.Flags().BoolVarP(&signVerbose, "verbose", "v", false,
"enable verbose output",
)
signCmd.Flags().StringVarP(&trustStoreDir, "tsdir", "", "",
"specify a truststore directory (default loaded from JESS_TS_DIR env variable)",
)
signCmd.Flags().StringVarP(&trustStoreKeyring, "tskeyring", "", "",
"specify a truststore keyring namespace (default loaded from JESS_TS_KEYRING env variable) - lower priority than tsdir",
)
}
var (
signCmd = &cobra.Command{
Use: "sign [index.json file]",
Short: "Sign an index",
RunE: sign,
Args: cobra.ExactArgs(1),
}
envelopeName string
signVerbose bool
)
func sign(cmd *cobra.Command, args []string) error {
indexFilename := args[0]
// Setup trust store.
trustStore, err := setupTrustStore()
if err != nil {
return err
}
// Get envelope.
signingEnvelope, err := trustStore.GetEnvelope(envelopeName)
if err != nil {
return err
}
// Read index file from disk.
unsignedIndexData, err := os.ReadFile(indexFilename)
if err != nil {
return fmt.Errorf("read index file: %w", err)
}
// Parse index and check if it is valid.
index, err := updates.ParseIndex(unsignedIndexData, "", nil)
if err != nil {
return fmt.Errorf("invalid index: %w", err)
}
err = index.CanDoUpgrades()
if err != nil {
return fmt.Errorf("invalid index: %w", err)
}
// Sign index.
signedIndexData, err := filesig.AddJSONSignature(unsignedIndexData, signingEnvelope, trustStore)
if err != nil {
return fmt.Errorf("sign: %w", err)
}
// Check by parsing again.
index, err = updates.ParseIndex(signedIndexData, "", nil)
if err != nil {
return fmt.Errorf("invalid index after signing: %w", err)
}
err = index.CanDoUpgrades()
if err != nil {
return fmt.Errorf("invalid index after signing: %w", err)
}
// Write back to file.
err = os.WriteFile(indexFilename, signedIndexData, 0o0644)
if err != nil {
return fmt.Errorf("write signed index file: %w", err)
}
return nil
}
var (
trustStoreDir string
trustStoreKeyring string
)
func setupTrustStore() (trustStore truststores.ExtendedTrustStore, err error) {
// Get trust store directory.
if trustStoreDir == "" {
trustStoreDir, _ = os.LookupEnv("JESS_TS_DIR")
if trustStoreDir == "" {
trustStoreDir, _ = os.LookupEnv("JESS_TSDIR")
}
}
if trustStoreDir != "" {
trustStore, err = truststores.NewDirTrustStore(trustStoreDir)
if err != nil {
return nil, err
}
}
// Get trust store keyring.
if trustStore == nil {
if trustStoreKeyring == "" {
trustStoreKeyring, _ = os.LookupEnv("JESS_TS_KEYRING")
if trustStoreKeyring == "" {
trustStoreKeyring, _ = os.LookupEnv("JESS_TSKEYRING")
}
}
if trustStoreKeyring != "" {
trustStore, err = truststores.NewKeyringTrustStore(trustStoreKeyring)
if err != nil {
return nil, err
}
}
}
// Truststore is mandatory.
if trustStore == nil {
return nil, errors.New("no truststore configured, please pass arguments or use env variables")
}
return trustStore, nil
}
func getSignedByMany(fds []*filesig.FileData, trustStore jess.TrustStore) string {
signedBy := make([]string, 0, len(fds))
for _, fd := range fds {
if sig := fd.Signature(); sig != nil {
for _, seal := range sig.Signatures {
if signet, err := trustStore.GetSignet(seal.ID, true); err == nil {
signedBy = append(signedBy, fmt.Sprintf("%s (%s)", signet.Info.Name, seal.ID))
} else {
signedBy = append(signedBy, seal.ID)
}
}
}
}
return strings.Join(signedBy, " and ")
}
func getSignedBySingle(fd *filesig.FileData, trustStore jess.TrustStore) string {
if sig := fd.Signature(); sig != nil {
signedBy := make([]string, 0, len(sig.Signatures))
for _, seal := range sig.Signatures {
if signet, err := trustStore.GetSignet(seal.ID, true); err == nil {
signedBy = append(signedBy, fmt.Sprintf("%s (%s)", signet.Info.Name, seal.ID))
} else {
signedBy = append(signedBy, seal.ID)
}
}
return strings.Join(signedBy, " and ")
}
return ""
}