Skip to content

Commit

Permalink
Add signature support to updatemgr
Browse files Browse the repository at this point in the history
  • Loading branch information
dhaavi committed Sep 23, 2022
1 parent b1b3125 commit 168cf01
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 62 deletions.
45 changes: 37 additions & 8 deletions cmds/updatemgr/release.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
"time"

"github.com/spf13/cobra"

Expand Down Expand Up @@ -75,32 +76,60 @@ func release(cmd *cobra.Command, args []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.
versionData, err := json.MarshalIndent(versions, "", " ")
confirmData, err := json.MarshalIndent(indexFile, "", " ")
if err != nil {
return err
}

// Build destination path.
indexFilePath := filepath.Join(registry.StorageDir().Path, channel+".json")
// Build index paths.
oldIndexPath := filepath.Join(registry.StorageDir().Path, channel+".json")
newIndexPath := filepath.Join(registry.StorageDir().Path, channel+".v2.json")

// Print preview.
fmt.Printf("%s (%s):\n", channel, indexFilePath)
fmt.Println(string(versionData))
fmt.Printf("%s\n%s\n%s\n\n", channel, oldIndexPath, newIndexPath)
fmt.Println(string(confirmData))

// Ask for confirmation.
if !confirm("\nDo you want to write this index?") {
fmt.Println("aborted...")
return nil
}

// Write new index to disk.
err = ioutil.WriteFile(indexFilePath, versionData, 0o0644) //nolint:gosec // 0644 is intended
// Write indexes.
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 {
return err
}

// Write to disk.
err = ioutil.WriteFile(path, jsonData, 0o0644) //nolint:gosec
if err != nil {
return err
}
fmt.Printf("written %s\n", indexFilePath)

fmt.Printf("written %s\n", path)
return nil
}

Expand Down
113 changes: 59 additions & 54 deletions cmds/updatemgr/sign.go
Original file line number Diff line number Diff line change
@@ -1,26 +1,19 @@
package main

import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"

"github.com/spf13/cobra"

"github.com/safing/jess"
"github.com/safing/jess/filesig"
"github.com/safing/jess/truststores"
"github.com/safing/portbase/formats/dsd"
"github.com/safing/portbase/updater"
)

const letterFileExtension = ".letter"

func init() {
rootCmd.AddCommand(signCmd)
signCmd.PersistentFlags().StringVarP(&envelopeName, "envelope", "", "",
Expand All @@ -33,6 +26,7 @@ func init() {
signCmd.PersistentFlags().StringVarP(&trustStoreKeyring, "tskeyring", "", "",
"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)
}

Expand Down Expand Up @@ -68,7 +62,7 @@ func sign(cmd *cobra.Command, args []string) error {

// Get all resources and iterate over all versions.
export := registry.Export()
var fails int
var verified, signed, fails int
for _, rv := range export {
for _, version := range rv.Versions {
file := version.GetFile()
Expand All @@ -89,6 +83,7 @@ func sign(cmd *cobra.Command, args []string) error {
fails++
} else {
fmt.Printf("[ OK ] valid signature for %s: signed by %s\n", file.Path(), getSignedByMany(fileData, trustStore))
verified++
}

case os.IsNotExist(err):
Expand All @@ -105,6 +100,7 @@ func sign(cmd *cobra.Command, args []string) error {
fails++
} else {
fmt.Printf("[SIGN] signed %s with %s\n", file.Path(), getSignedBySingle(fileData, trustStore))
signed++
}

default:
Expand All @@ -122,12 +118,6 @@ func sign(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.
trustStore, err := setupTrustStore()
if err != nil {
Expand All @@ -140,54 +130,69 @@ func signIndex(cmd *cobra.Command, args []string) error {
return err
}

// Read index file.
indexData, err := ioutil.ReadFile(indexFilePath)
if err != nil {
return fmt.Errorf("failed to read index file %s: %w", indexFilePath, err)
// Resolve globs.
files := make([]string, 0, len(args))
for _, arg := range args {
matches, err := filepath.Glob(arg)
if err != nil {
return err
}
files = append(files, matches...)
}

// Load index.
resourceVersions := make(map[string]string)
err = json.Unmarshal(indexData, &resourceVersions)
if err != nil {
return fmt.Errorf("failed to parse index file: %w", err)
}
// Go through all files.
var fails int
for _, file := range files {
sigFile := file + filesig.Extension

// Create signed index file structure.
index := updater.IndexFile{
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,
}
// Ignore matches for the signatures.
if strings.HasSuffix(file, filesig.Extension) {
continue
}

// Serialize index.
indexData, err = dsd.Dump(index, dsd.CBOR)
if err != nil {
return fmt.Errorf("failed to serialize index structure: %w", err)
}
// 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
}

// 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)
}
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))
}

// 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)
default:
// File access error.
fmt.Printf("[FAIL] failed to access %s: %s\n", sigFile, err)
fails++
}
}

if fails > 0 {
return fmt.Errorf("signing or checking failed on %d files", fails)
}
return nil
}

Expand Down

0 comments on commit 168cf01

Please sign in to comment.