Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 30 additions & 14 deletions internal/archive/archive.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,19 @@ type Archive interface {
}

type PackageInfo struct {
Name string
Version string
Arch string
SHA256 string
Name string
Version string
Arch string
Digest string
DigestKind cache.DigestKind
}

func (p *PackageInfo) PkgName() string { return p.Name }
func (p *PackageInfo) PkgVersion() string { return p.Version }
func (p *PackageInfo) PkgRevision() int { return 0 }
func (p *PackageInfo) PkgArch() string { return p.Arch }
func (p *PackageInfo) PkgDigestKind() cache.DigestKind { return cache.SHA256 }
func (p *PackageInfo) PkgDigest() string { return p.SHA256 }
func (p *PackageInfo) PkgDigestKind() cache.DigestKind { return p.DigestKind }
func (p *PackageInfo) PkgDigest() string { return p.Digest }

type Options struct {
Label string
Expand Down Expand Up @@ -152,7 +153,7 @@ func (a *ubuntuArchive) Fetch(pkg string) (io.ReadSeekCloser, *PackageInfo, erro
if err != nil {
return nil, nil, err
}
info := sectionPackageInfo(section)
info := sectionPackageInfo(section, digest, digestKind)
return reader, info, nil
}

Expand All @@ -161,7 +162,8 @@ func (a *ubuntuArchive) Info(pkg string) (*PackageInfo, error) {
if err != nil {
return nil, err
}
info := sectionPackageInfo(section)
digest, digestKind := packageDigest(section)
info := sectionPackageInfo(section, digest, digestKind)
return info, nil
}

Expand Down Expand Up @@ -365,8 +367,21 @@ func findDigest(release control.Section, path string, order []digestField) (dige
return "", digestField{}
}

// packageDigestFields lists the checksum fields Chisel looks up in a package
// section, in order of preference: sha256 first.
var packageDigestFields = []digestField{
{"SHA256", cache.SHA256},
{"SHA512", cache.SHA512},
}

// packageDigest returns the digest recorded for the package in the section,
// along with its kind. SHA256 is preferred over stronger digests so that
// the digest used for verification, caching and the manifest keeps matching
// the one consumers expect for as long as archives publish it. Unlike index
// files, packages are fetched by their named pool path, so the by-hash
// layout does not constrain the preference order.
func packageDigest(section control.Section) (digest string, kind cache.DigestKind) {
for _, f := range digestFields {
for _, f := range packageDigestFields {
if d := section.Get(f.name); d != "" {
return d, f.kind
}
Expand Down Expand Up @@ -516,12 +531,13 @@ func (index *ubuntuIndex) fetch(path, digest string, digestKind cache.DigestKind
return index.archive.cache.Open(digestKind, writer.Digest())
}

func sectionPackageInfo(section control.Section) *PackageInfo {
func sectionPackageInfo(section control.Section, digest string, digestKind cache.DigestKind) *PackageInfo {
return &PackageInfo{
Name: section.Get("Package"),
Version: section.Get("Version"),
Arch: section.Get("Architecture"),
SHA256: section.Get("SHA256"),
Name: section.Get("Package"),
Version: section.Get("Version"),
Arch: section.Get("Architecture"),
Digest: digest,
DigestKind: digestKind,
}
}

Expand Down
97 changes: 57 additions & 40 deletions internal/archive/archive_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
. "gopkg.in/check.v1"

"crypto/sha256"
"crypto/sha512"
"debug/elf"
"errors"
"flag"
Expand All @@ -20,6 +19,7 @@ import (

"github.com/canonical/chisel/internal/archive"
"github.com/canonical/chisel/internal/archive/testarchive"
"github.com/canonical/chisel/internal/cache"
"github.com/canonical/chisel/internal/tarball"
"github.com/canonical/chisel/internal/testutil"
)
Expand Down Expand Up @@ -252,21 +252,23 @@ func (s *httpSuite) TestFetchPackage(c *C) {
pkg, info, err := testArchive.Fetch("mypkg1")
c.Assert(err, IsNil)
c.Assert(info, DeepEquals, &archive.PackageInfo{
Name: "mypkg1",
Version: "1.1",
Arch: "amd64",
SHA256: "1f08ef04cfe7a8087ee38a1ea35fa1810246648136c3c42d5a61ad6503d85e05",
Name: "mypkg1",
Version: "1.1",
Arch: "amd64",
Digest: "1f08ef04cfe7a8087ee38a1ea35fa1810246648136c3c42d5a61ad6503d85e05",
DigestKind: cache.SHA256,
})
c.Assert(read(pkg), Equals, "mypkg1 1.1 data")

// Last on component universe.
pkg, info, err = testArchive.Fetch("mypkg4")
c.Assert(err, IsNil)
c.Assert(info, DeepEquals, &archive.PackageInfo{
Name: "mypkg4",
Version: "1.4",
Arch: "amd64",
SHA256: "54af70097b30b33cfcbb6911ad3d0df86c2d458928169e348fa7873e4fc678e4",
Name: "mypkg4",
Version: "1.4",
Arch: "amd64",
Digest: "54af70097b30b33cfcbb6911ad3d0df86c2d458928169e348fa7873e4fc678e4",
DigestKind: cache.SHA256,
})
c.Assert(read(pkg), Equals, "mypkg4 1.4 data")
}
Expand All @@ -290,16 +292,25 @@ func (s *httpSuite) TestFetchSHA512Digests(c *C) {
testArchive, err := archive.Open(&options)
c.Assert(err, IsNil)

pkg, _, err := testArchive.Fetch("mypkg1")
pkg, info, err := testArchive.Fetch("mypkg1")
c.Assert(err, IsNil)
c.Assert(info, DeepEquals, &archive.PackageInfo{
Name: "mypkg1",
Version: "1.1",
Arch: "amd64",
Digest: "27c6e88def3d3848f4a068040bddbf908ab90e33bf93fc24fd02af7ed6a1953151302f2c59306313f065163143b51f1000cd22d102b7a58d7efd6430f5e162fb",
DigestKind: cache.SHA512,
})
c.Assert(read(pkg), Equals, "mypkg1 1.1 data")
}

func (s *httpSuite) TestFetchBothDigests(c *C) {
// An archive publishing both SHA256 and SHA512 sections (index table and
// package fields) must be handled, with the strongest digest preferred
// for verification and caching. PackageInfo.SHA256 still surfaces: it is
// read from the package section directly, not from the preference order.
// package fields) must be handled. For packages, SHA256 is preferred for
// verification, caching and the manifest, so the recorded digest keeps
// matching the one consumers expect; the strongest digest is used only
// when the archive does not publish SHA256. Index files still use the
// strongest digest, as required by the by-hash layout.
s.prepareArchiveAdjustRelease("stonking", "25.10", "amd64", []string{"main", "universe"},
[]string{"SHA256", "SHA512"}, nil)

Expand All @@ -319,17 +330,18 @@ func (s *httpSuite) TestFetchBothDigests(c *C) {
pkg, info, err := testArchive.Fetch("mypkg1")
c.Assert(err, IsNil)
c.Assert(info, DeepEquals, &archive.PackageInfo{
Name: "mypkg1",
Version: "1.1",
Arch: "amd64",
SHA256: "1f08ef04cfe7a8087ee38a1ea35fa1810246648136c3c42d5a61ad6503d85e05",
Name: "mypkg1",
Version: "1.1",
Arch: "amd64",
Digest: "1f08ef04cfe7a8087ee38a1ea35fa1810246648136c3c42d5a61ad6503d85e05",
DigestKind: cache.SHA256,
})
c.Assert(read(pkg), Equals, "mypkg1 1.1 data")

// Pin the cache key: with both digests advertised, the package is cached
// under its strongest digest.
sha512Digest := fmt.Sprintf("%x", sha512.Sum512([]byte("mypkg1 1.1 data")))
_, err = os.Stat(filepath.Join(options.CacheDir, "sha512", sha512Digest))
// under its SHA256 digest.
_, err = os.Stat(filepath.Join(options.CacheDir, "sha256",
"1f08ef04cfe7a8087ee38a1ea35fa1810246648136c3c42d5a61ad6503d85e05"))
c.Assert(err, IsNil)
}

Expand All @@ -356,21 +368,23 @@ func (s *httpSuite) TestFetchPortsPackage(c *C) {
pkg, info, err := testArchive.Fetch("mypkg1")
c.Assert(err, IsNil)
c.Assert(info, DeepEquals, &archive.PackageInfo{
Name: "mypkg1",
Version: "1.1",
Arch: "arm64",
SHA256: "1f08ef04cfe7a8087ee38a1ea35fa1810246648136c3c42d5a61ad6503d85e05",
Name: "mypkg1",
Version: "1.1",
Arch: "arm64",
Digest: "1f08ef04cfe7a8087ee38a1ea35fa1810246648136c3c42d5a61ad6503d85e05",
DigestKind: cache.SHA256,
})
c.Assert(read(pkg), Equals, "mypkg1 1.1 data")

// Last on component universe.
pkg, info, err = testArchive.Fetch("mypkg4")
c.Assert(err, IsNil)
c.Assert(info, DeepEquals, &archive.PackageInfo{
Name: "mypkg4",
Version: "1.4",
Arch: "arm64",
SHA256: "54af70097b30b33cfcbb6911ad3d0df86c2d458928169e348fa7873e4fc678e4",
Name: "mypkg4",
Version: "1.4",
Arch: "arm64",
Digest: "54af70097b30b33cfcbb6911ad3d0df86c2d458928169e348fa7873e4fc678e4",
DigestKind: cache.SHA256,
})
c.Assert(read(pkg), Equals, "mypkg4 1.4 data")
}
Expand Down Expand Up @@ -407,20 +421,22 @@ func (s *httpSuite) TestFetchSecurityPackage(c *C) {
pkg, info, err := testArchive.Fetch("mypkg1")
c.Assert(err, IsNil)
c.Assert(info, DeepEquals, &archive.PackageInfo{
Name: "mypkg1",
Version: "1.1.2.2",
Arch: "amd64",
SHA256: "5448585bdd916e5023eff2bc1bc3b30bcc6ee9db9c03e531375a6a11ddf0913c",
Name: "mypkg1",
Version: "1.1.2.2",
Arch: "amd64",
Digest: "5448585bdd916e5023eff2bc1bc3b30bcc6ee9db9c03e531375a6a11ddf0913c",
DigestKind: cache.SHA256,
})
c.Assert(read(pkg), Equals, "package from jammy-security")

pkg, info, err = testArchive.Fetch("mypkg2")
c.Assert(err, IsNil)
c.Assert(info, DeepEquals, &archive.PackageInfo{
Name: "mypkg2",
Version: "1.2",
Arch: "amd64",
SHA256: "a4b4f3f3a8fa09b69e3ba23c60a41a1f8144691fd371a2455812572fd02e6f79",
Name: "mypkg2",
Version: "1.2",
Arch: "amd64",
Digest: "a4b4f3f3a8fa09b69e3ba23c60a41a1f8144691fd371a2455812572fd02e6f79",
DigestKind: cache.SHA256,
})
c.Assert(read(pkg), Equals, "mypkg2 1.2 data")
}
Expand Down Expand Up @@ -662,10 +678,11 @@ var packageInfoTests = []struct {
summary: "Basic",
pkg: "mypkg1",
info: &archive.PackageInfo{
Name: "mypkg1",
Version: "1.1",
Arch: "amd64",
SHA256: "1f08ef04cfe7a8087ee38a1ea35fa1810246648136c3c42d5a61ad6503d85e05",
Name: "mypkg1",
Version: "1.1",
Arch: "amd64",
Digest: "1f08ef04cfe7a8087ee38a1ea35fa1810246648136c3c42d5a61ad6503d85e05",
DigestKind: cache.SHA256,
},
}, {
summary: "Package not found in archive",
Expand Down
10 changes: 10 additions & 0 deletions internal/cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"io"
"os"
"path/filepath"
"slices"
"time"

"golang.org/x/crypto/sha3"
Expand Down Expand Up @@ -102,6 +103,15 @@ const (

var digestKinds = []DigestKind{SHA256, SHA384, SHA512}

// ValidateKind returns an error unless kind is a digest kind Chisel
// supports.
func ValidateKind(kind DigestKind) error {
if !slices.Contains(digestKinds, kind) {
return fmt.Errorf("unsupported digest kind: %q", kind)
}
return nil
}

var ErrMiss = fmt.Errorf("not cached")

func (c *Cache) filePath(digestKind DigestKind, digest string) string {
Expand Down
47 changes: 34 additions & 13 deletions internal/manifestutil/manifestutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,12 @@ func Write(options *WriteOptions, writer io.Writer) error {
func manifestAddPackages(dbw *jsonwall.DBWriter, infos []PackageInfo) error {
for _, info := range infos {
err := dbw.Add(&manifest.Package{
Kind: "package",
Name: info.PkgName(),
Version: info.PkgVersion(),
Digest: info.PkgDigest(),
Arch: info.PkgArch(),
Kind: "package",
Name: info.PkgName(),
Version: info.PkgVersion(),
Digest: info.PkgDigest(),
DigestKind: string(info.PkgDigestKind()),
Arch: info.PkgArch(),
})
if err != nil {
return err
Expand Down Expand Up @@ -272,13 +273,13 @@ func validatePackage(pkg PackageInfo) (err error) {
if pkg.PkgArch() == "" {
return fmt.Errorf("package %q missing arch", name)
}
// The manifest records the package digest as a SHA256 one. Fail rather
// than recording a digest of another kind under that name.
// TODO: record packages whose digest is not a SHA256 one, such as the
// ones coming from a store. This requires recording the digest kind in
// the manifest as well.
if pkg.PkgDigestKind() != cache.SHA256 || pkg.PkgDigest() == "" {
return fmt.Errorf("package %q missing sha256", name)
kind := pkg.PkgDigestKind()
err = cache.ValidateKind(kind)
if err != nil {
return fmt.Errorf("package %q: %s", name, err)
}
if pkg.PkgDigest() == "" {
return fmt.Errorf("package %q missing %s", name, kind)
}
if pkg.PkgVersion() == "" {
return fmt.Errorf("package %q missing version", name)
Expand All @@ -298,7 +299,27 @@ func Validate(mfest *manifest.Manifest) (err error) {

pkgExist := map[string]bool{}
err = mfest.IteratePackages(func(pkg *manifest.Package) error {
pkgExist[pkg.Name] = true
name := pkg.Name
if name == "" {
return fmt.Errorf("package name not set")
}
if pkg.Arch == "" {
return fmt.Errorf("package %q missing arch", name)
}
kind := cache.DigestKind(pkg.DigestKind)
if kind == "" {
return fmt.Errorf("package %q missing digest", name)
}
if err := cache.ValidateKind(kind); err != nil {
return fmt.Errorf("package %q: %s", name, err)
}
if pkg.Digest == "" {
return fmt.Errorf("package %q missing %s", name, kind)
}
if pkg.Version == "" {
return fmt.Errorf("package %q missing version", name)
}
pkgExist[name] = true
return nil
})
if err != nil {
Expand Down
Loading
Loading