mirror of
https://github.com/safing/portmaster
synced 2025-09-02 18:49:14 +00:00
Add signature support to updatemgr
This commit is contained in:
parent
b1b31257b4
commit
168cf010ea
2 changed files with 99 additions and 65 deletions
|
@ -7,6 +7,7 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
@ -75,18 +76,26 @@ func release(cmd *cobra.Command, args []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeIndex(channel string, versions map[string]string) error {
|
func writeIndex(channel string, versions map[string]string) error {
|
||||||
|
// Create new index file.
|
||||||
|
indexFile := &updater.IndexFile{
|
||||||
|
Channel: channel,
|
||||||
|
Published: time.Now().UTC().Round(time.Second),
|
||||||
|
Releases: versions,
|
||||||
|
}
|
||||||
|
|
||||||
// Export versions and format them.
|
// Export versions and format them.
|
||||||
versionData, err := json.MarshalIndent(versions, "", " ")
|
confirmData, err := json.MarshalIndent(indexFile, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build destination path.
|
// Build index paths.
|
||||||
indexFilePath := filepath.Join(registry.StorageDir().Path, channel+".json")
|
oldIndexPath := filepath.Join(registry.StorageDir().Path, channel+".json")
|
||||||
|
newIndexPath := filepath.Join(registry.StorageDir().Path, channel+".v2.json")
|
||||||
|
|
||||||
// Print preview.
|
// Print preview.
|
||||||
fmt.Printf("%s (%s):\n", channel, indexFilePath)
|
fmt.Printf("%s\n%s\n%s\n\n", channel, oldIndexPath, newIndexPath)
|
||||||
fmt.Println(string(versionData))
|
fmt.Println(string(confirmData))
|
||||||
|
|
||||||
// Ask for confirmation.
|
// Ask for confirmation.
|
||||||
if !confirm("\nDo you want to write this index?") {
|
if !confirm("\nDo you want to write this index?") {
|
||||||
|
@ -94,13 +103,33 @@ func writeIndex(channel string, versions map[string]string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write new index to disk.
|
// Write indexes.
|
||||||
err = ioutil.WriteFile(indexFilePath, versionData, 0o0644) //nolint:gosec // 0644 is intended
|
err = writeAsJSON(oldIndexPath, versions)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to write %s: %w", oldIndexPath, err)
|
||||||
|
}
|
||||||
|
err = writeAsJSON(newIndexPath, indexFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to write %s: %w", newIndexPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeAsJSON(path string, data any) error {
|
||||||
|
// Marshal to JSON.
|
||||||
|
jsonData, err := json.MarshalIndent(data, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Printf("written %s\n", indexFilePath)
|
|
||||||
|
|
||||||
|
// Write to disk.
|
||||||
|
err = ioutil.WriteFile(path, jsonData, 0o0644) //nolint:gosec
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("written %s\n", path)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,26 +1,19 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/safing/jess"
|
"github.com/safing/jess"
|
||||||
"github.com/safing/jess/filesig"
|
"github.com/safing/jess/filesig"
|
||||||
"github.com/safing/jess/truststores"
|
"github.com/safing/jess/truststores"
|
||||||
"github.com/safing/portbase/formats/dsd"
|
|
||||||
"github.com/safing/portbase/updater"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const letterFileExtension = ".letter"
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(signCmd)
|
rootCmd.AddCommand(signCmd)
|
||||||
signCmd.PersistentFlags().StringVarP(&envelopeName, "envelope", "", "",
|
signCmd.PersistentFlags().StringVarP(&envelopeName, "envelope", "", "",
|
||||||
|
@ -33,6 +26,7 @@ func init() {
|
||||||
signCmd.PersistentFlags().StringVarP(&trustStoreKeyring, "tskeyring", "", "",
|
signCmd.PersistentFlags().StringVarP(&trustStoreKeyring, "tskeyring", "", "",
|
||||||
"specify a truststore keyring namespace (default loaded from JESS_TS_KEYRING env variable) - lower priority than tsdir",
|
"specify a truststore keyring namespace (default loaded from JESS_TS_KEYRING env variable) - lower priority than tsdir",
|
||||||
)
|
)
|
||||||
|
// FIXME: Add silent flag to suppress verification checks.
|
||||||
signCmd.AddCommand(signIndexCmd)
|
signCmd.AddCommand(signIndexCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,7 +62,7 @@ func sign(cmd *cobra.Command, args []string) error {
|
||||||
|
|
||||||
// Get all resources and iterate over all versions.
|
// Get all resources and iterate over all versions.
|
||||||
export := registry.Export()
|
export := registry.Export()
|
||||||
var fails int
|
var verified, signed, fails int
|
||||||
for _, rv := range export {
|
for _, rv := range export {
|
||||||
for _, version := range rv.Versions {
|
for _, version := range rv.Versions {
|
||||||
file := version.GetFile()
|
file := version.GetFile()
|
||||||
|
@ -89,6 +83,7 @@ func sign(cmd *cobra.Command, args []string) error {
|
||||||
fails++
|
fails++
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("[ OK ] valid signature for %s: signed by %s\n", file.Path(), getSignedByMany(fileData, trustStore))
|
fmt.Printf("[ OK ] valid signature for %s: signed by %s\n", file.Path(), getSignedByMany(fileData, trustStore))
|
||||||
|
verified++
|
||||||
}
|
}
|
||||||
|
|
||||||
case os.IsNotExist(err):
|
case os.IsNotExist(err):
|
||||||
|
@ -105,6 +100,7 @@ func sign(cmd *cobra.Command, args []string) error {
|
||||||
fails++
|
fails++
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("[SIGN] signed %s with %s\n", file.Path(), getSignedBySingle(fileData, trustStore))
|
fmt.Printf("[SIGN] signed %s with %s\n", file.Path(), getSignedBySingle(fileData, trustStore))
|
||||||
|
signed++
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -122,12 +118,6 @@ func sign(cmd *cobra.Command, args []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func signIndex(cmd *cobra.Command, args []string) error {
|
func signIndex(cmd *cobra.Command, args []string) error {
|
||||||
// FIXME:
|
|
||||||
// Do not sign embedded, but also as a separate file.
|
|
||||||
// Slightly more complex, but it makes all the other handling easier.
|
|
||||||
|
|
||||||
indexFilePath := args[0]
|
|
||||||
|
|
||||||
// Setup trust store.
|
// Setup trust store.
|
||||||
trustStore, err := setupTrustStore()
|
trustStore, err := setupTrustStore()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -140,54 +130,69 @@ func signIndex(cmd *cobra.Command, args []string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read index file.
|
// Resolve globs.
|
||||||
indexData, err := ioutil.ReadFile(indexFilePath)
|
files := make([]string, 0, len(args))
|
||||||
if err != nil {
|
for _, arg := range args {
|
||||||
return fmt.Errorf("failed to read index file %s: %w", indexFilePath, err)
|
matches, err := filepath.Glob(arg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
files = append(files, matches...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load index.
|
// Go through all files.
|
||||||
resourceVersions := make(map[string]string)
|
var fails int
|
||||||
err = json.Unmarshal(indexData, &resourceVersions)
|
for _, file := range files {
|
||||||
if err != nil {
|
sigFile := file + filesig.Extension
|
||||||
return fmt.Errorf("failed to parse index file: %w", err)
|
|
||||||
|
// Ignore matches for the signatures.
|
||||||
|
if strings.HasSuffix(file, filesig.Extension) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if there is an existing signature.
|
||||||
|
_, err := os.Stat(sigFile)
|
||||||
|
switch {
|
||||||
|
case err == nil || os.IsExist(err):
|
||||||
|
// If the file exists, just verify.
|
||||||
|
fileData, err := filesig.VerifyFile(
|
||||||
|
file,
|
||||||
|
sigFile,
|
||||||
|
nil,
|
||||||
|
trustStore,
|
||||||
|
)
|
||||||
|
if err == nil {
|
||||||
|
fmt.Printf("[ OK ] valid signature for %s: signed by %s\n", file, getSignedByMany(fileData, trustStore))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fallthrough
|
||||||
|
case os.IsNotExist(err):
|
||||||
|
// Attempt to sign file.
|
||||||
|
fileData, err := filesig.SignFile(
|
||||||
|
file,
|
||||||
|
sigFile,
|
||||||
|
nil,
|
||||||
|
signingEnvelope,
|
||||||
|
trustStore,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("[FAIL] failed to sign %s: %s\n", file, err)
|
||||||
|
fails++
|
||||||
|
} else {
|
||||||
|
fmt.Printf("[SIGN] signed %s with %s\n", file, getSignedBySingle(fileData, trustStore))
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
// File access error.
|
||||||
|
fmt.Printf("[FAIL] failed to access %s: %s\n", sigFile, err)
|
||||||
|
fails++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create signed index file structure.
|
if fails > 0 {
|
||||||
index := updater.IndexFile{
|
return fmt.Errorf("signing or checking failed on %d files", fails)
|
||||||
Channel: strings.TrimSuffix(filepath.Base(indexFilePath), filepath.Ext(indexFilePath)),
|
|
||||||
Published: time.Now(),
|
|
||||||
Expires: time.Now().Add(3 * 31 * 24 * time.Hour), // Expires in 3 Months.
|
|
||||||
Versions: resourceVersions,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serialize index.
|
|
||||||
indexData, err = dsd.Dump(index, dsd.CBOR)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to serialize index structure: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign index.
|
|
||||||
session, err := signingEnvelope.Correspondence(trustStore)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to prepare signing: %w", err)
|
|
||||||
}
|
|
||||||
signedIndex, err := session.Close(indexData)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to sign: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write new file.
|
|
||||||
signedIndexData, err := signedIndex.ToDSD(dsd.CBOR)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to serialize signed index: %w", err)
|
|
||||||
}
|
|
||||||
signedIndexFilePath := strings.TrimSuffix(indexFilePath, filepath.Ext(indexFilePath)) + letterFileExtension
|
|
||||||
err = ioutil.WriteFile(signedIndexFilePath, signedIndexData, 0o644) //nolint:gosec // Permission is ok.
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to write signed index to %s: %w", signedIndexFilePath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue