swupd: use Hash type in Hashcalc

Also add GetHashForFile and GetHashForBytes functions. Tests of
genHash were changed to test the equivalent GetHashForBytes function.

Signed-off-by: Caio Marcelo de Oliveira Filho <caio.oliveira@intel.com>
This commit is contained in:
Caio Marcelo de Oliveira Filho
2018-01-17 12:39:10 -08:00
committed by tmarcu
parent 81f40f2c5c
commit 8a1e71e5e8
2 changed files with 103 additions and 96 deletions
+67 -75
View File
@@ -20,7 +20,7 @@ import (
"encoding/hex"
"fmt"
"hash"
"io/ioutil"
"io"
"os"
"syscall"
)
@@ -58,64 +58,14 @@ func HashEquals(h1 Hashval, h2 Hashval) bool {
// Hashcalc returns the swupd hash for the given file
func Hashcalc(filename string) (Hashval, error) {
var info syscall.Stat_t
var err error
var data []byte
if err = syscall.Lstat(filename, &info); err != nil {
return 0, fmt.Errorf("error statting file '%s' %v", filename, err)
r, err := GetHashForFile(filename)
if err != nil {
return 0, err
}
// Get magic constants out of /usr/include/bits/stat.h
switch info.Mode & syscall.S_IFMT {
case syscall.S_IFREG: // Regular file
data, err = ioutil.ReadFile(filename)
if err != nil {
return 0, fmt.Errorf("read error for '%s' %v", filename, err)
}
case syscall.S_IFDIR: // Directory
info.Size = 0
data = []byte("DIRECTORY") // fixed magic string
case syscall.S_IFLNK:
info.Mode = 0
target, err := os.Readlink(filename)
if err != nil {
return 0, fmt.Errorf("error readlink file '%s' %v", filename, err)
}
data = []byte(target)
default:
return 0, fmt.Errorf("%s is not a file, directory or symlink %o", filename, info.Mode&syscall.S_IFMT)
}
r := internHash(genHash(info, data))
return r, nil
return internHash(r), nil
}
// genHash generates hash string from butchered Stat_t and data
// Expects that its callers have validated the arguments
func genHash(info syscall.Stat_t, data []byte) string {
key := hmacComputeKey(info)
result := hmacSha256ForData(key, data)
return string(result[:])
}
// hmacSha256ForData returns an array of 64 ascii hex digits
func hmacSha256ForData(key []byte, data []byte) []byte {
var result [64]byte
mac := hmac.New(sha256.New, key)
_, _ = mac.Write(data)
hex.Encode(result[:], mac.Sum(nil))
return result[:]
}
// This is what I want to have for the key
// type updatestat struct {
// st_mode uint64
// st_uid uint64
// st_gid uint64
// st_rdev uint64
// st_size uint64
// }
// set fills in a buffer with an int in little endian order
// set fills in a buffer with an int in little endian order.
func set(out []byte, in int64) {
for i := range out {
out[i] = byte(in & 0xff)
@@ -123,24 +73,6 @@ func set(out []byte, in int64) {
}
}
// hmacComputeKey returns what should be an ascii string as an array of byte
// it is really ugly to be compatible with the C implementation. It is not portable
// as the C version isn't portable.
// The syscall.Stat_t has been butchered
func hmacComputeKey(info syscall.Stat_t) []byte {
// Create the key
updatestat := [40]byte{}
set(updatestat[0:8], int64(info.Mode))
set(updatestat[8:16], int64(info.Uid))
set(updatestat[16:24], int64(info.Gid))
// 24:32 is rdev, but this is always zero
set(updatestat[24:32], 0)
set(updatestat[32:40], int64(info.Size))
// fmt.Printf("key is %v\n", updatestat)
key := hmacSha256ForData(updatestat[:], nil)
return key
}
// HashFileInfo contains the metadata of a file that is included as
// part of the swupd hash.
type HashFileInfo struct {
@@ -217,7 +149,7 @@ func NewHash(info *HashFileInfo) (*Hash, error) {
hmac: hmac.New(sha256.New, key[:]),
}
// Pre-write data we known so that directories and symbolic
// Pre-write data we know so that directories and symbolic
// links don't need further data from the caller.
if data != nil {
_, err = h.hmac.Write(data)
@@ -241,3 +173,63 @@ func (h *Hash) Sum() string {
hex.Encode(result[:], h.hmac.Sum(nil))
return string(result[:])
}
// GetHashForFile calculate the swupd hash for a file in the disk.
func GetHashForFile(filename string) (string, error) {
var info syscall.Stat_t
var err error
if err = syscall.Lstat(filename, &info); err != nil {
return "", fmt.Errorf("error statting file '%s' %v", filename, err)
}
hashInfo := &HashFileInfo{
Mode: info.Mode,
UID: info.Uid,
GID: info.Gid,
Size: info.Size,
}
if info.Mode&syscall.S_IFMT == syscall.S_IFLNK {
var link string
link, err = os.Readlink(filename)
if err != nil {
return "", err
}
hashInfo.Linkname = link
}
h, err := NewHash(hashInfo)
if err != nil {
return "", fmt.Errorf("error creating hash for file %s: %s", filename, err)
}
if info.Mode&syscall.S_IFMT == syscall.S_IFREG {
f, err := os.Open(filename)
if err != nil {
return "", fmt.Errorf("read error for file %s: %s", filename, err)
}
_, err = io.Copy(h, f)
_ = f.Close()
if err != nil {
return "", fmt.Errorf("error hashing file %s: %s", filename, err)
}
}
return h.Sum(), nil
}
// GetHashForBytes calculate the hash for data already in memory and the
// associated metadata.
func GetHashForBytes(info *HashFileInfo, data []byte) (string, error) {
h, err := NewHash(info)
if err != nil {
return "", err
}
if data != nil {
_, err = h.Write(data)
if err != nil {
return "", err
}
}
return h.Sum(), nil
}
+36 -21
View File
@@ -3,7 +3,6 @@ package swupd
import (
"fmt"
"os"
"syscall"
"testing"
)
@@ -91,33 +90,49 @@ const (
Reg = 0100000
)
// TestGenHash checks that the internal data hashing is correct
func TestGenHash(t *testing.T) {
func TestGetHashForBytes(t *testing.T) {
testCases := []struct {
info syscall.Stat_t
name string
info HashFileInfo
data []byte
result string
}{
{syscall.Stat_t{Mode: (Dir + 0755)},
[]byte("DIRECTORY"), directoryhash},
{syscall.Stat_t{Mode: (Dir + 01777)},
[]byte("DIRECTORY"),
"d93a5e9129361e28b9e244fe422234e3a1794b001a082aeb78e16fd881673a2b"},
{syscall.Stat_t{Mode: Reg + 0644, Uid: 1000, Gid: 1000},
[]byte(""),
"b85f1dc2c2317a20f47a36d3257313b131124ffa6d4f19bb060d43014fd386b0"},
{syscall.Stat_t{Mode: Reg + 0644, Uid: 1000, Gid: 201},
[]byte(""),
"0a3978d8b6ea47b779a2dfb5d6a7f57c93d28e131870bcd187470da3678d1298"},
{syscall.Stat_t{Mode: Reg + 0644, Uid: 1000, Gid: 201, Size: 6},
[]byte("hello\n"),
"53b40563c1162a14d9ce0233a6b346cd0a4cbce54c40affbdf0fc286fd3bfe7b"},
{
name: "directory",
info: HashFileInfo{Mode: Dir + 0755},
result: directoryhash,
},
{
name: "directory with different permissions",
info: HashFileInfo{Mode: Dir + 01777},
result: "d93a5e9129361e28b9e244fe422234e3a1794b001a082aeb78e16fd881673a2b",
},
{
name: "empty regular user file 0644",
info: HashFileInfo{Mode: Reg + 0644, UID: 1000, GID: 1000},
result: "b85f1dc2c2317a20f47a36d3257313b131124ffa6d4f19bb060d43014fd386b0",
},
{
name: "empty regular user file 0644 with different group",
info: HashFileInfo{Mode: Reg + 0644, UID: 1000, GID: 201},
result: "0a3978d8b6ea47b779a2dfb5d6a7f57c93d28e131870bcd187470da3678d1298",
},
{
name: "regular user file with different group",
info: HashFileInfo{Mode: Reg + 0644, UID: 1000, GID: 201, Size: 6},
data: []byte("hello\n"),
result: "53b40563c1162a14d9ce0233a6b346cd0a4cbce54c40affbdf0fc286fd3bfe7b",
},
}
for _, tc := range testCases {
r := genHash(tc.info, tc.data)
if r != tc.result {
t.Errorf("Unexpected result %s for\n%v\n", r, tc)
hash, err := GetHashForBytes(&tc.info, tc.data)
if err != nil {
t.Errorf("couldn't calculate hash for case %q: %s", tc.name, err)
continue
}
if hash != tc.result {
t.Errorf("Unexpected result for case %s, got %s but wanted %s", tc.name, hash, tc.result)
}
}
}