diff options
Diffstat (limited to 'core')
-rw-r--r-- | core/crypt.go | 6 | ||||
-rw-r--r-- | core/storage.go | 40 | ||||
-rw-r--r-- | core/verification.go | 24 |
3 files changed, 63 insertions, 7 deletions
diff --git a/core/crypt.go b/core/crypt.go index 19a8420..067a0b3 100644 --- a/core/crypt.go +++ b/core/crypt.go @@ -8,6 +8,7 @@ import ( "strings" ) +// calculateStringHash calculates the sha1 checksum of a given string a. func calculateStringHash(a string) (string, error) { hash := sha1.New() hash.Write([]byte(a)) @@ -15,9 +16,10 @@ func calculateStringHash(a string) (string, error) { return strings.TrimSpace(fmt.Sprintf("%x", hashInBytes)), nil } -func CalculateBlockHash(block []byte) (string, error) { +// 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(block)); err != nil { + if _, err := io.Copy(hash, bytes.NewReader(b)); err != nil { return "", err } hashInBytes := hash.Sum(nil)[:20] diff --git a/core/storage.go b/core/storage.go index 7f2dac6..8628e8d 100644 --- a/core/storage.go +++ b/core/storage.go @@ -11,6 +11,7 @@ import ( "os" ) +// Header contains all information stored in the header of a fsverify partition. type Header struct { MagicNumber int Signature string @@ -20,6 +21,8 @@ type Header struct { 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 @@ -27,10 +30,14 @@ type Node struct { 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: @@ -50,6 +57,8 @@ func parseUnitSpec(size []byte) int { } } +// 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) { @@ -63,6 +72,10 @@ func ReadHeader(partition string) (Header, error) { 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) @@ -114,6 +127,9 @@ func ReadHeader(partition string) (Header, error) { 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) { @@ -126,6 +142,9 @@ func ReadDB(partition string) (string, error) { 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) @@ -138,11 +157,13 @@ func ReadDB(partition string) (string, error) { 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 { - fmt.Println("failed reading db") - fmt.Println(header.TableSize * header.TableUnit) return "", err } if n != header.TableSize*header.TableUnit { @@ -150,11 +171,16 @@ func ReadDB(partition string) (string, error) { } 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 @@ -163,6 +189,7 @@ func ReadDB(partition string) (string, error) { 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) { @@ -175,6 +202,8 @@ func OpenDB(dbpath string, readonly bool) (*bolt.DB, error) { 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 @@ -198,7 +227,14 @@ func GetNode(checksum string, db *bolt.DB) (Node, error) { 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 { diff --git a/core/verification.go b/core/verification.go index f1e1f0b..289ce1e 100644 --- a/core/verification.go +++ b/core/verification.go @@ -12,8 +12,7 @@ import ( "github.com/tarm/serial" ) -//var TotalReadBlocks int = 0 - +// 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) @@ -23,6 +22,7 @@ func fileReadKey() (string, error) { 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) @@ -35,7 +35,11 @@ func fileReadKey() (string, error) { 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 { @@ -67,6 +71,9 @@ func serialReadKey() (string, error) { } 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 } @@ -79,6 +86,7 @@ func serialReadKey() (string, error) { 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: @@ -86,14 +94,21 @@ func ReadKey() (string, error) { case 1: return fileReadKey() case 2: - return "", nil + 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) @@ -109,6 +124,7 @@ func ReadBlock(node Node, part *bytes.Reader, totalReadBlocks int) ([]byte, int, 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 { @@ -123,6 +139,7 @@ func VerifySignature(key string, signature string, database string) (bool, error 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 { @@ -135,6 +152,7 @@ func VerifyBlock(block []byte, node Node) error { 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 { |