From 3341bd0e945341528033ec6ebaef4f611f654ebe Mon Sep 17 00:00:00 2001 From: axtloss Date: Wed, 28 Feb 2024 23:42:26 +0100 Subject: restructure repository layout --- cmd/root.go | 22 ---- cmd/verify.go | 174 ------------------------------- config/config.go | 15 --- core/crypt.go | 27 ----- core/storage.go | 246 -------------------------------------------- core/verification.go | 165 ----------------------------- fbwarn/src/warn.c | 2 +- go.mod | 18 ---- go.sum | 29 ------ main.go | 13 --- verify/cmd/root.go | 22 ++++ verify/cmd/verify.go | 174 +++++++++++++++++++++++++++++++ verify/config/config.go | 15 +++ verify/core/crypt.go | 27 +++++ verify/core/storage.go | 246 ++++++++++++++++++++++++++++++++++++++++++++ verify/core/verification.go | 165 +++++++++++++++++++++++++++++ verify/fsverify | Bin 0 -> 6109315 bytes verify/go.mod | 18 ++++ verify/go.sum | 29 ++++++ verify/main.go | 13 +++ 20 files changed, 710 insertions(+), 710 deletions(-) delete mode 100644 cmd/root.go delete mode 100644 cmd/verify.go delete mode 100644 config/config.go delete mode 100644 core/crypt.go delete mode 100644 core/storage.go delete mode 100644 core/verification.go delete mode 100644 go.mod delete mode 100644 go.sum delete mode 100644 main.go create mode 100644 verify/cmd/root.go create mode 100644 verify/cmd/verify.go create mode 100644 verify/config/config.go create mode 100644 verify/core/crypt.go create mode 100644 verify/core/storage.go create mode 100644 verify/core/verification.go create mode 100755 verify/fsverify create mode 100644 verify/go.mod create mode 100644 verify/go.sum create mode 100644 verify/main.go diff --git a/cmd/root.go b/cmd/root.go deleted file mode 100644 index d212e4e..0000000 --- a/cmd/root.go +++ /dev/null @@ -1,22 +0,0 @@ -package cmd - -import ( - "github.com/spf13/cobra" - "os" -) - -var rootCmd = &cobra.Command{ - Use: "fsverify", -} - -func init() { - rootCmd.AddCommand(NewVerifyCommand()) -} - -func Execute() { - // cobra does not exit with a non-zero return code when failing - // solution from https://github.com/spf13/cobra/issues/221 - if err := rootCmd.Execute(); err != nil { - os.Exit(1) - } -} diff --git a/cmd/verify.go b/cmd/verify.go deleted file mode 100644 index d0360f6..0000000 --- a/cmd/verify.go +++ /dev/null @@ -1,174 +0,0 @@ -package cmd - -import ( - "bytes" - "fmt" - "math" - "os" - "sync" - - "github.com/axtloss/fsverify/config" - "github.com/axtloss/fsverify/core" - "github.com/spf13/cobra" -) - -var validateFailed bool - -func NewVerifyCommand() *cobra.Command { - cmd := &cobra.Command{ - Use: "verify", - Short: "Verify the root filesystem based on the given verification", - RunE: ValidateCommand, - SilenceUsage: true, - } - - return cmd -} - -// validateThread validates a chain of nodes against a given byte slice -func validateThread(blockStart int, blockEnd int, bundleSize int, diskBytes []byte, n int, dbfile string, waitGroup *sync.WaitGroup, errChan chan error) { - defer waitGroup.Done() - defer close(errChan) - var reader *bytes.Reader - blockCount := math.Floor(float64(bundleSize / 2000)) - totalReadBlocks := 0 - - db, err := core.OpenDB(dbfile, true) - if err != nil { - errChan <- err - } - - reader = bytes.NewReader(diskBytes) - - node, err := core.GetNode(fmt.Sprintf("Entrypoint%d", n), db) - if err != nil { - errChan <- err - } - block, i, err := core.ReadBlock(node, reader, totalReadBlocks) - totalReadBlocks = i - - err = core.VerifyBlock(block, node) - if err != nil { - errChan <- err - } - - for int64(totalReadBlocks) < int64(blockCount) { - if validateFailed { - return - } - nodeSum, err := node.GetHash() - if err != nil { - fmt.Println("Using node ", nodeSum) - errChan <- err - } - node, err = core.GetNode(nodeSum, db) - if err != nil { - fmt.Println("Failed to get next node") - errChan <- err - } - part, i, err := core.ReadBlock(node, reader, totalReadBlocks) - totalReadBlocks = i - if err != nil { - errChan <- err - validateFailed = true - return - } - err = core.VerifyBlock(part, node) - if err != nil { - errChan <- err - validateFailed = true - return - } - - } - -} - -func ValidateCommand(_ *cobra.Command, args []string) error { - if len(args) != 1 { - return fmt.Errorf("Usage: fsverify verify [disk]") - } - header, err := core.ReadHeader(config.FsVerifyPart) - if err != nil { - return err - } - - // Check if the partition is even correct - // this does not check if the partition has been tampered with - // it only checks if the specified partition is even an fsverify partition - if header.MagicNumber != 0xACAB { - return fmt.Errorf("sanity bit does not match. Expected %d, got %d", 0xACAB, header.MagicNumber) - } - - fmt.Println("Reading DB") - dbfile, err := core.ReadDB(config.FsVerifyPart) - if err != nil { - return err - } - key, err := core.ReadKey() - if err != nil { - return err - } - fmt.Println("Key: " + key) - verified, err := core.VerifySignature(key, header.Signature, dbfile) - if err != nil { - return err - } else if !verified { - return fmt.Errorf("Signature verification failed\n") - } else { - fmt.Println("Signature verification success!") - } - - fmt.Println("----") - disk, err := os.Open(args[0]) - if err != nil { - return err - } - defer disk.Close() - diskInfo, err := disk.Stat() - if err != nil { - return err - } - diskSize := diskInfo.Size() - - // If the filesystem size has increased ever since the fsverify partition was created - // it would mean that fsverify is not able to verify the entire partition, making it useless - if header.FilesystemSize*header.FilesystemUnit != int(diskSize) { - return fmt.Errorf("disk size does not match disk size specified in header. Expected %d, got %d", header.FilesystemSize*header.FilesystemUnit, diskSize) - } - - bundleSize := math.Floor(float64(diskSize / int64(config.ProcCount))) - diskBytes := make([]byte, diskSize) - _, err = disk.Read(diskBytes) - if err != nil { - return err - } - reader := bytes.NewReader(diskBytes) - var waitGroup sync.WaitGroup - errChan := make(chan error) - validateFailed = false - for i := 0; i < config.ProcCount; i++ { - // To ensure that each thread only uses the byte area it is meant to use, a copy of the - // area is made - diskBytes, err := core.CopyByteArea(i*(int(bundleSize)), (i+1)*(int(bundleSize)), reader) - if err != nil { - fmt.Println("Failed to copy byte area ", i*int(bundleSize), " ", (i+1)+int(bundleSize)) - return err - } - waitGroup.Add(1) - go validateThread(i*int(bundleSize), (i+1)*int(bundleSize), int(bundleSize), diskBytes, i, dbfile, &waitGroup, errChan) - } - - go func() { - waitGroup.Wait() - close(errChan) - }() - - for err := range errChan { - if err != nil { - return err - } - } - - return nil -} diff --git a/config/config.go b/config/config.go deleted file mode 100644 index 965243d..0000000 --- a/config/config.go +++ /dev/null @@ -1,15 +0,0 @@ -package config - -// How the public key is stored -// 0: external file, 1: external storage device, 2: tpm2, 3: usb serial -var KeyStore = 3 - -// Where the public key is stored, only applies for KeyStore = 0, 1 and 3 -var KeyLocation = "/dev/ttyACM1" - -// The amount of threads the DB was created with, has to be the amount of processes -// verifysetup was set to use -var ProcCount = 12 - -// Which partition/file to use as the fsverify partition -var FsVerifyPart = "./verifysetup/part.fsverify" diff --git a/core/crypt.go b/core/crypt.go deleted file mode 100644 index 067a0b3..0000000 --- a/core/crypt.go +++ /dev/null @@ -1,27 +0,0 @@ -package core - -import ( - "bytes" - "crypto/sha1" - "fmt" - "io" - "strings" -) - -// calculateStringHash calculates the sha1 checksum of a given string a. -func calculateStringHash(a string) (string, error) { - hash := sha1.New() - hash.Write([]byte(a)) - hashInBytes := hash.Sum(nil)[:20] - return strings.TrimSpace(fmt.Sprintf("%x", hashInBytes)), nil -} - -// CalculateBlockHash calculates the sha1 checksum of a given byte slice b. -func CalculateBlockHash(b []byte) (string, error) { - hash := sha1.New() - if _, err := io.Copy(hash, bytes.NewReader(b)); err != nil { - return "", err - } - hashInBytes := hash.Sum(nil)[:20] - return strings.TrimSpace(fmt.Sprintf("%x", hashInBytes)), nil -} diff --git a/core/storage.go b/core/storage.go deleted file mode 100644 index 182e1ec..0000000 --- a/core/storage.go +++ /dev/null @@ -1,246 +0,0 @@ -package core - -import ( - "bufio" - "bytes" - "encoding/binary" - "encoding/json" - "fmt" - bolt "go.etcd.io/bbolt" - "io" - "os" -) - -// Header contains all information stored in the header of a fsverify partition. -type Header struct { - MagicNumber int - Signature string - FilesystemSize int - FilesystemUnit int - TableSize int - TableUnit int -} - -// Node contains all information stored in a database node. -// If the Node is the first node in the database, PrevNodeSum should be set to Entrypoint. -type Node struct { - BlockStart int - BlockEnd int - BlockSum string - PrevNodeSum string -} - -// GetHash returns the hash of all fields of a Node combined. -// The Node fields are combined in the order BlockStart, BlockEnd, BlockSum and PrevNodeSum -func (n *Node) GetHash() (string, error) { - return calculateStringHash(fmt.Sprintf("%d%d%s%s", n.BlockStart, n.BlockEnd, n.BlockSum, n.PrevNodeSum)) -} - -// parseUnitSpec parses the file size unit specified in the header and returns it as an according multiplier. -// In the case of an invalid Unit byte the function returns -1. -func parseUnitSpec(size []byte) int { - switch size[0] { - case 0: - return 1 - case 1: - return 1000 - case 2: - return 1000 * 1000 - case 3: - return 1000 * 1000 * 10000 - case 4: - return 100000000000000 - case 5: - return 1000000000000000 - default: - return -1 - } -} - -// ReadHeader reads the partition header and puts it in a variable of type Header. -// If any field fails to be read, the function returns an empty Header struct and the error. -func ReadHeader(partition string) (Header, error) { - _, exist := os.Stat(partition) - if os.IsNotExist(exist) { - return Header{}, fmt.Errorf("Cannot find partition %s", partition) - } - part, err := os.Open(partition) - if err != nil { - return Header{}, err - } - defer part.Close() - - header := Header{} - reader := bufio.NewReader(part) - // Since the size of each field is already known - // it is best to hard code them, in the case - // that a field goes over its allocated size - // fsverify should (and will) fail - MagicNumber := make([]byte, 2) - UntrustedHash := make([]byte, 100) - TrustedHash := make([]byte, 88) - FilesystemSize := make([]byte, 4) - FilesystemUnit := make([]byte, 1) - TableSize := make([]byte, 4) - TableUnit := make([]byte, 1) - - _, err = reader.Read(MagicNumber) - MagicNum := binary.BigEndian.Uint16(MagicNumber) - if MagicNum != 0xACAB { // The Silliest of magic numbers - return Header{}, err - } - header.MagicNumber = int(MagicNum) - - _, err = reader.Read(UntrustedHash) - if err != nil { - return Header{}, err - } - _, err = reader.Read(TrustedHash) - if err != nil { - return Header{}, err - } - _, err = reader.Read(FilesystemSize) - if err != nil { - return Header{}, err - } - _, err = reader.Read(FilesystemUnit) - if err != nil { - return Header{}, err - } - _, err = reader.Read(TableSize) - if err != nil { - return Header{}, err - } - _, err = reader.Read(TableUnit) - if err != nil { - return Header{}, err - } - - header.Signature = fmt.Sprintf("untrusted comment: fsverify\n%s\ntrusted comment: fsverify\n%s\n", string(UntrustedHash), string(TrustedHash)) - header.FilesystemSize = int(binary.BigEndian.Uint32(FilesystemSize)) - header.TableSize = int(binary.BigEndian.Uint32(TableSize)) - header.FilesystemUnit = parseUnitSpec(FilesystemUnit) - header.TableUnit = parseUnitSpec(TableUnit) - if header.FilesystemUnit == -1 || header.TableUnit == -1 { - return Header{}, fmt.Errorf("unit size for Filesystem or Table invalid: fs: %x, table: %x", FilesystemUnit, TableUnit) - } - return header, nil -} - -// ReadDB reads the database from a fsverify partition. -// It verifies the the size of the database with the size specified in the partition header and returns an error if the sizes do not match. -// Due to limitations with bbolt the database gets written to a temporary path and the function returns the path to the database. -func ReadDB(partition string) (string, error) { - _, exist := os.Stat(partition) - if os.IsNotExist(exist) { - return "", fmt.Errorf("Cannot find partition %s", partition) - } - part, err := os.Open(partition) - if err != nil { - return "", err - } - defer part.Close() - reader := bufio.NewReader(part) - - // The area taken up by the header - // it is useless for this reader instance - // and will be skipped completely - _, err = reader.Read(make([]byte, 200)) - if err != nil { - fmt.Println(err) - return "", err - } - - header, err := ReadHeader(partition) - if err != nil { - fmt.Println(err) - return "", err - } - - // Reading the specified table size allows for tamper protection - // in the case that the partition was tampered with "lazily" - // meaning that only the database was modified, and not the header - // if that is the case, the database would be lacking data, making it unusable - db := make([]byte, header.TableSize*header.TableUnit) - n, err := io.ReadFull(reader, db) - if err != nil { - return "", err - } - if n != header.TableSize*header.TableUnit { - return "", fmt.Errorf("Database is not expected size. Expected %d, got %d", header.TableSize*header.TableUnit, n) - } - fmt.Printf("db: %d\n", n) - - // Write the database to a temporary directory - // to ensure that it disappears after the next reboot - temp, err := os.MkdirTemp("", "*-fsverify") - if err != nil { - return "", err - } - - // The file permission is immediately set to 0700 - // this ensures that the database is not modified - // after it has been written - err = os.WriteFile(temp+"/verify.db", db, 0700) - if err != nil { - return "", err - } - - return temp + "/verify.db", nil -} - -// OpenDB opens a bbolt database and returns a bbolt instance. -func OpenDB(dbpath string, readonly bool) (*bolt.DB, error) { - _, exist := os.Stat(dbpath) - if os.IsNotExist(exist) { - os.Create(dbpath) - } - db, err := bolt.Open(dbpath, 0777, &bolt.Options{ReadOnly: readonly}) - if err != nil { - return nil, err - } - return db, nil -} - -// GetNode retrieves a Node from the database based on the hash identifier. -// If db is set to nil, the function will open the database in read-only mode itself. -func GetNode(checksum string, db *bolt.DB) (Node, error) { - var err error - var deferDB bool - if db == nil { - db, err = OpenDB("my.db", true) - if err != nil { - return Node{}, err - } - deferDB = true - } - var node Node - err = db.View(func(tx *bolt.Tx) error { - nodes := tx.Bucket([]byte("Nodes")) - app := nodes.Get([]byte(checksum)) - err := json.Unmarshal(app, &node) - return err - }) - if deferDB { - defer db.Close() - } - return node, err -} - -// CopyByteArea copies an area of bytes from a reader. -// It verifies that the reader reads the wanted amount of bytes, and returns an error if this is not the case. -func CopyByteArea(start int, end int, reader *bytes.Reader) ([]byte, error) { - if end-start < 0 { - return []byte{}, fmt.Errorf("tried creating byte slice with negative length. %d to %d total %d\n", start, end, end-start) - } else if end-start > 2000 { - return []byte{}, fmt.Errorf("tried creating byte slice with length over 2000. %d to %d total %d\n", start, end, end-start) - } - bytes := make([]byte, end-start) - n, err := reader.ReadAt(bytes, int64(start)) - if err != nil { - return nil, err - } else if n != end-start { - return nil, fmt.Errorf("Unable to read requested size. Expected %d, got %d", end-start, n) - } - return bytes, nil -} diff --git a/core/verification.go b/core/verification.go deleted file mode 100644 index 289ce1e..0000000 --- a/core/verification.go +++ /dev/null @@ -1,165 +0,0 @@ -package core - -import ( - "bufio" - "bytes" - "fmt" - "os" - "strings" - - "aead.dev/minisign" - "github.com/axtloss/fsverify/config" - "github.com/tarm/serial" -) - -// fileReadKey reads the public minisign key from a file specified in config.KeyLocation. -func fileReadKey() (string, error) { - if _, err := os.Stat(config.KeyLocation); os.IsNotExist(err) { - return "", fmt.Errorf("Key location %s does not exist", config.KeyLocation) - } - file, err := os.Open(config.KeyLocation) - if err != nil { - return "", err - } - defer file.Close() - // A public key is never longer than 56 bytes - key := make([]byte, 56) - reader := bufio.NewReader(file) - n, err := reader.Read(key) - if n != 56 { - return "", fmt.Errorf("Key does not match expected key size. Expected 56, got %d", n) - } - if err != nil { - return "", err - } - return string(key), nil -} - -// serialReadKey reads the public minisign key from a usb tty specified in config.KeyLocation. -func serialReadKey() (string, error) { - // Since the usb serial is tested with an arduino - // it is assumed that the tty device does not always exist - // and can be manually plugged in by the user - if _, err := os.Stat(config.KeyLocation); !os.IsNotExist(err) { - fmt.Println("Reconnect arduino now") - for true { - if _, err := os.Stat(config.KeyLocation); os.IsNotExist(err) { - break - } - } - } else { - fmt.Println("Connect arduino now") - } - for true { - if _, err := os.Stat(config.KeyLocation); !os.IsNotExist(err) { - break - } - } - fmt.Println("Arduino connected") - c := &serial.Config{Name: config.KeyLocation, Baud: 9600} - s, err := serial.OpenPort(c) - if err != nil { - return "", err - } - - key := "" - for true { - buf := make([]byte, 128) - n, err := s.Read(buf) - if err != nil { - return "", err - } - defer s.Close() - key = key + fmt.Sprintf("%q", buf[:n]) - // ensure that two tab sequences are read - // meaning that the entire key has been captured - // since the key is surrounded by a tab sequence - if strings.Count(key, "\\t") == 2 { - break - } - } - key = strings.ReplaceAll(key, "\\t", "") - key = strings.ReplaceAll(key, "\"", "") - if len(key) != 56 { - return "", fmt.Errorf("Key does not match expected key size. Expected 56, got %d", len(key)) - } - return key, nil -} - -// ReadKey is a wrapper function to call the proper readKey function according to config.KeyStore. -func ReadKey() (string, error) { - switch config.KeyStore { - case 0: - return fileReadKey() - case 1: - return fileReadKey() - case 2: - return "", nil // TPM - case 3: - return serialReadKey() - } - return "", nil -} - -// ReadBlock reads a data area of a bytes.Reader specified in the given node. -// It additionally verifies that the amount of bytes read equal the wanted amount and returns an error if this is not the case. -func ReadBlock(node Node, part *bytes.Reader, totalReadBlocks int) ([]byte, int, error) { - if node.BlockEnd-node.BlockStart < 0 { - return []byte{}, -1, fmt.Errorf("tried creating byte slice with negative length. %d to %d total %d\n", node.BlockStart, node.BlockEnd, node.BlockEnd-node.BlockStart) - } else if node.BlockEnd-node.BlockStart > 2000 { - return []byte{}, -1, fmt.Errorf("tried creating byte slice with length over 2000. %d to %d total %d\n", node.BlockStart, node.BlockEnd, node.BlockEnd-node.BlockStart) - } - block := make([]byte, node.BlockEnd-node.BlockStart) - blockSize := node.BlockEnd - node.BlockStart - _, err := part.Seek(int64(node.BlockStart), 0) - if err != nil { - return []byte{}, -1, err - } - n, err := part.Read(block) - if err != nil { - return block, -1, err - } else if n != blockSize { - return block, -1, fmt.Errorf("Did not read correct amount of bytes. Expected: %d, Got: %d", blockSize, n) - } - return block, totalReadBlocks + 1, err -} - -// VerifySignature verifies the database using a given signature and public key. -func VerifySignature(key string, signature string, database string) (bool, error) { - var pk minisign.PublicKey - if err := pk.UnmarshalText([]byte(key)); err != nil { - return false, err - } - - data, err := os.ReadFile(database) - if err != nil { - return false, err - } - - return minisign.Verify(pk, data, []byte(signature)), nil -} - -// VerifyBlock verifies a byte slice with the hash in a given Node. -func VerifyBlock(block []byte, node Node) error { - calculatedBlockHash, err := CalculateBlockHash(block) - if err != nil { - return err - } - wantedBlockHash := node.BlockSum - if strings.Compare(calculatedBlockHash, strings.TrimSpace(wantedBlockHash)) == 0 { - return nil - } - return fmt.Errorf("Node %s ranging from %d to %d does not match block. Expected %s, got %s.", node.PrevNodeSum, node.BlockStart, node.BlockEnd, wantedBlockHash, calculatedBlockHash) -} - -// VerifyNode verifies that the current Node is valid by matching the checksum of it with the PrevNodeSum field of the next node. -func VerifyNode(node Node, nextNode Node) error { - nodeHash, err := calculateStringHash(fmt.Sprintf("%d%d%s%s", node.BlockStart, node.BlockEnd, node.BlockSum, node.PrevNodeSum)) - if err != nil { - return err - } - if strings.Compare(nodeHash, nextNode.PrevNodeSum) != 0 { - return fmt.Errorf("Node %s is not valid!", node.PrevNodeSum) - } - return nil -} diff --git a/fbwarn/src/warn.c b/fbwarn/src/warn.c index 70e4baf..9786883 100644 --- a/fbwarn/src/warn.c +++ b/fbwarn/src/warn.c @@ -80,7 +80,7 @@ int main(int argc, char **argv) { collectArgs(args, callTrim, 2); BVGIMG *imgsize = BVGParseIMG(args); - InitWindow (imgsize->width*scale, imgsize->height*scale, ":3"); + InitWindow (imgsize->width*scale, imgsize->height*scale, argv[1]); free(imgsize); free(call-strlen("IMG (")); diff --git a/go.mod b/go.mod deleted file mode 100644 index 1d863fa..0000000 --- a/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module github.com/axtloss/fsverify - -go 1.21.6 - -require ( - github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 - github.com/spf13/cobra v1.8.0 - github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 - go.etcd.io/bbolt v1.3.8 -) - -require ( - aead.dev/minisign v0.2.1 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/crypto v0.17.0 // indirect - golang.org/x/sys v0.15.0 // indirect -) diff --git a/go.sum b/go.sum deleted file mode 100644 index a83ed25..0000000 --- a/go.sum +++ /dev/null @@ -1,29 +0,0 @@ -aead.dev/minisign v0.2.1 h1:Z+7HA9dsY/eGycYj6kpWHpcJpHtjAwGiJFvbiuO9o+M= -aead.dev/minisign v0.2.1/go.mod h1:oCOjeA8VQNEbuSCFaaUXKekOusa/mll6WtMoO5JY4M4= -github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 h1:TMtDYDHKYY15rFihtRfck/bfFqNfvcabqvXAFQfAUpY= -github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267/go.mod h1:h1nSAbGFqGVzn6Jyl1R/iCcBUHN4g+gW1u9CoBTrb9E= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= -github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 h1:UyzmZLoiDWMRywV4DUYb9Fbt8uiOSooupjTq10vpvnU= -github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= -go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA= -go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go deleted file mode 100644 index 4500a61..0000000 --- a/main.go +++ /dev/null @@ -1,13 +0,0 @@ -package main - -import ( - "github.com/axtloss/fsverify/cmd" -) - -var ( - Version = "0.1.0" -) - -func main() { - cmd.Execute() -} diff --git a/verify/cmd/root.go b/verify/cmd/root.go new file mode 100644 index 0000000..d212e4e --- /dev/null +++ b/verify/cmd/root.go @@ -0,0 +1,22 @@ +package cmd + +import ( + "github.com/spf13/cobra" + "os" +) + +var rootCmd = &cobra.Command{ + Use: "fsverify", +} + +func init() { + rootCmd.AddCommand(NewVerifyCommand()) +} + +func Execute() { + // cobra does not exit with a non-zero return code when failing + // solution from https://github.com/spf13/cobra/issues/221 + if err := rootCmd.Execute(); err != nil { + os.Exit(1) + } +} diff --git a/verify/cmd/verify.go b/verify/cmd/verify.go new file mode 100644 index 0000000..d0360f6 --- /dev/null +++ b/verify/cmd/verify.go @@ -0,0 +1,174 @@ +package cmd + +import ( + "bytes" + "fmt" + "math" + "os" + "sync" + + "github.com/axtloss/fsverify/config" + "github.com/axtloss/fsverify/core" + "github.com/spf13/cobra" +) + +var validateFailed bool + +func NewVerifyCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "verify", + Short: "Verify the root filesystem based on the given verification", + RunE: ValidateCommand, + SilenceUsage: true, + } + + return cmd +} + +// validateThread validates a chain of nodes against a given byte slice +func validateThread(blockStart int, blockEnd int, bundleSize int, diskBytes []byte, n int, dbfile string, waitGroup *sync.WaitGroup, errChan chan error) { + defer waitGroup.Done() + defer close(errChan) + var reader *bytes.Reader + blockCount := math.Floor(float64(bundleSize / 2000)) + totalReadBlocks := 0 + + db, err := core.OpenDB(dbfile, true) + if err != nil { + errChan <- err + } + + reader = bytes.NewReader(diskBytes) + + node, err := core.GetNode(fmt.Sprintf("Entrypoint%d", n), db) + if err != nil { + errChan <- err + } + block, i, err := core.ReadBlock(node, reader, totalReadBlocks) + totalReadBlocks = i + + err = core.VerifyBlock(block, node) + if err != nil { + errChan <- err + } + + for int64(totalReadBlocks) < int64(blockCount) { + if validateFailed { + return + } + nodeSum, err := node.GetHash() + if err != nil { + fmt.Println("Using node ", nodeSum) + errChan <- err + } + node, err = core.GetNode(nodeSum, db) + if err != nil { + fmt.Println("Failed to get next node") + errChan <- err + } + part, i, err := core.ReadBlock(node, reader, totalReadBlocks) + totalReadBlocks = i + if err != nil { + errChan <- err + validateFailed = true + return + } + err = core.VerifyBlock(part, node) + if err != nil { + errChan <- err + validateFailed = true + return + } + + } + +} + +func ValidateCommand(_ *cobra.Command, args []string) error { + if len(args) != 1 { + return fmt.Errorf("Usage: fsverify verify [disk]") + } + header, err := core.ReadHeader(config.FsVerifyPart) + if err != nil { + return err + } + + // Check if the partition is even correct + // this does not check if the partition has been tampered with + // it only checks if the specified partition is even an fsverify partition + if header.MagicNumber != 0xACAB { + return fmt.Errorf("sanity bit does not match. Expected %d, got %d", 0xACAB, header.MagicNumber) + } + + fmt.Println("Reading DB") + dbfile, err := core.ReadDB(config.FsVerifyPart) + if err != nil { + return err + } + key, err := core.ReadKey() + if err != nil { + return err + } + fmt.Println("Key: " + key) + verified, err := core.VerifySignature(key, header.Signature, dbfile) + if err != nil { + return err + } else if !verified { + return fmt.Errorf("Signature verification failed\n") + } else { + fmt.Println("Signature verification success!") + } + + fmt.Println("----") + disk, err := os.Open(args[0]) + if err != nil { + return err + } + defer disk.Close() + diskInfo, err := disk.Stat() + if err != nil { + return err + } + diskSize := diskInfo.Size() + + // If the filesystem size has increased ever since the fsverify partition was created + // it would mean that fsverify is not able to verify the entire partition, making it useless + if header.FilesystemSize*header.FilesystemUnit != int(diskSize) { + return fmt.Errorf("disk size does not match disk size specified in header. Expected %d, got %d", header.FilesystemSize*header.FilesystemUnit, diskSize) + } + + bundleSize := math.Floor(float64(diskSize / int64(config.ProcCount))) + diskBytes := make([]byte, diskSize) + _, err = disk.Read(diskBytes) + if err != nil { + return err + } + reader := bytes.NewReader(diskBytes) + var waitGroup sync.WaitGroup + errChan := make(chan error) + validateFailed = false + for i := 0; i < config.ProcCount; i++ { + // To ensure that each thread only uses the byte area it is meant to use, a copy of the + // area is made + diskBytes, err := core.CopyByteArea(i*(int(bundleSize)), (i+1)*(int(bundleSize)), reader) + if err != nil { + fmt.Println("Failed to copy byte area ", i*int(bundleSize), " ", (i+1)+int(bundleSize)) + return err + } + waitGroup.Add(1) + go validateThread(i*int(bundleSize), (i+1)*int(bundleSize), int(bundleSize), diskBytes, i, dbfile, &waitGroup, errChan) + } + + go func() { + waitGroup.Wait() + close(errChan) + }() + + for err := range errChan { + if err != nil { + return err + } + } + + return nil +} diff --git a/verify/config/config.go b/verify/config/config.go new file mode 100644 index 0000000..965243d --- /dev/null +++ b/verify/config/config.go @@ -0,0 +1,15 @@ +package config + +// How the public key is stored +// 0: external file, 1: external storage device, 2: tpm2, 3: usb serial +var KeyStore = 3 + +// Where the public key is stored, only applies for KeyStore = 0, 1 and 3 +var KeyLocation = "/dev/ttyACM1" + +// The amount of threads the DB was created with, has to be the amount of processes +// verifysetup was set to use +var ProcCount = 12 + +// Which partition/file to use as the fsverify partition +var FsVerifyPart = "./verifysetup/part.fsverify" diff --git a/verify/core/crypt.go b/verify/core/crypt.go new file mode 100644 index 0000000..067a0b3 --- /dev/null +++ b/verify/core/crypt.go @@ -0,0 +1,27 @@ +package core + +import ( + "bytes" + "crypto/sha1" + "fmt" + "io" + "strings" +) + +// calculateStringHash calculates the sha1 checksum of a given string a. +func calculateStringHash(a string) (string, error) { + hash := sha1.New() + hash.Write([]byte(a)) + hashInBytes := hash.Sum(nil)[:20] + return strings.TrimSpace(fmt.Sprintf("%x", hashInBytes)), nil +} + +// CalculateBlockHash calculates the sha1 checksum of a given byte slice b. +func CalculateBlockHash(b []byte) (string, error) { + hash := sha1.New() + if _, err := io.Copy(hash, bytes.NewReader(b)); err != nil { + return "", err + } + hashInBytes := hash.Sum(nil)[:20] + return strings.TrimSpace(fmt.Sprintf("%x", hashInBytes)), nil +} diff --git a/verify/core/storage.go b/verify/core/storage.go new file mode 100644 index 0000000..182e1ec --- /dev/null +++ b/verify/core/storage.go @@ -0,0 +1,246 @@ +package core + +import ( + "bufio" + "bytes" + "encoding/binary" + "encoding/json" + "fmt" + bolt "go.etcd.io/bbolt" + "io" + "os" +) + +// Header contains all information stored in the header of a fsverify partition. +type Header struct { + MagicNumber int + Signature string + FilesystemSize int + FilesystemUnit int + TableSize int + TableUnit int +} + +// Node contains all information stored in a database node. +// If the Node is the first node in the database, PrevNodeSum should be set to Entrypoint. +type Node struct { + BlockStart int + BlockEnd int + BlockSum string + PrevNodeSum string +} + +// GetHash returns the hash of all fields of a Node combined. +// The Node fields are combined in the order BlockStart, BlockEnd, BlockSum and PrevNodeSum +func (n *Node) GetHash() (string, error) { + return calculateStringHash(fmt.Sprintf("%d%d%s%s", n.BlockStart, n.BlockEnd, n.BlockSum, n.PrevNodeSum)) +} + +// parseUnitSpec parses the file size unit specified in the header and returns it as an according multiplier. +// In the case of an invalid Unit byte the function returns -1. +func parseUnitSpec(size []byte) int { + switch size[0] { + case 0: + return 1 + case 1: + return 1000 + case 2: + return 1000 * 1000 + case 3: + return 1000 * 1000 * 10000 + case 4: + return 100000000000000 + case 5: + return 1000000000000000 + default: + return -1 + } +} + +// ReadHeader reads the partition header and puts it in a variable of type Header. +// If any field fails to be read, the function returns an empty Header struct and the error. +func ReadHeader(partition string) (Header, error) { + _, exist := os.Stat(partition) + if os.IsNotExist(exist) { + return Header{}, fmt.Errorf("Cannot find partition %s", partition) + } + part, err := os.Open(partition) + if err != nil { + return Header{}, err + } + defer part.Close() + + header := Header{} + reader := bufio.NewReader(part) + // Since the size of each field is already known + // it is best to hard code them, in the case + // that a field goes over its allocated size + // fsverify should (and will) fail + MagicNumber := make([]byte, 2) + UntrustedHash := make([]byte, 100) + TrustedHash := make([]byte, 88) + FilesystemSize := make([]byte, 4) + FilesystemUnit := make([]byte, 1) + TableSize := make([]byte, 4) + TableUnit := make([]byte, 1) + + _, err = reader.Read(MagicNumber) + MagicNum := binary.BigEndian.Uint16(MagicNumber) + if MagicNum != 0xACAB { // The Silliest of magic numbers + return Header{}, err + } + header.MagicNumber = int(MagicNum) + + _, err = reader.Read(UntrustedHash) + if err != nil { + return Header{}, err + } + _, err = reader.Read(TrustedHash) + if err != nil { + return Header{}, err + } + _, err = reader.Read(FilesystemSize) + if err != nil { + return Header{}, err + } + _, err = reader.Read(FilesystemUnit) + if err != nil { + return Header{}, err + } + _, err = reader.Read(TableSize) + if err != nil { + return Header{}, err + } + _, err = reader.Read(TableUnit) + if err != nil { + return Header{}, err + } + + header.Signature = fmt.Sprintf("untrusted comment: fsverify\n%s\ntrusted comment: fsverify\n%s\n", string(UntrustedHash), string(TrustedHash)) + header.FilesystemSize = int(binary.BigEndian.Uint32(FilesystemSize)) + header.TableSize = int(binary.BigEndian.Uint32(TableSize)) + header.FilesystemUnit = parseUnitSpec(FilesystemUnit) + header.TableUnit = parseUnitSpec(TableUnit) + if header.FilesystemUnit == -1 || header.TableUnit == -1 { + return Header{}, fmt.Errorf("unit size for Filesystem or Table invalid: fs: %x, table: %x", FilesystemUnit, TableUnit) + } + return header, nil +} + +// ReadDB reads the database from a fsverify partition. +// It verifies the the size of the database with the size specified in the partition header and returns an error if the sizes do not match. +// Due to limitations with bbolt the database gets written to a temporary path and the function returns the path to the database. +func ReadDB(partition string) (string, error) { + _, exist := os.Stat(partition) + if os.IsNotExist(exist) { + return "", fmt.Errorf("Cannot find partition %s", partition) + } + part, err := os.Open(partition) + if err != nil { + return "", err + } + defer part.Close() + reader := bufio.NewReader(part) + + // The area taken up by the header + // it is useless for this reader instance + // and will be skipped completely + _, err = reader.Read(make([]byte, 200)) + if err != nil { + fmt.Println(err) + return "", err + } + + header, err := ReadHeader(partition) + if err != nil { + fmt.Println(err) + return "", err + } + + // Reading the specified table size allows for tamper protection + // in the case that the partition was tampered with "lazily" + // meaning that only the database was modified, and not the header + // if that is the case, the database would be lacking data, making it unusable + db := make([]byte, header.TableSize*header.TableUnit) + n, err := io.ReadFull(reader, db) + if err != nil { + return "", err + } + if n != header.TableSize*header.TableUnit { + return "", fmt.Errorf("Database is not expected size. Expected %d, got %d", header.TableSize*header.TableUnit, n) + } + fmt.Printf("db: %d\n", n) + + // Write the database to a temporary directory + // to ensure that it disappears after the next reboot + temp, err := os.MkdirTemp("", "*-fsverify") + if err != nil { + return "", err + } + + // The file permission is immediately set to 0700 + // this ensures that the database is not modified + // after it has been written + err = os.WriteFile(temp+"/verify.db", db, 0700) + if err != nil { + return "", err + } + + return temp + "/verify.db", nil +} + +// OpenDB opens a bbolt database and returns a bbolt instance. +func OpenDB(dbpath string, readonly bool) (*bolt.DB, error) { + _, exist := os.Stat(dbpath) + if os.IsNotExist(exist) { + os.Create(dbpath) + } + db, err := bolt.Open(dbpath, 0777, &bolt.Options{ReadOnly: readonly}) + if err != nil { + return nil, err + } + return db, nil +} + +// GetNode retrieves a Node from the database based on the hash identifier. +// If db is set to nil, the function will open the database in read-only mode itself. +func GetNode(checksum string, db *bolt.DB) (Node, error) { + var err error + var deferDB bool + if db == nil { + db, err = OpenDB("my.db", true) + if err != nil { + return Node{}, err + } + deferDB = true + } + var node Node + err = db.View(func(tx *bolt.Tx) error { + nodes := tx.Bucket([]byte("Nodes")) + app := nodes.Get([]byte(checksum)) + err := json.Unmarshal(app, &node) + return err + }) + if deferDB { + defer db.Close() + } + return node, err +} + +// CopyByteArea copies an area of bytes from a reader. +// It verifies that the reader reads the wanted amount of bytes, and returns an error if this is not the case. +func CopyByteArea(start int, end int, reader *bytes.Reader) ([]byte, error) { + if end-start < 0 { + return []byte{}, fmt.Errorf("tried creating byte slice with negative length. %d to %d total %d\n", start, end, end-start) + } else if end-start > 2000 { + return []byte{}, fmt.Errorf("tried creating byte slice with length over 2000. %d to %d total %d\n", start, end, end-start) + } + bytes := make([]byte, end-start) + n, err := reader.ReadAt(bytes, int64(start)) + if err != nil { + return nil, err + } else if n != end-start { + return nil, fmt.Errorf("Unable to read requested size. Expected %d, got %d", end-start, n) + } + return bytes, nil +} diff --git a/verify/core/verification.go b/verify/core/verification.go new file mode 100644 index 0000000..289ce1e --- /dev/null +++ b/verify/core/verification.go @@ -0,0 +1,165 @@ +package core + +import ( + "bufio" + "bytes" + "fmt" + "os" + "strings" + + "aead.dev/minisign" + "github.com/axtloss/fsverify/config" + "github.com/tarm/serial" +) + +// fileReadKey reads the public minisign key from a file specified in config.KeyLocation. +func fileReadKey() (string, error) { + if _, err := os.Stat(config.KeyLocation); os.IsNotExist(err) { + return "", fmt.Errorf("Key location %s does not exist", config.KeyLocation) + } + file, err := os.Open(config.KeyLocation) + if err != nil { + return "", err + } + defer file.Close() + // A public key is never longer than 56 bytes + key := make([]byte, 56) + reader := bufio.NewReader(file) + n, err := reader.Read(key) + if n != 56 { + return "", fmt.Errorf("Key does not match expected key size. Expected 56, got %d", n) + } + if err != nil { + return "", err + } + return string(key), nil +} + +// serialReadKey reads the public minisign key from a usb tty specified in config.KeyLocation. +func serialReadKey() (string, error) { + // Since the usb serial is tested with an arduino + // it is assumed that the tty device does not always exist + // and can be manually plugged in by the user + if _, err := os.Stat(config.KeyLocation); !os.IsNotExist(err) { + fmt.Println("Reconnect arduino now") + for true { + if _, err := os.Stat(config.KeyLocation); os.IsNotExist(err) { + break + } + } + } else { + fmt.Println("Connect arduino now") + } + for true { + if _, err := os.Stat(config.KeyLocation); !os.IsNotExist(err) { + break + } + } + fmt.Println("Arduino connected") + c := &serial.Config{Name: config.KeyLocation, Baud: 9600} + s, err := serial.OpenPort(c) + if err != nil { + return "", err + } + + key := "" + for true { + buf := make([]byte, 128) + n, err := s.Read(buf) + if err != nil { + return "", err + } + defer s.Close() + key = key + fmt.Sprintf("%q", buf[:n]) + // ensure that two tab sequences are read + // meaning that the entire key has been captured + // since the key is surrounded by a tab sequence + if strings.Count(key, "\\t") == 2 { + break + } + } + key = strings.ReplaceAll(key, "\\t", "") + key = strings.ReplaceAll(key, "\"", "") + if len(key) != 56 { + return "", fmt.Errorf("Key does not match expected key size. Expected 56, got %d", len(key)) + } + return key, nil +} + +// ReadKey is a wrapper function to call the proper readKey function according to config.KeyStore. +func ReadKey() (string, error) { + switch config.KeyStore { + case 0: + return fileReadKey() + case 1: + return fileReadKey() + case 2: + return "", nil // TPM + case 3: + return serialReadKey() + } + return "", nil +} + +// ReadBlock reads a data area of a bytes.Reader specified in the given node. +// It additionally verifies that the amount of bytes read equal the wanted amount and returns an error if this is not the case. +func ReadBlock(node Node, part *bytes.Reader, totalReadBlocks int) ([]byte, int, error) { + if node.BlockEnd-node.BlockStart < 0 { + return []byte{}, -1, fmt.Errorf("tried creating byte slice with negative length. %d to %d total %d\n", node.BlockStart, node.BlockEnd, node.BlockEnd-node.BlockStart) + } else if node.BlockEnd-node.BlockStart > 2000 { + return []byte{}, -1, fmt.Errorf("tried creating byte slice with length over 2000. %d to %d total %d\n", node.BlockStart, node.BlockEnd, node.BlockEnd-node.BlockStart) + } + block := make([]byte, node.BlockEnd-node.BlockStart) + blockSize := node.BlockEnd - node.BlockStart + _, err := part.Seek(int64(node.BlockStart), 0) + if err != nil { + return []byte{}, -1, err + } + n, err := part.Read(block) + if err != nil { + return block, -1, err + } else if n != blockSize { + return block, -1, fmt.Errorf("Did not read correct amount of bytes. Expected: %d, Got: %d", blockSize, n) + } + return block, totalReadBlocks + 1, err +} + +// VerifySignature verifies the database using a given signature and public key. +func VerifySignature(key string, signature string, database string) (bool, error) { + var pk minisign.PublicKey + if err := pk.UnmarshalText([]byte(key)); err != nil { + return false, err + } + + data, err := os.ReadFile(database) + if err != nil { + return false, err + } + + return minisign.Verify(pk, data, []byte(signature)), nil +} + +// VerifyBlock verifies a byte slice with the hash in a given Node. +func VerifyBlock(block []byte, node Node) error { + calculatedBlockHash, err := CalculateBlockHash(block) + if err != nil { + return err + } + wantedBlockHash := node.BlockSum + if strings.Compare(calculatedBlockHash, strings.TrimSpace(wantedBlockHash)) == 0 { + return nil + } + return fmt.Errorf("Node %s ranging from %d to %d does not match block. Expected %s, got %s.", node.PrevNodeSum, node.BlockStart, node.BlockEnd, wantedBlockHash, calculatedBlockHash) +} + +// VerifyNode verifies that the current Node is valid by matching the checksum of it with the PrevNodeSum field of the next node. +func VerifyNode(node Node, nextNode Node) error { + nodeHash, err := calculateStringHash(fmt.Sprintf("%d%d%s%s", node.BlockStart, node.BlockEnd, node.BlockSum, node.PrevNodeSum)) + if err != nil { + return err + } + if strings.Compare(nodeHash, nextNode.PrevNodeSum) != 0 { + return fmt.Errorf("Node %s is not valid!", node.PrevNodeSum) + } + return nil +} diff --git a/verify/fsverify b/verify/fsverify new file mode 100755 index 0000000..89d1dfe Binary files /dev/null and b/verify/fsverify differ diff --git a/verify/go.mod b/verify/go.mod new file mode 100644 index 0000000..1d863fa --- /dev/null +++ b/verify/go.mod @@ -0,0 +1,18 @@ +module github.com/axtloss/fsverify + +go 1.21.6 + +require ( + github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 + github.com/spf13/cobra v1.8.0 + github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 + go.etcd.io/bbolt v1.3.8 +) + +require ( + aead.dev/minisign v0.2.1 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + golang.org/x/crypto v0.17.0 // indirect + golang.org/x/sys v0.15.0 // indirect +) diff --git a/verify/go.sum b/verify/go.sum new file mode 100644 index 0000000..a83ed25 --- /dev/null +++ b/verify/go.sum @@ -0,0 +1,29 @@ +aead.dev/minisign v0.2.1 h1:Z+7HA9dsY/eGycYj6kpWHpcJpHtjAwGiJFvbiuO9o+M= +aead.dev/minisign v0.2.1/go.mod h1:oCOjeA8VQNEbuSCFaaUXKekOusa/mll6WtMoO5JY4M4= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 h1:TMtDYDHKYY15rFihtRfck/bfFqNfvcabqvXAFQfAUpY= +github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267/go.mod h1:h1nSAbGFqGVzn6Jyl1R/iCcBUHN4g+gW1u9CoBTrb9E= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 h1:UyzmZLoiDWMRywV4DUYb9Fbt8uiOSooupjTq10vpvnU= +github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= +go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA= +go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/verify/main.go b/verify/main.go new file mode 100644 index 0000000..4500a61 --- /dev/null +++ b/verify/main.go @@ -0,0 +1,13 @@ +package main + +import ( + "github.com/axtloss/fsverify/cmd" +) + +var ( + Version = "0.1.0" +) + +func main() { + cmd.Execute() +} -- cgit v1.2.3