mirror of
https://github.com/clearlinux/mixer-tools.git
synced 2026-09-05 05:01:28 +00:00
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:
committed by
tmarcu
parent
81f40f2c5c
commit
8a1e71e5e8
+67
-75
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user