Add sign command to updatemgr
This commit is contained in:
180
cmds/updatemgr/sign.go
Normal file
180
cmds/updatemgr/sign.go
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
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 ""
|
||||||
|
}
|
||||||
2
go.mod
2
go.mod
@@ -44,7 +44,7 @@ require (
|
|||||||
github.com/oschwald/maxminddb-golang v1.13.1
|
github.com/oschwald/maxminddb-golang v1.13.1
|
||||||
github.com/r3labs/diff/v3 v3.0.1
|
github.com/r3labs/diff/v3 v3.0.1
|
||||||
github.com/rot256/pblind v0.0.0-20240730113005-f3275049ead5
|
github.com/rot256/pblind v0.0.0-20240730113005-f3275049ead5
|
||||||
github.com/safing/jess v0.3.4
|
github.com/safing/jess v0.3.5
|
||||||
github.com/safing/structures v1.1.0
|
github.com/safing/structures v1.1.0
|
||||||
github.com/seehuhn/fortuna v1.0.1
|
github.com/seehuhn/fortuna v1.0.1
|
||||||
github.com/shirou/gopsutil v3.21.11+incompatible
|
github.com/shirou/gopsutil v3.21.11+incompatible
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -250,6 +250,8 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR
|
|||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/safing/jess v0.3.4 h1:/p6ensqEUn2jI/z1EB9JUdwH4MJQirh/C9jEwNBzxw8=
|
github.com/safing/jess v0.3.4 h1:/p6ensqEUn2jI/z1EB9JUdwH4MJQirh/C9jEwNBzxw8=
|
||||||
github.com/safing/jess v0.3.4/go.mod h1:+B6UJnXVxi406Wk08SDnoC5NNBL7t3N0vZGokEbkVQI=
|
github.com/safing/jess v0.3.4/go.mod h1:+B6UJnXVxi406Wk08SDnoC5NNBL7t3N0vZGokEbkVQI=
|
||||||
|
github.com/safing/jess v0.3.5 h1:KS5elTKfWcDUow8SUoCj5QdyyGJNoExJNySerNkbxUU=
|
||||||
|
github.com/safing/jess v0.3.5/go.mod h1:+B6UJnXVxi406Wk08SDnoC5NNBL7t3N0vZGokEbkVQI=
|
||||||
github.com/safing/structures v1.1.0 h1:QzHBQBjaZSLzw2f6PM4ibSmPcfBHAOB5CKJ+k4FYkhQ=
|
github.com/safing/structures v1.1.0 h1:QzHBQBjaZSLzw2f6PM4ibSmPcfBHAOB5CKJ+k4FYkhQ=
|
||||||
github.com/safing/structures v1.1.0/go.mod h1:QUrB74FcU41ahQ5oy3YNFCoSq+twE/n3+vNZc2K35II=
|
github.com/safing/structures v1.1.0/go.mod h1:QUrB74FcU41ahQ5oy3YNFCoSq+twE/n3+vNZc2K35II=
|
||||||
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||||
|
|||||||
Reference in New Issue
Block a user