Files
ipsw/internal/codesign/resources/resources.go
2023-01-13 22:19:10 -07:00

318 lines
8.7 KiB
Go

package resources
import (
"crypto/sha1"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/blacktop/go-macho"
"github.com/blacktop/go-plist"
pl "github.com/blacktop/ipsw/pkg/plist"
)
const libraryDirectory = "Contents/Library"
const resourcesDirectory = "Contents/Resources"
const frameworksDirectory = "Contents/Frameworks"
const CodeResourcesPath = "Contents/_CodeSignature/CodeResources"
type hash2 struct {
CDHash []byte `plist:"cdhash,omitempty" xml:"cdhash,omitempty"`
Requirement string `plist:"requirement,omitempty" xml:"requirement,omitempty"`
Hash2 []byte `plist:"hash2,omitempty" xml:"hash2,omitempty"`
Symlink string `plist:"symlink,omitempty" xml:"symlink,omitempty"`
Optional bool `plist:"optional,omitempty" xml:"optional,omitempty"`
}
type CodeResources struct {
Files map[string][]byte `plist:"files,omitempty" xml:"files,omitempty"`
Files2 map[string]hash2 `plist:"files2,omitempty" xml:"files2,omitempty"`
Rules map[string]any `plist:"rules,omitempty" xml:"rules,omitempty"`
Rules2 map[string]any `plist:"rules2,omitempty" xml:"rules2,omitempty"`
}
func CreateCodeResources(dir string) error {
cr := &CodeResources{
Files: make(map[string][]byte),
Files2: make(map[string]hash2),
Rules: make(map[string]any),
Rules2: make(map[string]any),
}
// rules
cr.Rules["^Resources/"] = true
cr.Rules["^Resources/.*\\.lproj/"] = struct {
Optional bool `plist:"optional,omitempty" xml:"optional,omitempty"`
Weight float64 `plist:"weight,omitempty" xml:"weight,omitempty"`
}{
Optional: true,
Weight: 1000,
}
cr.Rules["^Resources/.*\\.lproj/locversion.plist$"] = struct {
Omit bool `plist:"omit,omitempty" xml:"omit,omitempty"`
Weight float64 `plist:"weight,omitempty" xml:"weight,omitempty"`
}{
Omit: true,
Weight: 1100,
}
cr.Rules["^Resources/Base\\.lproj/"] = struct {
Weight float64 `plist:"weight,omitempty" xml:"weight,omitempty"`
}{
Weight: 1010,
}
cr.Rules["^version.plist$"] = true
// rules2
cr.Rules2[".*\\.dSYM($|/)"] = struct {
Weight float64 `plist:"weight,omitempty" xml:"weight,omitempty"`
}{
Weight: 11,
}
cr.Rules2["^(.*/)?\\.DS_Store$"] = struct {
Omit bool `plist:"omit,omitempty" xml:"omit,omitempty"`
Weight float64 `plist:"weight,omitempty" xml:"weight,omitempty"`
}{
Omit: true,
Weight: 2000,
}
cr.Rules2["^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/"] = struct {
Nested bool `plist:"nested,omitempty" xml:"nested,omitempty"`
Weight float64 `plist:"weight,omitempty" xml:"weight,omitempty"`
}{
Nested: true,
Weight: 10,
}
cr.Rules2["^.*"] = true
cr.Rules2["^Info\\.plist$"] = struct {
Omit bool `plist:"omit,omitempty" xml:"omit,omitempty"`
Weight float64 `plist:"weight,omitempty" xml:"weight,omitempty"`
}{
Omit: true,
Weight: 20,
}
cr.Rules2["^PkgInfo$"] = struct {
Omit bool `plist:"omit,omitempty" xml:"omit,omitempty"`
Weight float64 `plist:"weight,omitempty" xml:"weight,omitempty"`
}{
Omit: true,
Weight: 20,
}
cr.Rules2["^Resources/"] = struct {
Weight float64 `plist:"weight,omitempty" xml:"weight,omitempty"`
}{
Weight: 20,
}
cr.Rules2["^Resources/.*\\.lproj/"] = struct {
Optional bool `plist:"optional,omitempty" xml:"optional,omitempty"`
Weight float64 `plist:"weight,omitempty" xml:"weight,omitempty"`
}{
Optional: true,
Weight: 1000,
}
cr.Rules2["^Resources/.*\\.lproj/locversion.plist$"] = struct {
Omit bool `plist:"omit,omitempty" xml:"omit,omitempty"`
Weight float64 `plist:"weight,omitempty" xml:"weight,omitempty"`
}{
Omit: true,
Weight: 1100,
}
cr.Rules2["^Resources/Base\\.lproj/"] = struct {
Weight float64 `plist:"weight,omitempty" xml:"weight,omitempty"`
}{
Weight: 1010,
}
cr.Rules2["^[^/]+$"] = struct {
Nested bool `plist:"nested,omitempty" xml:"nested,omitempty"`
Weight float64 `plist:"weight,omitempty" xml:"weight,omitempty"`
}{
Nested: true,
Weight: 10,
}
cr.Rules2["^embedded\\.provisionprofile$"] = struct {
Weight float64 `plist:"weight,omitempty" xml:"weight,omitempty"`
}{
Weight: 20,
}
cr.Rules2["^version\\.plist$"] = struct {
Weight float64 `plist:"weight,omitempty" xml:"weight,omitempty"`
}{
Weight: 20,
}
if err := filepath.WalkDir(filepath.Join(dir, frameworksDirectory), func(path string, d os.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
relPath, err := filepath.Rel(filepath.Join(dir, "Contents"), path)
if err != nil {
return err
}
var fwpath string
if regexp.MustCompile(`^Frameworks/[^/]+\.framework/.+Info\.plist$`).MatchString(relPath) {
dat, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("failed to read %s: %w", path, err)
}
ainfo, err := pl.ParseAppInfo(dat)
if err != nil {
return fmt.Errorf("failed to parse %s: %w", path, err)
}
if ainfo.CFBundleExecutable != "" {
before, _, ok := strings.Cut(path, ".framework/")
if ok {
fwpath = filepath.Join(before+".framework/", ainfo.CFBundleExecutable)
}
}
} else {
return nil
}
if _, ok := cr.Files2[fwpath]; ok {
return nil // already added
}
// files
var m *macho.File
if fat, err := macho.OpenFat(fwpath); err == nil { // UNIVERSAL MACHO
defer fat.Close()
m = fat.Arches[len(fat.Arches)-1].File
} else { // SINGLE MACHO ARCH
if errors.Is(err, macho.ErrNotFat) {
m, err = macho.Open(fwpath)
if err != nil {
return nil
// return err
}
defer m.Close()
} else {
return nil // not a macho file
}
}
cs := m.CodeSignature()
if cs == nil {
return fmt.Errorf("no code signature in %s", path)
}
if len(cs.CodeDirectories) == 0 {
return fmt.Errorf("no code directory in %s", path)
}
cdhashBytes, err := hex.DecodeString(cs.CodeDirectories[len(cs.CodeDirectories)-1].CDHash)
if err != nil {
return err
}
var requirement string
if len(cs.Requirements) > 0 {
requirement = cs.Requirements[0].Detail
}
relPath, err = filepath.Rel(filepath.Join(dir, "Contents"), fwpath)
if err != nil {
return err
}
cr.Files2[filepath.Dir(relPath)] = hash2{
CDHash: cdhashBytes[:20],
Requirement: requirement,
}
return nil
}); err != nil {
return fmt.Errorf("failed to walk %s: %w", filepath.Join(dir, "Resources"), err)
}
// TODO: walk pluginsDirectory ??
// TODO: walk xpcServicesDirectory ??
// TODO: walk helpersDirectory ??
if err := filepath.WalkDir(filepath.Join(dir, libraryDirectory), func(path string, d os.DirEntry, err error) error {
if err != nil {
return err
}
relPath, err := filepath.Rel(filepath.Join(dir, "Contents"), path)
if err != nil {
return err
}
fi, err := os.Lstat(path)
if err != nil {
return fmt.Errorf("file %s does not exist", path)
}
var symlink string
if (fi.Mode() & os.ModeSymlink) != 0 {
symlink, err = os.Readlink(path)
if err != nil {
return fmt.Errorf("failed to eval symlink %s: %w", path, err)
}
}
if symlink != "" {
cr.Files2[relPath] = hash2{Symlink: symlink}
} else {
if d.IsDir() {
return nil
}
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
h := sha256.New()
if _, err := io.Copy(h, f); err != nil {
return err
}
cr.Files2[relPath] = hash2{Hash2: h.Sum(nil)}
}
return nil
}); err != nil {
return fmt.Errorf("failed to walk %s: %w", filepath.Join(dir, "Resources"), err)
}
if err := filepath.WalkDir(filepath.Join(dir, resourcesDirectory), func(path string, d os.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
relPath, err := filepath.Rel(filepath.Join(dir, "Contents"), path)
if err != nil {
return err
}
// files
f, err := os.Open(path)
if err != nil {
return err
}
h := sha1.New()
if _, err := io.Copy(h, f); err != nil {
return err
}
cr.Files[relPath] = h.Sum(nil)
f.Seek(0, io.SeekStart) // rewind
// files2
h = sha256.New()
if _, err := io.Copy(h, f); err != nil {
return err
}
cr.Files2[relPath] = hash2{Hash2: h.Sum(nil)}
if err := f.Close(); err != nil {
return err
}
return nil
}); err != nil {
return fmt.Errorf("failed to walk %s: %w", filepath.Join(dir, "Resources"), err)
}
if err := os.MkdirAll(filepath.Dir(filepath.Join(dir, CodeResourcesPath)), 0755); err != nil {
return fmt.Errorf("failed to create directory %s: %w", filepath.Dir(filepath.Join(dir, CodeResourcesPath)), err)
}
f, err := os.Create(filepath.Join(dir, CodeResourcesPath))
if err != nil {
return fmt.Errorf("failed to create %s: %w", CodeResourcesPath, err)
}
defer f.Close()
enc := plist.NewEncoder(f)
enc.Indent("\t")
return enc.Encode(cr)
}