diff --git a/client/lib/lib.go b/client/lib/lib.go index cc4758c..815d42d 100644 --- a/client/lib/lib.go +++ b/client/lib/lib.go @@ -2,6 +2,7 @@ package lib import ( "bytes" + "encoding/hex" "encoding/json" "errors" "fmt" @@ -13,6 +14,7 @@ import ( "os/exec" "os/user" "path" + "regexp" "runtime" "strconv" "strings" @@ -539,6 +541,14 @@ func Update() error { } } + // On MacOS, set the xattrs containing the signatures. These are generated by an action and pushed to a github release that we download and set + if runtime.GOOS == "darwin" { + err := setCodesigningXattrs(downloadData, "/tmp/hishtory-client") + if err != nil { + return fmt.Errorf("failed to set codesigning xattrs: %v", err) + } + } + // Install the new one cmd := exec.Command("chmod", "+x", "/tmp/hishtory-client") var stdout bytes.Buffer @@ -708,3 +718,72 @@ func ApiPost(path, contentType string, data []byte) ([]byte, error) { getLogger().Printf("ApiPost(%#v): %s\n", path, duration.String()) return respBody, nil } + +func parseXattr(xattrDump string) (map[string][]byte, error) { + m := make(map[string][]byte) + nextLineIsAttrName := true + attrName := "" + attrValue := make([]byte, 0) + for _, line := range strings.Split(xattrDump, "\n") { + if nextLineIsAttrName { + attrName = line[:len(line)-1] + nextLineIsAttrName = false + } else { + r := regexp.MustCompile("\\d{8} (?P([A-Z0-9]{2} )+)\\s+\\|[^\\s]+\\|") + match := r.FindStringSubmatch(line) + if match != nil { + for i, name := range r.SubexpNames() { + if name == "hex" { + bytes, err := hex.DecodeString(strings.ReplaceAll(match[i], " ", "")) + if err != nil { + return nil, fmt.Errorf("failed to decode hex string %#v in xattr file: %v", match[i], err) + } + attrValue = append(attrValue, bytes...) + } + } + } else { + if strings.Contains(line, "|") { + return nil, fmt.Errorf("entered confusing state in xattr file on line %#v, file=%#v", line, xattrDump) + } else { + nextLineIsAttrName = true + m[attrName] = attrValue + attrValue = make([]byte, 0) + } + } + } + } + return m, nil +} + +func setXattr(filename, xattrDump string) { + m, err := parseXattr(xattrDump) + if err != nil { + panic(fmt.Errorf("failed to parse xattr file: %v", err)) + } + for k, v := range m { + err := syscall.Setxattr(filename, k, v, 0) + if err != nil { + panic(fmt.Errorf("failed to set xattr %#v on file %#v: %v", k, filename, err)) + } + } +} + +func setCodesigningXattrs(downloadInfo shared.UpdateInfo, filename string) error { + if runtime.GOOS != "darwin" { + return fmt.Errorf("setCodesigningXattrs is only supported on macOS") + } + url := "" + if runtime.GOARCH == "arm64" { + url = downloadInfo.DarwinArm64Xattr + } else if runtime.GOARCH == "amd64" { + url = downloadInfo.DarwinAmd64Xattr + } else { + return fmt.Errorf("setCodesigningXattrs only supports arm64 and amd64: %#v", runtime.GOARCH) + } + xattrDump, err := ApiGet(url) + if err != nil { + return fmt.Errorf("failed to get xattr dump: %v", err) + } + setXattr(filename, string(xattrDump)) + return nil +} diff --git a/client/lib/lib_test.go b/client/lib/lib_test.go index ddfba98..65dff25 100644 --- a/client/lib/lib_test.go +++ b/client/lib/lib_test.go @@ -190,3 +190,57 @@ func TestParseCrossPlatformInt(t *testing.T) { t.Fatalf("failed to parse cross platform int %d", res) } } + +func TestParseXattr(t *testing.T) { + dump := `com.apple.macl: +00000000 04 00 34 5A 0D 8F 9B 10 48 FB 9D 12 E2 11 C7 21 |................| +00000010 D3 17 04 00 7D 17 C7 D7 51 B6 4B C4 B0 E5 1A 58 |................| +00000020 21 53 DD 4C 00 00 00 00 00 00 00 00 00 00 00 00 |!S.L............| +00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000040 00 00 00 00 00 00 00 00 |........�......| +00000048 +com.apple.metadata:kMDItemDownloadedDate: +00000000 62 70 6C 69 73 74 30 30 A1 01 33 41 C4 07 D4 F5 |bplist00..3A....| +00000010 D0 E7 A3 08 0A 00 00 00 00 00 00 01 01 00 00 00 |................| +00000020 00 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00 |................| +00000030 00 00 00 00 13 |.....| +00000035 +com.apple.metadata:kMDItemWhereFroms: +00000000 62 70 6C 69 73 74 30 30 A2 01 02 5F 10 47 68 74 |bplist00..._.Ght| +00000010 74 70 73 3A 2F 2F 64 6C 2E 67 6F 6F 67 6C 65 2E |tps://dl.google.| +00000020 63 6F 6D 2F 63 68 72 6F 6D 65 2F 6D 61 63 2F 75 |com/chrome/mac/u| +00000030 6E 69 76 65 72 73 61 6C 2F 73 74 61 62 6C 65 2F |niversal/stable/| +00000040 47 47 52 4F 2F 67 6F 6F 67 6C 65 63 68 72 6F 6D |GGRO/googlechrom| +00000050 65 2E 64 6D 67 5F 10 17 68 74 74 70 73 3A 2F 2F |e.dmg_..https://| +00000060 77 77 77 2E 67 6F 6F 67 6C 65 2E 63 6F 6D 2F 08 |www.google.com/.| +00000092 +com.apple.quarantine: +00000000 30 31 38 33 3B 36 32 35 66 37 32 36 62 3B 53 61 |0183;625f726b;Sa| +00000010 66 61 72 69 3B 46 37 33 37 42 42 43 33 2D 30 41 |fari;F737BBC3-0A| +00000020 35 38 2D 34 31 44 34 2D 38 46 33 36 2D 30 33 42 |58-41D4-8F36-03B| +00000030 42 33 31 36 36 39 35 39 39 |B31669599| +00000039` + xattr, err := parseXattr(dump) + if err != nil { + t.Fatal(err) + } + if len(xattr) != 4 { + t.Fatalf("xattr has an incorrect length: %d", len(xattr)) + } + val := xattr["com.apple.quarantine"] + if string(val) != "0183;625f726b;Safari;F737BBC3-0A58-41D4-8F36-03BB31669599" { + t.Fatalf("unexpected xattr value=%#v", string(val)) + } + val = xattr["com.apple.metadata:kMDItemWhereFroms"] + if string(val) != "bplist00\xa2\x01\x02_\x10Ghttps://dl.google.com/chrome/mac/universal/stable/GGRO/googlechrome.dmg_\x10\x17https://www.google.com/\b" { + t.Fatalf("unexpected xattr value=%#v", string(val)) + } + val = xattr["com.apple.metadata:kMDItemDownloadedDate"] + if string(val) != "bplist00\xa1\x013A\xc4\a\xd4\xf5\xd0\xe7\xa3\b\n\x00\x00\x00\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x13" { + t.Fatalf("unexpected xattr value=%#v", string(val)) + } + val = xattr["com.apple.macl"] + if string(val) != "\x04\x004Z\r\x8f\x9b\x10H\xfb\x9d\x12\xe2\x11\xc7!\xd3\x17\x04\x00}\x17\xc7\xd7Q\xb6Kİ\xe5\x1aX!S\xddL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" { + t.Fatalf("unexpected xattr value=%#v", string(val)) + } +} diff --git a/shared/data.go b/shared/data.go index b7c885f..d026a3d 100644 --- a/shared/data.go +++ b/shared/data.go @@ -30,8 +30,10 @@ type UpdateInfo struct { LinuxAmd64AttestationUrl string `json:"linux_amd_64_attestation_url"` DarwinAmd64Url string `json:"darwin_amd_64_url"` DarwinAmd64AttestationUrl string `json:"darwin_amd_64_attestation_url"` + DarwinAmd64Xattr string `json:"darwin_amd_64_xattr_url"` DarwinArm64Url string `json:"darwin_arm_64_url"` DarwinArm64AttestationUrl string `json:"darwin_arm_64_attestation_url"` + DarwinArm64Xattr string `json:"darwin_arm_64_xattr_url"` Version string `json:"version"` }