package aptrepo import ( "crypto/sha256" "encoding/hex" "errors" "io" "os" "path" "path/filepath" "time" "server-deploy/internal/installplan" ) // VerifyArtifacts authenticates repository metadata anew before checking the // selected five deb files. It never accepts a caller-asserted authenticated lock. // The staging directories and ancestors must be trusted and not concurrently // modified. This is observation evidence, not a capability to later execute paths. func VerifyArtifacts(metadataDirectory, artifactDirectory, suite, arch string, versions map[string]string, now time.Time) (Result, error) { result, err := Verify(metadataDirectory, suite, arch, versions, now) if err != nil { return Result{}, err } if err := verifyArtifacts(artifactDirectory, result.Lock); err != nil { return Result{}, err } result.PackageBytesVerified = true return result, nil } func verifyArtifacts(directory string, lock installplan.Lock) error { reject := errors.New("invalid staged Docker artifacts") if _, err := installplan.Validate(lock, lock.Suite, lock.Architecture); err != nil { return reject } if !filepath.IsAbs(directory) { return reject } directory = filepath.Clean(directory) before, err := os.Lstat(directory) if err != nil || !before.IsDir() || before.Mode()&os.ModeSymlink != 0 { return reject } root, err := os.OpenRoot(directory) if err != nil { return reject } defer root.Close() opened, err := root.Stat(".") if err != nil || !os.SameFile(before, opened) { return reject } expected := make(map[string]installplan.Package, len(lock.Packages)) for _, p := range lock.Packages { expected[path.Base(p.Filename)] = p } dir, err := root.Open(".") if err != nil { return reject } defer dir.Close() // Enumerate at most the expected count plus one, never an unbounded directory. seen := make(map[string]bool) for { entries, err := dir.ReadDir(1) if err != nil && err != io.EOF { return reject } if len(entries) == 0 { if err == io.EOF { break } return reject } name := entries[0].Name() p, ok := expected[name] if !ok || seen[name] { return reject } seen[name] = true if err := verifyArtifact(root, name, p); err != nil { return reject } } if len(seen) != len(expected) { return reject } return nil } func verifyArtifact(root *os.Root, name string, p installplan.Package) error { reject := errors.New("artifact size or digest mismatch") before, err := root.Lstat(name) if err != nil || !before.Mode().IsRegular() || before.Size() != int64(p.Size) { return reject } file, err := root.Open(name) if err != nil { return reject } defer file.Close() opened, err := file.Stat() if err != nil || !opened.Mode().IsRegular() || !os.SameFile(before, opened) || opened.Size() != before.Size() { return reject } digest := sha256.New() n, err := io.CopyBuffer(digest, io.LimitReader(file, int64(p.Size)+1), make([]byte, 64<<10)) if err != nil || n != int64(p.Size) || "sha256:"+hex.EncodeToString(digest.Sum(nil)) != p.Digest { return reject } after, err := file.Stat() if err != nil || after.Size() != opened.Size() || !after.ModTime().Equal(opened.ModTime()) { return reject } return nil }