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 "" }