pax_global_header00006660000000000000000000000064143267527100014521gustar00rootroot0000000000000052 comment=5b1317c0a77d040167051f8c16881bcf60020baf sif-2.8.3/000077500000000000000000000000001432675271000123145ustar00rootroot00000000000000sif-2.8.3/.circleci/000077500000000000000000000000001432675271000141475ustar00rootroot00000000000000sif-2.8.3/.circleci/config.yml000066400000000000000000000064771432675271000161550ustar00rootroot00000000000000version: 2.1 orbs: codecov: codecov/codecov@3 executors: node: docker: - image: node:18-slim golangci-lint: docker: - image: golangci/golangci-lint:v1.50-alpine golang-previous: docker: - image: golang:1.18 golang-latest: docker: - image: golang:1.19 jobs: lint-markdown: executor: node steps: - checkout - run: name: Install markdownlint command: npm install -g markdownlint-cli - run: name: Check for Lint command: markdownlint . lint-source: executor: golangci-lint steps: - checkout - run: name: Check for Lint command: golangci-lint run check-go-mod: executor: golang-latest steps: - checkout - run: name: Go Mod Tidy command: go mod tidy - run: name: Check Module Tidiness command: git diff --exit-code -- go.mod go.sum check-test-corpus: executor: golang-latest steps: - checkout - run: name: Generate Test Corpus command: pushd test/ && go run ./gen_sifs.go && popd - run: name: Check Test Corpus Tidiness command: git diff --exit-code -- check-vulnerabilities: executor: golang-latest steps: - checkout - run: name: Install govulncheck command: go install golang.org/x/vuln/cmd/govulncheck@latest - run: name: Check for vulnerabilities command: govulncheck ./... build-source: parameters: e: type: executor executor: << parameters.e >> steps: - checkout - run: name: Build Source command: go build ./... unit-test: parameters: e: type: executor executor: << parameters.e >> steps: - checkout - run: name: Run Unit Tests command: go test -coverprofile cover.out -race ./... - codecov/upload: file: cover.out release-test: executor: golang-latest steps: - checkout - run: name: Install syft command: curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin - run: name: Test Release command: curl -sL https://git.io/goreleaser | bash -s -- --snapshot --skip-publish publish-release: executor: golang-latest steps: - checkout - run: name: Install syft command: curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin - run: name: Publish Release command: curl -sL https://git.io/goreleaser | bash workflows: version: 2 build-and-test: jobs: - lint-markdown - lint-source - check-go-mod - check-test-corpus - check-vulnerabilities - build-source: matrix: parameters: e: ["golang-previous", "golang-latest"] - unit-test: matrix: parameters: e: ["golang-previous", "golang-latest"] - release-test tagged-release: jobs: - publish-release: filters: branches: ignore: /.*/ tags: only: /v[0-9]+(\.[0-9]+)*(-.*)*/ context: github-release sif-2.8.3/.github/000077500000000000000000000000001432675271000136545ustar00rootroot00000000000000sif-2.8.3/.github/dependabot.yml000066400000000000000000000003261432675271000165050ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: gomod directory: "/" schedule: interval: daily target-branch: v1 - package-ecosystem: gomod directory: "/" schedule: interval: daily target-branch: main sif-2.8.3/.gitignore000066400000000000000000000000031432675271000142750ustar00rootroot00000000000000*~ sif-2.8.3/.golangci.yml000066400000000000000000000014531432675271000147030ustar00rootroot00000000000000linters: disable-all: true enable: - asciicheck - bidichk - bodyclose - containedctx - contextcheck - decorder - depguard - dogsled - dupl - dupword - errcheck - errchkjson - errname - errorlint - exportloopref - gochecknoinits - gocritic - godot - godox - goerr113 - gofumpt - goimports - gomodguard - goprintffuncname - gosec - gosimple - govet - grouper - ineffassign - interfacebloat - ireturn - lll - maintidx - misspell - nilnil - nolintlint - nonamedreturns - prealloc - reassign - revive - staticcheck - tenv - typecheck - unconvert - unparam - unused - whitespace linters-settings: misspell: locale: US sif-2.8.3/.goreleaser.yml000066400000000000000000000027111432675271000152460ustar00rootroot00000000000000release: prerelease: auto changelog: use: github-native gomod: proxy: true env: - GOPROXY=https://proxy.golang.org,direct - GOSUMDB=sum.golang.org builds: - id: darwin-builds binary: siftool goos: - darwin goarch: - amd64 - arm64 main: &build-main ./cmd/siftool mod_timestamp: &build-timestamp '{{ .CommitTimestamp }}' env: &build-env - CGO_ENABLED=0 flags: &build-flags '-trimpath' ldflags: &build-ldflags | -s -w -X main.version={{ .Version }} -X main.date={{ .CommitDate }} -X main.builtBy=goreleaser -X main.commit={{ .FullCommit }} - id: linux-builds binary: siftool goos: - linux goarch: - '386' - 'amd64' - 'arm' - 'arm64' - 'mips' - 'mips64' - 'mips64le' - 'mipsle' - 'ppc64' - 'ppc64le' - 'riscv64' - 's390x' goarm: - '6' - '7' main: *build-main mod_timestamp: *build-timestamp env: *build-env flags: *build-flags ldflags: *build-ldflags archives: - id: darwin-archives builds: - darwin-builds - id: linux-archives builds: - linux-builds sboms: - documents: - '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}.bom.cdx.json' artifacts: binary args: ["$artifact", "--file", "$document", "--output", "cyclonedx-json"] sif-2.8.3/.markdownlint.json000066400000000000000000000000541432675271000157750ustar00rootroot00000000000000{ "default": true, "MD013": false } sif-2.8.3/.vscode/000077500000000000000000000000001432675271000136555ustar00rootroot00000000000000sif-2.8.3/.vscode/settings.json000066400000000000000000000001231432675271000164040ustar00rootroot00000000000000{ "go.lintTool":"golangci-lint", "go.lintFlags": [ "--fast" ] }sif-2.8.3/CONTRIBUTING.md000066400000000000000000000132131432675271000145450ustar00rootroot00000000000000# Contributor's Agreement You are under no obligation whatsoever to provide any bug fixes, patches, or upgrades to the features, functionality or performance of the source code ("Enhancements") to anyone; however, if you choose to make your Enhancements available either publicly, or directly to the project, without imposing a separate written license agreement for such Enhancements, then you hereby grant the following license: a non-exclusive, royalty-free perpetual license to install, use, modify, prepare derivative works, incorporate into other computer software, distribute, and sublicense such enhancements or derivative works thereof, in binary and source code form. ## Contributing When contributing to SIF, it is important to properly communicate the gist of the contribution. If it is a simple code or editorial fix, simply explaining this within the GitHub Pull Request (PR) will suffice. But if this is a larger fix or Enhancement, you are advised to first discuss the change with the project leader or developers. Please note we have a code of conduct, described below. Please follow it in all your interactions with the project members and users. ### Pull Requests (PRs) #### Process 1. Essential bug fix PRs should be sent to both `main` and release branches (if applicable). 1. Small bug fix and feature enhancement PRs should be sent to `main` only. 1. Follow the existing code style precedent. Use [golangci-lint](https://golangci-lint.run) to ensure your code is properly formatted and free of lint. 1. For any new functionality, please write appropriate tests that will run as part of the continuous integration system ([CircleCI](https://circleci.com/gh/sylabs/workflows/sif)). 1. Ensure the project's default copyright and header have been included in any new source files. 1. Make sure you have run `go test ./...` and all tests succeed before submitting the PR. 1. Is the code human understandable? This can be accomplished via a clear code style as well as documentation and/or comments. 1. The pull request will be reviewed by others, and finally merged when all requirements are met. 1. Documentation must be provided if necessary (next section) #### Documentation 1. If you are changing any of the following: - renamed commands - deprecated / removed commands - changed defaults - backward incompatible changes (recipe file format? image file format?) - migration guidance (how to convert images?) - changed behaviour (recipe sections work differently) ## Code of Conduct ### Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ### Our Standards Examples of behavior that contributes to creating a positive environment include: - Using welcoming and inclusive language - Being respectful of differing viewpoints and experiences - Gracefully accepting constructive criticism - Focusing on what is best for the community - Showing empathy towards other community members Examples of unacceptable behavior by participants include: - The use of sexualized language or imagery and unwelcome sexual attention or advances - Trolling, insulting/derogatory comments, and personal or political attacks - Public or private harassment - Publishing others' private information, such as a physical or electronic address, without explicit permission - Other conduct which could reasonably be considered inappropriate in a professional setting #### Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ### Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ### Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project leaders (info@sylabs.io). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers, contributors and users who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions with their involvement in the project as determined by the project's leader(s). ### Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ sif-2.8.3/LICENSE.md000066400000000000000000000027511432675271000137250ustar00rootroot00000000000000# LICENSE Copyright (c) 2018-2022, Sylabs Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. sif-2.8.3/README.md000066400000000000000000000033461432675271000136010ustar00rootroot00000000000000# The Singularity Image Format (SIF) [![PkgGoDev](https://pkg.go.dev/badge/github.com/sylabs/sif/v2?status.svg)](https://pkg.go.dev/github.com/sylabs/sif/v2) [![Build Status](https://circleci.com/gh/sylabs/sif.svg?style=shield)](https://circleci.com/gh/sylabs/workflows/sif) [![Code Coverage](https://codecov.io/gh/sylabs/sif/branch/main/graph/badge.svg)](https://app.codecov.io/gh/sylabs/sif) [![Go Report Card](https://goreportcard.com/badge/github.com/sylabs/sif)](https://goreportcard.com/report/github.com/sylabs/sif) [![Powered By GoReleaser](https://img.shields.io/badge/powered%20by-goreleaser-green.svg)](https://github.com/goreleaser) This module contains an open source implementation of the Singularity Image Format (SIF) that makes it easy to create complete and encapsulated container environments stored in a single file. ![SIF Image](doc/sif.png) Unless otherwise noted, the SIF source files are distributed under the BSD-style license found in the [LICENSE.md](LICENSE.md) file. ## Install Siftool Pre-built binaries are available with the [latest release](https://github.com/sylabs/sif/releases). ## Go Version Compatibility This module aims to maintain support for the two most recent stable versions of Go. This corresponds to the Go [Release Maintenance Policy](https://github.com/golang/go/wiki/Go-Release-Cycle#release-maintenance) and [Security Policy](https://golang.org/security), ensuring critical bug fixes and security patches are available for all supported language versions. ## Contributing SIF and [SingularityCE](https://github.com/sylabs/singularity) are the work of many contributors. We appreciate your help! To contribute, please read the contribution guidelines found in the [CONTRIBUTING.md](CONTRIBUTING.md) file. sif-2.8.3/cmd/000077500000000000000000000000001432675271000130575ustar00rootroot00000000000000sif-2.8.3/cmd/siftool/000077500000000000000000000000001432675271000145365ustar00rootroot00000000000000sif-2.8.3/cmd/siftool/doc.go000066400000000000000000000010711432675271000156310ustar00rootroot00000000000000// Copyright (c) 2018, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. /* Siftool is a program for Singularity Image Format (SIF) file manipulation. A set of commands are provided to display elements such as the SIF global header, the data object descriptors and to dump data objects. It is also possible to modify a SIF file via this tool via the add/del commands. */ package main sif-2.8.3/cmd/siftool/siftool.go000066400000000000000000000045531432675271000165530ustar00rootroot00000000000000// Copyright (c) 2018-2022, Sylabs Inc. All rights reserved. // Copyright (c) 2017, SingularityWare, LLC. All rights reserved. // Copyright (c) 2017, Yannick Cote All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package main import ( "fmt" "io" "os" "runtime" "strconv" "text/tabwriter" "github.com/spf13/cobra" "github.com/sylabs/sif/v2/pkg/sif" "github.com/sylabs/sif/v2/pkg/siftool" ) var ( version = "unknown" date = "" builtBy = "" commit = "" ) func writeVersion(w io.Writer) error { tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) defer tw.Flush() fmt.Fprintf(tw, "Version:\t%v\n", version) if builtBy != "" { fmt.Fprintf(tw, "By:\t%v\n", builtBy) } if commit != "" { fmt.Fprintf(tw, "Commit:\t%v\n", commit) } if date != "" { fmt.Fprintf(tw, "Date:\t%v\n", date) } fmt.Fprintf(tw, "Runtime:\t%v (%v/%v)\n", runtime.Version(), runtime.GOOS, runtime.GOARCH) fmt.Fprintf(tw, "Spec:\t%v\n", sif.CurrentVersion) return nil } func getVersion() *cobra.Command { return &cobra.Command{ Use: "version", Short: "Display version information", Long: "Display binary version, build info and compatible SIF version(s).", Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, args []string) error { return writeVersion(cmd.OutOrStdout()) }, DisableFlagsInUseLine: true, } } func main() { root := cobra.Command{ Use: "siftool", Short: "siftool is a program for Singularity Image Format (SIF) file manipulation", Long: `A set of commands are provided to display elements such as the SIF global header, the data object descriptors and to dump data objects. It is also possible to modify a SIF file via this tool via the add/del commands.`, } root.AddCommand(getVersion()) var experimental bool if val, ok := os.LookupEnv("SIFTOOL_EXPERIMENTAL"); ok { b, err := strconv.ParseBool(val) if err != nil { fmt.Fprintln(os.Stderr, "Error: failed to parse SIFTOOL_EXPERIMENTAL environment variable:", err) } experimental = b } if err := siftool.AddCommands(&root, siftool.OptWithExperimental(experimental)); err != nil { fmt.Fprintln(os.Stderr, "Error:", err) os.Exit(1) } if err := root.Execute(); err != nil { os.Exit(1) } } sif-2.8.3/doc/000077500000000000000000000000001432675271000130615ustar00rootroot00000000000000sif-2.8.3/doc/sif.png000066400000000000000000001321341432675271000143540ustar00rootroot00000000000000PNG  IHDRn^?? IDATx |e?MҦmZZzR(mB)[E<Y\˂ ?WwQEE`QEQrS)g -P(L_Of&i$MR>z343\5d؍(@ P(AXgV(@ P(``P(@ PVAm:V(@ P(@ P(@`P(@ P(@<(@ P(@ P `c)@ P(@ PA- P(@ PXXq P(@ P`Pc(@ PVAm:V(@ P(@ P(@`P(@ P(@<(@ P(@ P `c)@ P(@ PA- P(@ PXXq P(@ P`Pc(@ PV@횧v['GN^hEzI̟(@ Pz+)bg_RzfUCx3Y5S'cw!(p (@ P(@-PWW,k t<vꪫQ(@ P(@?lC ݽ+5%TX\|2ze1S P(@ P~/ APĉޘԊ{hEce̓(@ P@Pć"N䕠V ʼn(@ P(`V蕡(ǜ(@ xZ@SQs8_V@0u[$&#RS4().3P Ct\#4=|jM@rk}x>\,A8_ m.kσ}.(`_[q' j4  P ? >ڽvsI2O<: 1֧?}p6 #ј:.IVVUX[wۜxdpbNF<5wwяcdy÷HL1qToC ,o"o钴۱laUC} =q<=TD@c3೏aҀN 2pg?4̴b}\1k{>0oJxtznm<$KtAv,Î~@)+:g))T涚_5rT?sWWeN}9|xG76n~7c# \n|`["0}чxVVr<5CxN퉆I@Ϯ}1lhαng.(Dt?v=LB P%K_uЊLom,,q)φʱgrLe'b;bllKƻ4U|.^y$?ƮWh~3)q .4o7}v렃jػ?b~Ƙ^{p}ǻ'\|Sz%B֟Iuo}i\IT\yҠ>#%{σ׾> {8~UgbrMR6 MVjq>$=tŘ33~\J P%Զ6ˢ\_L.f?2$ٻyc923޽zl"kI a`h ;?|vb3Mc/$!c}w5q X#LL{gxc[݇-v|LЬ09EPvF|3Fl˛y%)T2L3 Cb}C Y}^A@=/JZKFJzXB]>А|0e:K16Ly ~ćM߅z!Q \;0iY2r\agKR%_'~>\bu\<; ̚ fxO]"[o}J44Z{+0u;jS7O,,(@Ԍ\QA7nQiVbJJ V : vju`{gͪ/fIG 3V`K6%筇#"K3cضrt>I$fHsvAd5V{% <l/DpW+mJ1ݟG¼yE1gVZW6ǻ)<ْTm$]Oo3o0oE e!ۦ}y-'Q@ ggm91Ika0( tIh mpc(Т h,@C3G? ţMJK_6*h Wrm|%KA-i/>yaeMKo}9=#h =zNa 񡴋8:yEbiJ VX:W Mކ$t5tMA VH^I袾XayڥSpQm NjWޓ\-_KZ[4rUnϖS1s-cۺ?Cru*n^&$4xCg71~ B *pAnPߎ+voeIm K!sw<'70Ĝ(@ ,~$6m^y_B';gFA,_x"q]DLh(Bwb~R 4k?\܍ۭO[@*eY;gK5S8ӴX^ ,o"ޑ:XWǎ2wz[®n5I=ME ۉ _O7Iش G#<̜4>j6}f ^ R6iyi-{TQhTAmDL@ ]Dwbun1 c@KM^Q1ggmhkZg?!bgEO4ggxMn[D۱콩4*|˕2oִW9\5(􇱼~%cImɸq,T;NL\}Igun~8J;yð,m[JjOYcA}AA怑)/|Q2I\u18܃U/,rǤgiX7{S;0=(7~7tuWUM% zl7o2+UZc\4iGτiPmQi^6ÂRљE Rp@k,/cVAxm6|Ƌ^ۯdH۔||;A"Js`5mt BBU~P0 ŀ֔\@S^=r{(8y}꛻[-m&ۿ}0L,(N^yʼsm}ߋsÕX UcS Z\[,hr vn}G1GǍҠF)|_m8ݤ*d_ [#)sot\!<~0ԏ ^1 7O9q(;u+ykm&zoUFiS{oby\j9JMޘ4ċQy+lcs-8[ݏ^l4Wwo-z i#Îms)yH,WjQ97. P(9&`8Ƿ7aȟg级<^)H"PQOz`ʤ kB Ԝ?-4A윶+)@ %->nL xK@?o iYOo.>uXh*kuB&[v;qS&/eWbv>/? ŹXꜣW_ZexD wpzs$XϬ-Fp'Zwwo"=B59!65w;i~)3cwwgǹpu }=ê N*oZccV jQU4-R,w[+.(v?#3!9 ~i嬔xLc;ĺ]X%ٲռ_TD >}]F=oqFM줳4=bQ FO ]b۫dM蜎f>2 i<|:^8pm1wR7IW!~xc r䔐סyǻKFm7lev޼r>[V1`ٛO`xfI{f^os}Au삨pgl>S|Yn7ykVHR'^5d؍=Hw1? Pk..k U3]jVqg/\@P(B 1|5ojjOX[$&'!qi5g\(:weW:am1?P9:~p (@E W+_wcǎrjܜ\N.f焀I?<<{,WE"YՌ\*I@ĉޘxg_/{"7 '(@ P(@ xI@ą">q7&G~7(@ P("qEIұlod=l F̓(@ PW@,Zze(ieS;uĭ#w/\xE=(@ P(@ H@r|Z[WhzPk./0ϏaܸUkH^dd!ٕNӜ]4 WR@0q@ P(@+\`Fzdfz,-8YkmL&+~P(@ P_ 0JQ(@ P+ j]Qb P(@ PK~[X) P(@ PpEA+JLC P(@ P~)/w +E P(@ P0uEi(@ P(@ P/na(@ P(@ P+&FEW]!PMY)j۶M._ƅRi(@ PNpZӪ8!M6 IDATuuuxo9JR]{Hmo#;D ̌vX:/Wϲ;)@f l:nH PXAm+޹ҦQSS{row^Nj/(o_Y= *G0={a{ Px^}PR:v?aM)p  ɼYüZ-fe.6o0 N 6k OL]b;vggAѸT>YPk l+*&~HkXGIW`'h .]2<¼y6L(2}Q7? 3j3OV\)uhTSSg ~sS7HzQ(_ԣ)=s`W([+㾪'˥\y5'(@+GA핳[kׯxgQQQYoDYLQvs'qv֬9n͢YۉDLCBsm(@yeY (@ O}3pRP!e;ab͚oʥE9:gΜx"O(ӂ{~76+?$xځQys̉SAm`7x'PPxcD"ooN }YȣEDޚo0_ P=W( j{ygϞcMcS!yrf,y{ m)@ <7nE P/pՐa7zAJeڵk+ꪫb ßIͪNAA!~]TUU5k{w6߯/cb [WWe>öTFDПGBB|nPTTWH=(p rt6-$pm0)mO[駟{$/fₑV5|?=w4=)jPDF"XFPڦ _?>5ztmR'⣯iRaw_ۤVoA. 7 ]tV>wM||WGOfGoXyGb&*puZe>EQ|W*+JD@ԩT)(BsnwV}ߣgz s{?1cIZdq4|+o^y΃H"NtQ?W}\H \<\-K!qjnB(uR3 S2.{eZYJγ=M)wJKcg P[s>=]G\@<v1N87:}t-<,DndhxOoGiZlٽiR⇄W5ZK陈x^7[K-=R":G>zR"FNpz ^ jݡj ua]@BLî[~݇7? "셋8pdeڽb[}ǼX i//Y$Wl[raJ%U(++h^0(J ѭS` ]O>dž_4D,DN`JAļX.$mr MbH#r(И+ q=(@ 8K"V䕠V 4$r |˗wVW?nox/u<=U{1.Fb^,)HWS֋W+WS𽀈 E(bEOO^^وåc@_ t*Eth' Bnm J.\U!M!"Cʠl𮫕b'.%Lv"a]ȶSN7u~= l(嵵TC~j4Yn*aa&7eG\7,iY1*rP:S~*yOjQ6zUsiwsԤi" | kwܦU~2~ox_w[eO.Ag0J~|UF yuqW! UJ]S V7XW>:Pq]jPG{ zC|zUjw p(1+ 3* ;yk/w51cɕVrrǛZ]Wh%H֢>uN؊+Њ:ڢ.F~uTq\4zŖuU\z*Ïy~d@+{ Z+V @+!H[csֺ%z1uk[n8bbJyZW*)Zi]E}DMqKvvVNEԩIt9HYF\]-N2p2r(@ P. @w.V,X=6R'=~VqWJn+ur%ջNCˉ(@V'@"vT]hX_ _bP(WK 劑KujƠPVWJ*ь]+iQ[4V' /7(@ +ƹR'WWr>'> j zI(@ P(`YP@ P(@ PpWAܞ(@ P|&g,(@ P`P (@ P(@ 0= (@ P(@wԺ+)@ P(@ Pg j}Fς)@ P(@ P] r{ P(@ PZѳ` P(@ PpWAܞ(@ P|&g,(@ P`P (@ P(@ 0= (@ P(@wԺ+)@ P(@ Pg j}Fς)@ P(@ P] r{ P(@ PZѳ` P(@ PpW@nܾ 09##ɬ, BYA=es(@ PZ@Am rv=hB(4B u(@ P(@ xOݏg*r/i@*FP(@ P++ZZSAU؞Q#):D&Ù%K(@ PFUTKqBO xOAl[E{h+Oŋ6(@ PP=Nv#ţ9qXTn'QC~!֚͡C“20>(@ PhQ"8^t;ICtS R֔ csdQv1ISsdrxhl(@ P0ޘTG"P)' #4pCr 7mm=V*SU=2 P(H×Cug,xsZv=n`PY֗ 7<E(@ Pvj0oWPO]SX~3ٌ֔(a jՐ uQafg-p"o3=b\( Ezt(\yeNT"^tBZj p`(k^6ܪ g0c` JSyKl ' P(@V/v ޽v*7%܌k'VV-1Ҷ/M %+c11%vˠ(@ P~ jzܣTU@@HGSߕyӿo?dIƊTZ`@& ;a(@ P>BSCƅ?4rW[k@u?Ϻ2LL97J}~L#aߧ+Zku5=Vw nR?#bCG!EjB(@ P|%p6nY. ܕZy\XWZY !?E!*@Rv[̅M֚J[Q$ hEUx3Ƽj$Wn=S(@ @JRtH6dБXzPܓd=K7cЏ+Z_5O Fշ ۦvȂ>&-g)@ PZ@JX0jdEGwCC[Ǝül \cmq09\@Q{6o\ BJ\ƟeF?>m6Jrl(@ x^@uVS%@Z5o!1[ EMx>mxo$~]=&O<7ZPWj"I푓PЊ{+Vu^$7{u7=?7R P(0Yx|߬ƂS0v ÀTcJ71F nDphmo$t=gOJG-MmHmvs+}sy Z-xo8̽u2d&V~̰(@{rp6A=nE?/j. 2:J"3cnĞֵڝ >K#6*}`^y?+ ŨV\9Rl[)=޶((@ P9*!3:ZK,k0ԛE_ϔE= @ M(c9_3_ެHP^]PЊJ'f'>i3@mxu^Zee2,5 )mpA,s,(@ B`wX/խT. b˼.k#fT@MbC.HLCWi*nDzrPӣrx@#_şX>RHlwn =OarfG4Be6WeA( rOU 7(@ PXsC0k :E: v9B)c\ ڋ.DEֳ"EH\u*fr[=9owbҽau*V22E.l2bƓ&奭5\{5Bd29^o)@ Pb0}$Ur ƁGۤ”cXq.}r MCS /%4A?{HNLiV^vt݇q7hNEoъy0>b,b</c<'˜(@ P@D$ڙ߽m1@8B#Y yxfH@@N_U#]^DHs y'cSj ˜6V3-Fm[С =“20>V(@ P@ou~K@+`N߃'bkF1 xIay){d!F[=.*!K[7 d+Ja#^0x D/ EP *-1 Fq{LTF@.S m~M(@ P𤀼aH1n]D@+^2m *Am>rn Ԗ`ڿװ&MHch)Nn UKWsƀV?%m/) /L~j }٧b=}n@m4cItVJqDl_up(@ @uᢌ]%8ZR-cU0:+j:+R@PS]-XЊuV#nú5_x . ׍y"ýl?*"ӚAP*d2?ylw87j{LQ(-!\Vx _ps 4] Z7^n4'o@qzht dJT\O7i~NjJ\ը͸dB$`bV R#á+[/`pz:xȍf(@ Pkw}CӕYKQ aƚ|G/ \P{Tya)Ja:>[/5+жln'CTLCmf/DSsLH%BM'ko6|Tk <kI9D &G떚(@ PW~G'!F_^SgW+W&\P{~&c^ J, /cRUX4m/D[eVRrȣ:cV~6(@Hr 1, z *ˠ Ge|C%p_ ںpt0\ l3Qu>6YObqS.ODtt;C`^S.UT/i>_Ӏ6\|)prc!>[0(J.Hz'_ (@ PA"W:4p`3 fpI Z*ͯ73VŨ:$hmx,ܬT[~oc)@ P|잃1w`{KC4EpKNe2#D+<̲(WBuc;!cfP$7+;[՚GʽVuL}8[XV*&T *I_pp6;ה0o9gt IDAT;Ph~XUK7[v\zWUP(@/ ҀVLh%55HoN+ײ~Byh5Ç` 1:ͧVq Sp5swl SI Q?>Ht燦ƯH̛zL1}j>\>Zt:_=by >+>>.X<(@ PW@lZ ꠔ| V=/$*ڢ_[خ㥥xA)Ȋ+KFo<槥(SV.4P=1q(*j刉πZi Gh ޓv[+QHue;}@+&f(C[K6ɗ Ǵ$QH.H dAtt]G,(p ]'vqqKB+5 'ƂkݑW*b^ض 6lPel72CBlrOĚkx`VK|3&F?SRYcXduЋgHJح%~uV<ɾze4u+)@ Pq$JpKB{Vhe|ںsqho %PǨ)= j-XNq:k͆'!T(`OceE`~2'?LX6 #fHjd.圡(@ P>U {"@R+U`4#߰~ pYBB~ E*bع|hXUFj*04QѺ=۩ܩ3M㭴U| PTtߵZ >߂v~ P(@0 ʴ7SaBZ{hk^F( @@%(<ulh8-yC-X _ܞ#cTH@,|{}We8?;`݆?me FKmXS0[SrmS(@YV%`p-!Az̿9 SCH{+p0!*'lNJ^VYb яn,pO0C >pogpCEXȕa珝@j~R{W.4 d`5ᖤl'㢱ETJk&),;IY(@hF8l!CqfC2 2:[Kz FƘuM+pup#YԙizՕя萙{.rTf֪ΐˠtk mlNswQemo9Q,JÕp 0F sA(@ @2\ň{\(WYU@sQLF_x%{"&+B:%zAWwz?J_ѢKkQ&@xBn1UNv}'55yvna^\ qPjq;+׻UZa5V~K(@ P ƔE P z9gFEzl8i?'oj>C[@;vCDHPdw.2p~[y0*/l;}Z]c5$9``|QN8s壈,PXX|fI?&۪^(@ Po.5DJfU # +cTMRu{~vVsN28S~s,|1"<nX\/LcEYGe0RZs\IBiEU5=X ,e0 kon_p߹]'j*/b[iMÒ 핤<1>5)h.a֭'^f/c塶5JܐhUCēQ6--1/ip.}5kls㥣0encGإ } \?jSxtf=sdzrbnW 내I=u=.x<]1on{ v~dga^1c N⒬ ۪Q{~[c~sQD㣈~=^#<+*R>t0 ?Ў;c6 w5 PhvRÆ0; c,سL=Bɼ(pE *<ڤx&t@g\1k?Bt2,GCL NWiMU 6% cK/~Yϊ!q;Thrx{3񛗈)t%Bj]SKŏovL0Isq)us W ҠhUT{L:կqiO]0u,knwt)۴|\-][ǯi YQ/l9z!%'W\ƱIhϪ8!=sZV@=cXr4EЏm'DZvWB!2$n G6CR?ICݥC@ęKvqoڈpP[MٝrOַ]:[SBY=.0;WVRQRwCJɈZ rdvB`wΘ>$z: 0ܺ~[CE[&Bau"k۴|G1h8 tB"z]UgnB:zJOչئ-W#VݺW.c;k @'.!%)}X,OboF˗Wcpr[Q_Pr(n~;6 JPYzoNzz=Se1w;oE۲< -2Cqg@se rCg30zQm s=Q1bsaZxHS0HlI0Yxv<9|MS- =Zh Nso5~[$ m9[KCo#6t)29ץU;dmVƠ֜Əj5]=*$=ky UAw0 e/U`DfkmuBzP,J*W9m:D5~Xoc-E%b\<ۮ׋ O}YlCPVYoa jk:ZF2T q4txcDw8n/6ԣ]6ʢj7Bġ B0Z68:,P1a{^S,~1ubirK'͍c1F1XJ|*#m!+l|R6[E0{Q :`ʈ1n8r jd5}Ξ.>#93Ykd0l=)T5ezԢ[rOWG'~Z19c8^U65=y%ޖ7Z@,7Nߴ:ʔ %;M+/\TnmȺg+$pVlX |DGD ^)È.iھA]x4;k~B75 Ol.>WR<Š=p]e@/6h},/6{|qa!ܐn @}Nس2";{Ndm \y@Cq8PB ;" ~ɏI߯ Š[jrl/أ~Jё" BG5Wz(naO>r:FAĘJo30KǏqV57 o17'F:j,;j]~ #*aᜫ \W' 8 '}%#\ sآx'L2էKPeYc Q=K/P7[Դ2:0qUX4^C6B. Hxm6'7՛n`La0nLr+ɓ1 pAHu -O2 Bf ?%;Ģi y183 {R r3-]¡js=y6R_-V*=<\KV* &#,y}8FBܞXSvBxd2#M/-6})>CZ=,=@Μ3`jm}Mr~C.Ƨ_g MdZ~i~U Ks|.Pҝ4e@GfLqW`LK[#T5Ǝamo)C| 5 ;J`׏%jzC,Eu/8wIZRTf'`'.|A9pA@m>Op2$Fu)kwiJiLKV#P5x&:o;diOf ļ ¹Pi\۾%R?{eqdl6, -)Jڪ{αxci*bQTK0HrO6̻%d7{|wޙg3ksՒ+*2c^Q`gp[Ց7[U[`v|.F-?fMu}n3yk !;\ xYH\<Տ aVZih֦j+P[W4I>Jn"26Q\ƺю ۥS#C4@B: `*mm/]ܤ+q׉ |шsiBA^kg-&:{d[e@s(T ܮZ$::K'0s;0wzkײهza8>awpqT `,2ө:@1b&W<}EY5Cu$V=uּmXai5v/ [pƒ~wgɻPR%Fss >Fm/KW?b6)^)Y1pOϼnX6bnZ jZ)&47Oc\dw(!AO "dWUԎ:&O?e0Uu:\U]μCHQeð[p PZ;R~~u+5_$,b{z6ޕUgՇO{ iŧ KS5@| 9(,8`PJ :p.FF`o'YeIṷ}nFM^B8Z߅EVǍ8\eyw*MSuRΦ S{#JzrOso/02y& PfkXD v_6*x2yZ5>+wVBWgj[vnRDVb,& \z\ދtfKd-TW ޑb5OU Ou%f'QNNF]րtOܒS\N-O!½6"~\s)4 |382 @QTy;*]]W Ԍ+o"Hלێ_Uu Mzi|[vs{#CC#THYumz@t`bݭYMgK`ȧ}^sfP[vK=rGO'Tf]upiSX쨂ْ b]/"Y K_֓sqJ}dW78Pe j֧NzoWϻwd:[iLm:nv!s'곧M8e1Y)>M-ۗgi$Ɩm谻1**WrkW=֮$0r cLF%zX`hJvɨ M`lCa3/Le C. jP]_oYj@L5hhiFQݔ!Hjz: \z/$YDɃDo=W;sO}<{Ts4W-&hoi@Qs׎*9 mJj OW_wHm n:hʫ3oƤn584ֱɶHggô$:}gXkpa*02037>Ju>Z?46e>BV1lc~^QYZ-:ݎP nWxJ$S:Pt9ChtMnioCa3Ƭ\a#..`𬛚&޼ZФUw[•]^@L1lh7@(۰9^NlfR .]BI1vkӲ@rp\iop# ?>$0HHH@{jaUL\lt兜tmk)цZb -F'oe-sDk}/v۴5s& !`$ltX[AY,Zn찾 ÃĢ(6Msj(h~PnCPeLskZ-m`pnŢHHH\'`jv\o0sEExg'N9\ #Q[}zd_a^ ū~>xV;Ö& Y mx\;ЬDK} @wnʔ(}p:E-T\ IDAT߆gWa Sܰu0<̏-5xc^=:Q&H'!;*LzXNcX 8`3 JghO2DV|bBk=/~`!aC 1ڔp}k`iRZo~w% UJߪŁ,oɥy?{U!?P\M696e4/IoPc!Zw]ALðqa9wځHN]:^q]k:9KW!1l':%{U&^%b҈q>,$@%`7U(HhGu_axhm$Z-*~~4^xӕAQ+}멎Mi PñdB)}Lamx{MۛnR{;ˋ C 6]ַ ax` Ą@ (dAZw|}`ȼj>֝y#[:+Dab;h uuNd` &$F#qK Ccu2;G OhF!gV&wĝ7EkmӍOCbpKi>SRZzͰ:Gc6rΠW#wLC$'u IwE}1l۬~Wq~n=Vňu\Ŷ#iiSqT$Tz%ExaOymh];:1&LBvJt>3zq[;Zy|z=VǓ N]3&`6Lڣh0_|ME@h4nZ@˹#xYe&;%wLOF\x.6]žCx2:g|7) MK-pSSݬy[R(U6 P1 X6 i 4֗iqגfhDZ =/.D @P袤a2פ{)ux|+UGmxʢKl;r޵ i˜aq%&xl[e9F\s-KsDaBhHңQv#gFy|kp.yJ~z %0flykvQ4jÐ>LwеuYO}quM0zQmx>E8X,RmƤ걮c(F0g~~G_>J څYߡ%9i&6C2Zf B6Y[*09J;y#:ZUu"t%9V c E.R*C,c0`/A떬ĝb5|Zt[Zk"" A69w#w坈;mՀRٶ‘LTjdb,?E4oυd*)S:s{Y0@$08mؼS*L萶T&BB"p ̉ LPl)\ɩIƚ6ZJ-^9Z_g ؃0̑*R]yK]2ś5E3%HΊe@]K3_{f܅`y$Vc0^#DsXWFmR܄~֦#PpO-}$Vt*~EkEz$EG!*&6aru1 im@ ۻF#jB07%r2 h´g ._zY PZYK $"Jm~j[ ;|I:P6kQ؇Xl!&ku{ ֕x. ?dJ#"0ER_s= %:;,h[Ӟ3S pVh;G_eFYbжVc Q'oƒy)9oJϖoB65ۏNa%5U}چ];?vc[([7W) ?.SAܾD\].T f5h+Oc{*0~ NDz%έ}$l>6 Wq+'6-2U3jcm2I$&;s:~/J H821ylPZ[̝cֽ!4O@rA֪SxT8 i|`joBuM-Ъ>x^  JΰY5_&[.ubXl2f:,”䓟t@称*uįq 4a{$ܙ,  T~o+DED N@NF&%$98eS"c9>U1}!1?"pv:وO\F*2 袠$J09\4*%HσĴ@xV%ܗ y"9<>}jQk3ό¯N.;Zz6i*顊ع4sOn7ߵZ<>2厛\r?IKR! D*&m.~(Q8&ŢxV~u׌=(FaDWUUM=ȞIxbdYffLLe[_ !sj2{e".Y(FyO0,OYG\{7Be;l!TbdVPjnzUf8F0@$:'*D-)|;`P822W]~]ܖ(f܍ Ȱd.eK+moJ80#j6n3t.S-9,u {(b˚Zfʕ/UsUIW !lހɲ-ZBaYpcؕ?Xy%e5NH3IDy6';L-TIT"x"TQa _{]jS /6>߆i^*,Y0/9\?- Sjn[BFLpQv&v:\x-Y}xtTd "<.LoVL̳e _WY>Ja4"D` ťM<"N_?ξͣuxov^}YF7f᭎IB^I0̎IzV$@S ~Y)BBDLj(T΢I!lXf#Ƨ.C#uʘx $6 e=PDyI-qdpuYvFceQ~er?3)ǭfn_mˀ}_ ?0jj ,D~f/,{>[k &.22)x&QExx,^jDC;B2[1 O`W߽;8l_8jw9;K갨c[U/hBݘ& ݣ3g^EpY![ e 􈕍 $ji1x,:7Xg+;ͳbX*?ʊbM]GVb<g | lIlJm8rӤi:kvIm?Q㭳0GąBTlވ$<{< @K$Q0Zp`^8нˣ*.;%*Y£-k@D/˖-(6Lf * )眼/}nS_ {ScDi4v*7ZLMg}vF;g"}Z,K|(!|Ga!hDj!u#.T@{1,O/2yQIme]5 >3{*ֲd&FwCo إ|4o{U1yZ1DE+VaLg⍍)G" #)e'aH4ӳ.l:#+rRly#H{­᪺6btֵPQ{ĵ߽ZZ&`q'w<сY1Pp緿IڜҀOzǜ<#p@^)C?Ё(]4;(1h8Z JmgqHSuC5 bk on)D=>)mAZ(-*sϑ:i 1 ;9b;`vZ\R+OlA}IX$F'?lI*>} UVc[ӑ2jЊIkaq`kHDiՈpEpN7uM=?S~V)DeIl$FdykX  Uq_՘ԴZIxe׌sZ'lol[gj`"ؿmsR--f-qᙱ" /ȁt\pYMgK`ȧ}^Bې> ކWc#<0[֘n={lfNIԛC=[3naz:L枠RE>-[_{*$O4ZL mlm@a}s%B1& @CC3v8@F,5tX A%uS2 Y0a\ Y<*˴Ig@}QkWYX6#~_Nu|QNΒ Pp1"eE:2&"W9h*c.8d]5DAA[^?V8xTE+<-e@hinfgZw袵6tF#Z6gߡM0ή]iK{ {Zm&a߽[UPGǚ=P.:yqr$B`Rd,Jn 2'eU]*u#.ֺuɚK?3iK9م=_z֠KO3j-o#=)?ACϡ Е"/Ba5n![m[Ͽ]8 ieK+c-x5=2uTaX ޱ+/  s螿Ɔ!_:λqbDƺϻt  x9ln " i.)9,Hmp'%థG >Nj~g^:Xd._ T7 alTY-%:^W^NIZ ZWq_/f-&T8L|&ƾ[Eb"VT}!eErS^1qI$@M`۞ؖ1iյqŠo׌ڑ1jj 53&"NT:&k㫓ooBjQu8u8fMDhzc ={Q6gGnRNG8ه&(=W <zMū4Pcp2dF iu9э+1GBOع"FCA*ĝӻIN؁ªsT@Qi$9JIQmؑw̲+e X9KΨz7lr6j4PDB{etKXi?>.M-f=^ p2jwc]bi(RF(s 6"Sqm^1sr/XE$@$@$0x5]4RmD2b8QPnY@OK6h=֭覾|F܈M 4y˨m! vFs/0  uh~.*tX}imN;݋@c-Z+;܁6<߭&O1oXWC'g @,נr+Hi~gF/Π5BKqKzx$a*`\:QYQHٽs7fПˇon#5g&h\jmFOJM5H;/.2*9 _.bStNP'ɔ$@$@$@&z(:+֚kҧw;&DDZ~.\$JCbUo|g&>t}U?2+j*g[}Ki6 =mJv 7,@;cƍEqB."ih8W(v*ynӗBB|╸u/V`Kк <G*+kPc XISJi"` յ-HIJYBv_n͈rUGVܱ`&m;LNTЖ!   ɩ*k ^kqSƓ!l馇TXFmRQ@ lLG IDATL  w|(Urg>~h l,z)~lVĢ|eiwud PMK0l>6 k(LiX(ۃ@P~>(HHH++p~.rZ5ED$"حh׏|$&~188bTa1 QՆ"lv3܄}b2f̱kgaEN' @7i%",|#Xvk^` 7-p䌙wDVHV*P]U>ٍvgoԜRHHHRPsFjbq3wZe2YPǻj>̜+%H4Iؠ;z(u}ybr]i C;q]@Hez .x1,UKB em L |j IGl^jQgoDimz5j,BcqBcS:,$V-BcѾf7M /YbJ7HmuzT P*h>Hb7NwQTG!u xwks8?8Ae|:`lačeOo?CmCO;ż 6AUg gKH0ެpM0DZeb1s%XSc)gEBBSI<LK$@$@$@'~ ZI$0j4x m>tWv+sPkݿz٫Ać VGYt ymd_伪P _3f*64;*P3IHHHK}j4|w*{O Q%X!wFf;0T47ރڗXܺvDwW͈ObOIu ^?/+ڇk?jkA4 { (T:{F'KiN;64arq{`jq?_$@%o^ix:aͫx">}-R$#JE,ãWbVcs)xqE2nTh-;;w2   a  ؆B=^aPEH~gv4 " %ߠè&a"at~_zuÛjfob2'btO}8\I{vEÚ^"[( M g@_zsevo.Y Q{ J /|h17 3j3 [! "g!¡1CNBAk7]E(hjjQGOPPoXcsBD : ᓲiB<[.1[+*갞B{xsKB$@$@$@h#oCLY;1&; (~g{qgqϏM}S-*~UFə3m`_޶@ۀ뾏xɎ`ʕkQ][eLm1hPFY<(@.F;r~ҙHHHK؁'/-H@7Ϟg߃2v!1fPMEh:{ԇD"i޺6{VBkߊoY!yBV:9FORqv<||L!r\2P"_Pupj %?d?0'  caxh \ hң~s֍KTQ7!:߯EfPpO-r>-l7(fZ9: usx斫~[V$.Yl7iYs9*wpHiPwgUPCğP}c͡ o"v787 ƐGcn7y'YFqA=}vKӾ((ib[au+Țeti1+ZR*5P &IHH fL6Չ3P2 )]zؠYZFc4>j]x  xޛQYU $"JmVTǩC]``Ol~13+ J*O(l7"fx<B>D%I?YuT#S&1Ѡf*uy#j(7   OHθ-s2,Tᡍ+8>zF &:Ft N\́Xq`ӺR!FbC0oCiTWŽPRBy؁9hwjK:PpTK՛7˘9%1?&`Bum ZmhlUo@}σHHHA@! )UR` *%  +Q @B7d#<?k<0 ep;Zym씽\HI<_PK`o l>6{5? #`@y|cz"H$0XoԶA^sU|a :f| b,ReY_;X,g#`d u&l"   xeEV]v"B$*ކlx1ov~f ,@dL}9?Tq %8{H_uT}4 @Kx[!9ːmފs\$@$@$@$@~H #-ߝCٗxVtè.Tţe֒* 7jq@̆yӠQYx2 "D G;zT+7#@dy0r+ǹ_Ϸ$$   X8o~eЛFp2$ B\r~?*vOB}}GQ_W;VA*A 4~T]pLʳ9^Lthn'+Ms+.55ص @+ŝITY-U|9 ]ɲw.T^lK Z3{cc}~{Lϡp<}UXČw~_MVHHHHE5pt~ɣ:Xd+2l)[S2@&&@:l^SWL+A@̪ +țp.S߃b/ '' ÂO O+)W;S'$@$@$@$@$@Ckj_rqSWKeIHHHH p6Z-u1 M T*To8!S ШTفM&'    !8x44I$@$@$@$@$@HFm *D$@$@$@$@$@#Ь& "ت 4jGHC$@$@$@$@$@$hbN$@$@$@$@$@$0BШ! j @ Q: !@v44I$@$@$@$@$@HFm *D$@$@$@$@$@#Ь& "ت 4jGHC$@$@$@$@$@$hbN$@$@$@$@$@$0BШ! j @ Q: !@v44I$@$@$@$@$@HkF˾WMZԊHHHH MDFkFmTCC`RzuJ'ix*3:9S%Iӗ|O詒)HHHOLo>BxͨdSWCvF }NΤ93i|Mv 7#G$@$@Hg?]D[bu&^3ja|O5'ߔܯNeB@2(-ːt@;.>ڵaj   pET݋qN~ܭ,ժGL1lA+zw4ujq҄ڇ5a ]NAz(jdLJ!u5: tCqC>Y!+ pR(/^wD.$@$@$@>@@.^xt5PN_w"IZ_fɄj[ .-E!eH G boQ(Њ)bv V%͝HĨ&ûQS3A0.u(Pȗ#̔:uƮH`2ALmoԜ1hC[pI>/tKz/H' SBoeKp#;[\~ MHTRKG0bH>2az'DcAjT܁ʚڡr- xû?qܡdoCXt5t"( qk#     @"{zHCB@mY֋HHHHH$L6݅_=@>/eA6Z!    ;+/o{E@:<(*`.$@$@$@$@$@$nw~v|[lT*AܾW{Mo4jŨ/ @@P*DNa0,F㰗9؝4)HHHHH ?zŠƴAGjz^V^*Œ x?}%zI[YY(Ο]Z?j,J$@$@$@$@$%"+[3Mů.ć}or~bw.^=$@,MIDAT$@$@$@$@#rn3j++s\S-GIHHHHH^pMX     "ڀjNVHHHHHFzKY, @@Hm@5'+C$@$@$@$@$@#ڑެ- N?RsQX     "ڀjNVHHHHHFz(KY, @@Hm@5'+C$@$@$@$@$@#ڑެ- N?RsQX     "ڀjNVHHHHHFz(KY, @@Hm@5'+C$@$@$@$@$@#ڑެ- N?RsQX     "ڀjNVHHHHHFz(KY, @@Hm@5'+C$@$@$@$@$@#Gj\S%,HHHHH p6!     E#\K;Y @@Hm@7/+G$@$@$@$@$@MFm`/kG$@$@$@$@$@MFm@7/+G$@$@$@$@$@MFm`/kG$@$@$@$@$@MFm@7/+G$@$@$@$@$@MFm`/kG$@$@$@$@$@MFm@7/+G$@$@$@$@$@MFm`/kG$@$@$@$@$@MFm@7/+G$@$@$@$@$@MFm`/kG$@$@$@$@$@MFm@7/+G$@$@$@$@$@M_d^3WĤI1*2ғEQ6 |2pi!V3jCjc<< >/׿ z+DzA+,t$@$@$@$@$@$@# `=qxĨSB+zS& 6l [݇GZV4h\G$@$@$@$@$@G@ e[ѝ5Q9)Ý:R @}(-PȋzB,e Q[] f CTl<ōGCДYIHH P Djo+9֋H|$~p]ҿ4Q-v%g= z]롖cy93k>{\ht?.ӆZ7cs5k~ ~W̍w`{6?֌jnЀW Sލ:}jqH1k7WO_OXzcr:Rr3& p4dwV#ӏݩ e 1\ULt|M"-8͠Y41clmymxOV+)n/@w:!y>41'wWK"u7hcAVlMﮫ\jwnMM8xtxiRj'up  L$@$@NPN%%jAcvg-cY或(qS.)o?e: z1@~nr%ΎnHŎ+{Ӫ]ggQ؛{ \{X  p@f1! H@;߹i]g'4FPcΊ%\3|;~l\81.t1iMaj܈};"Tv[l* ɗWw~JzlivE<ͱ"bl|XlqL'_Ong*%v ~JDJ 07Cղ(gql blcm=$&   ~ Ш/ @<~dž[/e/Xحm{p8e?w9e'bj鏛8|\k\\t~077 3[^F3y$b-y+k`_-ZZ!-{m(Şꍸ.NG˜5݁ymoKu\v]˴$@$@$Нw's   0j LW;"gcfͨ]' Z5٣ >2|{pnz?rc#C:fB`pN㐑<#޲1HH|w}@C@$@$8jف3z*WKhj`|x4Ϛ;ps:ӆ_ئ9սzN+ӱ8|6Z{B9rP\?'9FYĞL̒4Ǟ^73We|)W' RvqehBut>n})x  `$@$@#@S~HABhZȳq8Gjohˏ[%_ T!gVV|#2-޶6;'u2<+oÞlzlZT{ >d[_&4-wd3ڔ_sloHb[  p; HH&غT*-zdJP,nkf,lUgn/+[KElPC*cfpFBLY2Y qdHjej"dW-e*MXN<{{5׾ob9=xJ,/{:$ $QY&• ɺe'A8D{^Pw뾻;>/SQj_(pGOAs@Z )iDefХկUļ/32h‶R?[}բ=q\0cFi{ʌ4Mj8 @\L=@کm;ox/YXɒE` ]*mޮ'ulOڬ;O֦lNծ?=V9TuSWpHU}uqx_T^IxL:G^ܯ}/lRfIXu\?U=$|s'?z%NHs;=/U~x$TزVk/p@HN69'Z!dH`MGHk|ImBER9y(KYaC}fپZ6٪:u K LUC/7Z=+U,[k~z\V%J$4鈷w֬화S Tk,#?(}{R{B+y_@H,@qb"@?@j%mZUfzek_kH*˟{g$J߻CgY$[c߹sy7}W{QQ=~ֲegjfzq@I]@V R:*=v)]a'+TxYI Wh۞WYsMM)њ_mU[룊Sޞ6_Oz|QG4 !`܃M:HPVk;;8Nà?C$3@{L_ K22>[ohLk΀ &0ޞy}/M<]yЕc_'@h KkmF?݂A=v\ݜ @_ o|@@8v섻ﬔ M\GrB@@77tsL4+Im?:G4i34c!  䆀:#:b?YIj lyr$tЌ   nBde(3%ZA)L   SgR(Z8Oh]'F@@@ Y+?t   _/>   @̭"P@@@I_}@@@ ͙[E   ~Z   9#@R3@@@@$~@@@rF6gn"  Hj"#  1ȉIENDB`sif-2.8.3/go.mod000066400000000000000000000011251432675271000134210ustar00rootroot00000000000000module github.com/sylabs/sif/v2 go 1.18 require ( github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 github.com/google/uuid v1.3.0 github.com/sebdah/goldie/v2 v2.5.3 github.com/spf13/cobra v1.6.1 github.com/spf13/pflag v1.0.5 ) require ( github.com/cloudflare/circl v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/sergi/go-diff v1.2.0 // indirect golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac // indirect ) sif-2.8.3/go.sum000066400000000000000000000106021432675271000134460ustar00rootroot00000000000000github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 h1:ra2OtmuW0AE5csawV4YXMNGNQQXvLRps3z2Z59OPO+I= github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/cloudflare/circl v1.1.0 h1:bZgT/A+cikZnKIwn7xL2OBj012Bmvho/o6RpRvv3GKY= github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sebdah/goldie/v2 v2.5.3 h1:9ES/mNN+HNUbNWpVAlrzuZ7jE+Nrczbj8uFRjM7624Y= github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac h1:oN6lz7iLW/YC7un8pq+9bOLyXrprv2+DKfkJY+2LJJw= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= sif-2.8.3/internal/000077500000000000000000000000001432675271000141305ustar00rootroot00000000000000sif-2.8.3/internal/app/000077500000000000000000000000001432675271000147105ustar00rootroot00000000000000sif-2.8.3/internal/app/siftool/000077500000000000000000000000001432675271000163675ustar00rootroot00000000000000sif-2.8.3/internal/app/siftool/app.go000066400000000000000000000024771432675271000175100ustar00rootroot00000000000000// Copyright (c) 2021-2022, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package siftool import ( "io" "os" ) // appOpts contains configured options. type appOpts struct { out io.Writer err io.Writer } // AppOpt are used to configure optional behavior. type AppOpt func(*appOpts) error // App holds state and configured options. type App struct { opts appOpts } // OptAppOutput specifies that output should be written to w. func OptAppOutput(w io.Writer) AppOpt { return func(o *appOpts) error { o.out = w return nil } } // OptAppError specifies that errors should be written to w. func OptAppError(w io.Writer) AppOpt { return func(o *appOpts) error { o.err = w return nil } } // New creates a new App configured with opts. // // By default, application output and errors are written to os.Stdout and os.Stderr respectively. // To modify this behavior, consider using OptAppOutput and/or OptAppError. func New(opts ...AppOpt) (*App, error) { a := App{ opts: appOpts{ out: os.Stdout, err: os.Stderr, }, } for _, opt := range opts { if err := opt(&a.opts); err != nil { return nil, err } } return &a, nil } sif-2.8.3/internal/app/siftool/file.go000066400000000000000000000013371432675271000176410ustar00rootroot00000000000000// Copyright (c) 2021-2022, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package siftool import ( "os" "github.com/sylabs/sif/v2/pkg/sif" ) // withFileImage calls fn with a FileImage loaded from path. func withFileImage(path string, writable bool, fn func(*sif.FileImage) error) error { flag := os.O_RDONLY if writable { flag = os.O_RDWR } f, err := sif.LoadContainerFromPath(path, sif.OptLoadWithFlag(flag)) if err != nil { return err } err = fn(f) if uerr := f.UnloadContainer(); err == nil { err = uerr } return err } sif-2.8.3/internal/app/siftool/info.go000066400000000000000000000145551432675271000176630ustar00rootroot00000000000000// Copyright (c) 2018-2022, Sylabs Inc. All rights reserved. // Copyright (c) 2018, Divya Cote All rights reserved. // Copyright (c) 2017, SingularityWare, LLC. All rights reserved. // Copyright (c) 2017, Yannick Cote All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package siftool import ( "fmt" "io" "math" "strings" "text/tabwriter" "github.com/google/uuid" "github.com/sylabs/sif/v2/pkg/sif" ) // readableSize returns the size in human readable format. func readableSize(size int64) string { if -1024 < size && size < 1024 { return fmt.Sprintf("%d B", size) } units := "KMGTPE" div, exp := uint64(1024), 0 for n := size / 1024; (n <= -1024) || (1024 <= n); n /= 1024 { div *= 1024 exp++ } return fmt.Sprintf("%.0f %ciB", math.Round(float64(size)/float64(div)), units[exp]) } // writeHeader writes header information in f to w. func writeHeader(w io.Writer, f *sif.FileImage) error { tw := tabwriter.NewWriter(w, 0, 0, 1, ' ', 0) if s := f.LaunchScript(); s != "" { fmt.Fprintf(tw, "Launch Script:\t%v\n", strings.TrimSuffix(s, "\n")) } fmt.Fprintf(tw, "Version:\t%v\n", f.Version()) if arch := f.PrimaryArch(); arch != "unknown" { fmt.Fprintf(tw, "Primary Architecture:\t%v\n", arch) } if id := f.ID(); id != uuid.Nil.String() { fmt.Fprintf(tw, "ID:\t%v\n", id) } if t := f.CreatedAt(); !t.IsZero() { fmt.Fprintf(tw, "Created At:\t%v\n", t.UTC()) } if t := f.ModifiedAt(); !t.IsZero() { fmt.Fprintf(tw, "Modified At:\t%v\n", t.UTC()) } fmt.Fprintf(tw, "Descriptors Free:\t%v\n", f.DescriptorsFree()) fmt.Fprintf(tw, "Descriptors Total:\t%v\n", f.DescriptorsTotal()) fmt.Fprintf(tw, "Descriptors Offset:\t%v\n", f.DescriptorsOffset()) fmt.Fprintf(tw, "Descriptors Size:\t%v\n", readableSize(f.DescriptorsSize())) fmt.Fprintf(tw, "Data Offset:\t%v\n", f.DataOffset()) fmt.Fprintf(tw, "Data Size:\t%v\n", readableSize(f.DataSize())) return tw.Flush() } // Header displays a SIF file global header. func (a *App) Header(path string) error { return withFileImage(path, false, func(f *sif.FileImage) error { return writeHeader(a.opts.out, f) }) } // writeList writes the list of descriptors in f to w. func writeList(w io.Writer, f *sif.FileImage) error { fmt.Fprintln(w, ("------------------------------------------------------------------------------")) fmt.Fprintf(w, "%-4s %-8s %-8s %-26s %s\n", "ID", "|GROUP", "|LINK", "|SIF POSITION (start-end)", "|TYPE") fmt.Fprintln(w, ("------------------------------------------------------------------------------")) f.WithDescriptors(func(d sif.Descriptor) bool { fmt.Fprintf(w, "%-4d ", d.ID()) if id := d.GroupID(); id == 0 { fmt.Fprintf(w, "|%-7s ", "NONE") } else { fmt.Fprintf(w, "|%-7d ", id) } switch id, isGroup := d.LinkedID(); { case id == 0: fmt.Fprintf(w, "|%-7s ", "NONE") case isGroup: fmt.Fprintf(w, "|%-3d (G) ", id) default: fmt.Fprintf(w, "|%-7d ", id) } fmt.Fprintf(w, "%-26s ", fmt.Sprintf("|%d-%d ", d.Offset(), d.Offset()+d.Size())) switch dt := d.DataType(); dt { case sif.DataPartition: fs, pt, arch, err := d.PartitionMetadata() if err == nil { fmt.Fprintf(w, "|%s (%s/%s/%s)\n", dt, fs, pt, arch) } case sif.DataSignature: ht, _, err := d.SignatureMetadata() if err == nil { fmt.Fprintf(w, "|%s (%s)\n", dt, ht) } case sif.DataCryptoMessage: ft, mt, err := d.CryptoMessageMetadata() if err == nil { fmt.Fprintf(w, "|%s (%s/%s)\n", dt, ft, mt) } case sif.DataSBOM: f, err := d.SBOMMetadata() if err == nil { fmt.Fprintf(w, "|%s (%s)\n", dt, f) } default: fmt.Fprintf(w, "|%s\n", dt) } return false }) return nil } // List displays a list of all active descriptors from a SIF file. func (a *App) List(path string) error { return withFileImage(path, false, func(f *sif.FileImage) error { return writeList(a.opts.out, f) }) } // writeInfo writes info about d to w. func writeInfo(w io.Writer, v sif.Descriptor) error { tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) fmt.Fprintf(tw, "\tData Type:\t%v\n", v.DataType()) fmt.Fprintf(tw, "\tID:\t%v\n", v.ID()) fmt.Fprint(tw, "\tGroup ID:\t") if id := v.GroupID(); id == 0 { fmt.Fprintln(tw, "NONE") } else { fmt.Fprintln(tw, id) } fmt.Fprint(tw, "\tLinked ID:\t") switch id, isGroup := v.LinkedID(); { case id == 0: fmt.Fprintln(tw, "NONE") case isGroup: fmt.Fprintln(tw, id, "(G)") default: fmt.Fprintln(tw, id) } fmt.Fprintf(tw, "\tOffset:\t%v\n", v.Offset()) fmt.Fprintf(tw, "\tSize:\t%v\n", v.Size()) if t := v.CreatedAt(); !t.IsZero() { fmt.Fprintf(tw, "\tCreated At:\t%v\n", t.UTC()) } if t := v.ModifiedAt(); !t.IsZero() { fmt.Fprintf(tw, "\tModified At:\t%v\n", t.UTC()) } if nm := v.Name(); nm != "" { fmt.Fprintf(tw, "\tName:\t%v\n", nm) } switch v.DataType() { case sif.DataPartition: fs, pt, arch, err := v.PartitionMetadata() if err != nil { return err } fmt.Fprintf(tw, "\tFilesystem Type:\t%v\n", fs) fmt.Fprintf(tw, "\tPartition Type:\t%v\n", pt) fmt.Fprintf(tw, "\tArchitecture:\t%v\n", arch) case sif.DataSignature: ht, fp, err := v.SignatureMetadata() if err != nil { return err } fmt.Fprintf(tw, "\tHash Type:\t%v\n", ht) if len(fp) > 0 { fmt.Fprintf(tw, "\tEntity:\t%X\n", fp) } case sif.DataCryptoMessage: ft, mt, err := v.CryptoMessageMetadata() if err != nil { return err } fmt.Fprintf(tw, "\tFormat Type:\t%v\n", ft) fmt.Fprintf(tw, "\tMessage Type:\t%v\n", mt) case sif.DataSBOM: f, err := v.SBOMMetadata() if err != nil { return err } fmt.Fprintf(tw, "\tFormat:\t%v\n", f) } return tw.Flush() } // Info displays detailed info about a descriptor from a SIF file. func (a *App) Info(path string, id uint32) error { return withFileImage(path, false, func(f *sif.FileImage) error { d, err := f.GetDescriptor(sif.WithID(id)) if err != nil { return err } return writeInfo(a.opts.out, d) }) } // Dump extracts and outputs a data object from a SIF file. func (a *App) Dump(path string, id uint32) error { return withFileImage(path, false, func(f *sif.FileImage) error { d, err := f.GetDescriptor(sif.WithID(id)) if err != nil { return err } _, err = io.CopyN(a.opts.out, d.GetReader(), d.Size()) return err }) } sif-2.8.3/internal/app/siftool/info_test.go000066400000000000000000000207041432675271000207130ustar00rootroot00000000000000// Copyright (c) 2021-2022, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package siftool import ( "bytes" "errors" "os" "path/filepath" "testing" "github.com/sebdah/goldie/v2" "github.com/sylabs/sif/v2/pkg/sif" ) var corpus = filepath.Join("..", "..", "..", "test", "images") func Test_readableSize(t *testing.T) { tests := []struct { name string size int64 want string }{ { name: "B", size: 1, want: "1 B", }, { name: "KiB", size: 1024, want: "1 KiB", }, { name: "MiB", size: 1024 * 1024, want: "1 MiB", }, { name: "GiB", size: 1024 * 1024 * 1024, want: "1 GiB", }, { name: "TiB", size: 1024 * 1024 * 1024 * 1024, want: "1 TiB", }, { name: "PiB", size: 1024 * 1024 * 1024 * 1024 * 1024, want: "1 PiB", }, { name: "EiB", size: 1024 * 1024 * 1024 * 1024 * 1024 * 1024, want: "1 EiB", }, { name: "Rounding", size: 2047, want: "2 KiB", }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { if got, want := readableSize(tt.size), tt.want; got != want { t.Errorf("got %v, want %v", got, want) } }) } } //nolint:dupl func TestApp_Header(t *testing.T) { tests := []struct { name string path string wantErr error }{ { name: "NotExist", path: "not-exist.sif", wantErr: os.ErrNotExist, }, { name: "Empty", path: filepath.Join(corpus, "empty.sif"), }, { name: "EmptyID", path: filepath.Join(corpus, "empty-id.sif"), }, { name: "EmptyLaunchScript", path: filepath.Join(corpus, "empty-launch-script.sif"), }, { name: "OneObjectTime", path: filepath.Join(corpus, "one-object-time.sif"), }, { name: "OneObjectGenericJSON", path: filepath.Join(corpus, "one-object-generic-json.sif"), }, { name: "OneObjectCryptMessage", path: filepath.Join(corpus, "one-object-crypt-message.sif"), }, { name: "OneObjectSBOM", path: filepath.Join(corpus, "one-object-sbom.sif"), }, { name: "OneGroup", path: filepath.Join(corpus, "one-group.sif"), }, { name: "OneGroupSigned", path: filepath.Join(corpus, "one-group-signed.sif"), }, { name: "OneGroupSignedLegacy", path: filepath.Join(corpus, "one-group-signed-legacy.sif"), }, { name: "OneGroupSignedLegacyAll", path: filepath.Join(corpus, "one-group-signed-legacy-all.sif"), }, { name: "OneGroupSignedLegacyGroup", path: filepath.Join(corpus, "one-group-signed-legacy-group.sif"), }, { name: "TwoGroups", path: filepath.Join(corpus, "two-groups.sif"), }, { name: "TwoGroupsSigned", path: filepath.Join(corpus, "two-groups-signed.sif"), }, { name: "TwoGroupsSignedLegacy", path: filepath.Join(corpus, "two-groups-signed-legacy.sif"), }, { name: "TwoGroupsSignedLegacyAll", path: filepath.Join(corpus, "two-groups-signed-legacy-all.sif"), }, { name: "TwoGroupsSignedLegacyGroup", path: filepath.Join(corpus, "two-groups-signed-legacy-group.sif"), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var b bytes.Buffer a, err := New(OptAppOutput(&b)) if err != nil { t.Fatalf("failed to create app: %v", err) } if got, want := a.Header(tt.path), tt.wantErr; !errors.Is(got, want) { t.Fatalf("got error %v, want %v", got, want) } if tt.wantErr == nil { g := goldie.New(t, goldie.WithTestNameForDir(true)) g.Assert(t, tt.name, b.Bytes()) } }) } } //nolint:dupl func TestApp_List(t *testing.T) { tests := []struct { name string path string wantErr error }{ { name: "NotExist", path: "not-exist.sif", wantErr: os.ErrNotExist, }, { name: "Empty", path: filepath.Join(corpus, "empty.sif"), }, { name: "EmptyID", path: filepath.Join(corpus, "empty-id.sif"), }, { name: "EmptyLaunchScript", path: filepath.Join(corpus, "empty-launch-script.sif"), }, { name: "OneObjectTime", path: filepath.Join(corpus, "one-object-time.sif"), }, { name: "OneObjectGenericJSON", path: filepath.Join(corpus, "one-object-generic-json.sif"), }, { name: "OneObjectCryptMessage", path: filepath.Join(corpus, "one-object-crypt-message.sif"), }, { name: "OneObjectSBOM", path: filepath.Join(corpus, "one-object-sbom.sif"), }, { name: "OneGroup", path: filepath.Join(corpus, "one-group.sif"), }, { name: "OneGroupSigned", path: filepath.Join(corpus, "one-group-signed.sif"), }, { name: "OneGroupSignedLegacy", path: filepath.Join(corpus, "one-group-signed-legacy.sif"), }, { name: "OneGroupSignedLegacyAll", path: filepath.Join(corpus, "one-group-signed-legacy-all.sif"), }, { name: "OneGroupSignedLegacyGroup", path: filepath.Join(corpus, "one-group-signed-legacy-group.sif"), }, { name: "TwoGroups", path: filepath.Join(corpus, "two-groups.sif"), }, { name: "TwoGroupsSigned", path: filepath.Join(corpus, "two-groups-signed.sif"), }, { name: "TwoGroupsSignedLegacy", path: filepath.Join(corpus, "two-groups-signed-legacy.sif"), }, { name: "TwoGroupsSignedLegacyAll", path: filepath.Join(corpus, "two-groups-signed-legacy-all.sif"), }, { name: "TwoGroupsSignedLegacyGroup", path: filepath.Join(corpus, "two-groups-signed-legacy-group.sif"), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var b bytes.Buffer a, err := New(OptAppOutput(&b)) if err != nil { t.Fatalf("failed to create app: %v", err) } if got, want := a.List(tt.path), tt.wantErr; !errors.Is(got, want) { t.Fatalf("got error %v, want %v", got, want) } if tt.wantErr == nil { g := goldie.New(t, goldie.WithTestNameForDir(true)) g.Assert(t, tt.name, b.Bytes()) } }) } } func TestApp_Info(t *testing.T) { tests := []struct { name string path string id uint32 wantErr error }{ { name: "NotExist", path: "not-exist.sif", wantErr: os.ErrNotExist, }, { name: "Time", path: filepath.Join(corpus, "one-object-time.sif"), id: 1, }, { name: "GenericJSON", path: filepath.Join(corpus, "one-object-generic-json.sif"), id: 1, }, { name: "CryptMessage", path: filepath.Join(corpus, "one-object-crypt-message.sif"), id: 1, }, { name: "SBOM", path: filepath.Join(corpus, "one-object-sbom.sif"), id: 1, }, { name: "DataPartitionRaw", path: filepath.Join(corpus, "two-groups-signed.sif"), id: 1, }, { name: "DataPartitionSquashFS", path: filepath.Join(corpus, "two-groups-signed.sif"), id: 2, }, { name: "DataPartitionEXT3", path: filepath.Join(corpus, "two-groups-signed.sif"), id: 3, }, { name: "DataSignature", path: filepath.Join(corpus, "two-groups-signed.sif"), id: 4, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var b bytes.Buffer a, err := New(OptAppOutput(&b)) if err != nil { t.Fatalf("failed to create app: %v", err) } if got, want := a.Info(tt.path, tt.id), tt.wantErr; !errors.Is(got, want) { t.Fatalf("got error %v, want %v", got, want) } if tt.wantErr == nil { g := goldie.New(t, goldie.WithTestNameForDir(true)) g.Assert(t, tt.name, b.Bytes()) } }) } } func TestApp_Dump(t *testing.T) { tests := []struct { name string path string id uint32 wantErr error }{ { name: "NotExist", path: "not-exist.sif", wantErr: os.ErrNotExist, }, { name: "InvalidObjectID", path: filepath.Join(corpus, "one-group-signed.sif"), id: 0, wantErr: sif.ErrInvalidObjectID, }, { name: "One", path: filepath.Join(corpus, "one-group-signed.sif"), id: 1, }, { name: "Two", path: filepath.Join(corpus, "one-group-signed.sif"), id: 2, }, { name: "Three", path: filepath.Join(corpus, "one-group-signed.sif"), id: 3, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var b bytes.Buffer a, err := New(OptAppOutput(&b)) if err != nil { t.Fatalf("failed to create app: %v", err) } if got, want := a.Dump(tt.path, tt.id), tt.wantErr; !errors.Is(got, want) { t.Fatalf("got error %v, want %v", got, want) } if tt.wantErr == nil { g := goldie.New(t, goldie.WithTestNameForDir(true)) g.Assert(t, tt.name, b.Bytes()) } }) } } sif-2.8.3/internal/app/siftool/modif.go000066400000000000000000000027331432675271000200210ustar00rootroot00000000000000// Copyright (c) 2018-2021, Sylabs Inc. All rights reserved. // Copyright (c) 2018, Divya Cote All rights reserved. // Copyright (c) 2017, SingularityWare, LLC. All rights reserved. // Copyright (c) 2017, Yannick Cote All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package siftool import ( "io" "github.com/sylabs/sif/v2/pkg/sif" ) // New creates a new empty SIF file. func (*App) New(path string) error { f, err := sif.CreateContainerAtPath(path) if err != nil { return err } return f.UnloadContainer() } // Add adds a data object to a SIF file. func (*App) Add(path string, t sif.DataType, r io.Reader, opts ...sif.DescriptorInputOpt) error { return withFileImage(path, true, func(f *sif.FileImage) error { input, err := sif.NewDescriptorInput(t, r, opts...) if err != nil { return err } return f.AddObject(input) }) } // Del deletes a specified object descriptor and data from the SIF file. func (*App) Del(path string, id uint32) error { return withFileImage(path, true, func(f *sif.FileImage) error { return f.DeleteObject(id) }) } // Setprim sets the primary system partition of the SIF file. func (*App) Setprim(path string, id uint32) error { return withFileImage(path, true, func(f *sif.FileImage) error { return f.SetPrimPart(id) }) } sif-2.8.3/internal/app/siftool/modif_test.go000066400000000000000000000066051432675271000210620ustar00rootroot00000000000000// Copyright (c) 2021-2022, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package siftool import ( "bytes" "crypto" "errors" "os" "testing" "github.com/sylabs/sif/v2/pkg/sif" ) func TestApp_New(t *testing.T) { a, err := New() if err != nil { t.Fatalf("failed to create app: %v", err) } tf, err := os.CreateTemp("", "sif-test-*") if err != nil { t.Fatal(err) } defer os.Remove(tf.Name()) tf.Close() if err := a.New(tf.Name()); err != nil { t.Fatal(err) } } func TestApp_Add(t *testing.T) { a, err := New() if err != nil { t.Fatalf("failed to create app: %v", err) } tests := []struct { name string data []byte dataType sif.DataType opts []sif.DescriptorInputOpt wantErr error }{ { name: "DataPartition", data: []byte{0xde, 0xad, 0xbe, 0xef}, dataType: sif.DataPartition, opts: []sif.DescriptorInputOpt{ sif.OptPartitionMetadata(sif.FsSquash, sif.PartPrimSys, "386"), }, }, { name: "DataSignature", data: []byte{0xde, 0xad, 0xbe, 0xef}, dataType: sif.DataSignature, opts: []sif.DescriptorInputOpt{ sif.OptSignatureMetadata(crypto.SHA256, []byte{ 0x12, 0x04, 0x5c, 0x8c, 0x0b, 0x10, 0x04, 0xd0, 0x58, 0xde, 0x4b, 0xed, 0xa2, 0x0c, 0x27, 0xee, 0x7f, 0xf7, 0xba, 0x84, }), }, }, { name: "CryptoMessage", data: []byte{0xde, 0xad, 0xbe, 0xef}, dataType: sif.DataCryptoMessage, opts: []sif.DescriptorInputOpt{ sif.OptCryptoMessageMetadata(sif.FormatOpenPGP, sif.MessageClearSignature), }, }, { name: "SBOM", data: []byte{0xde, 0xad, 0xbe, 0xef}, dataType: sif.DataSBOM, opts: []sif.DescriptorInputOpt{ sif.OptSBOMMetadata(sif.SBOMFormatCycloneDXJSON), }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tf, err := os.CreateTemp("", "sif-test-*") if err != nil { t.Fatal(err) } defer os.Remove(tf.Name()) tf.Close() if err := a.New(tf.Name()); err != nil { t.Fatal(err) } data := bytes.NewReader(tt.data) if got, want := a.Add(tf.Name(), tt.dataType, data, tt.opts...), tt.wantErr; !errors.Is(got, want) { t.Fatalf("got error %v, want %v", got, want) } }) } } func TestApp_Del(t *testing.T) { a, err := New() if err != nil { t.Fatalf("failed to create app: %v", err) } tf, err := os.CreateTemp("", "sif-test-*") if err != nil { t.Fatal(err) } defer os.Remove(tf.Name()) tf.Close() if err := a.New(tf.Name()); err != nil { t.Fatal(err) } err = a.Add(tf.Name(), sif.DataGeneric, bytes.NewReader([]byte{0xde, 0xad, 0xbe, 0xef})) if err != nil { t.Fatal(err) } if err := a.Del(tf.Name(), 1); err != nil { t.Fatal(err) } } func TestApp_Setprim(t *testing.T) { a, err := New() if err != nil { t.Fatalf("failed to create app: %v", err) } tf, err := os.CreateTemp("", "sif-test-*") if err != nil { t.Fatal(err) } defer os.Remove(tf.Name()) tf.Close() if err := a.New(tf.Name()); err != nil { t.Fatal(err) } err = a.Add(tf.Name(), sif.DataPartition, bytes.NewReader([]byte{0xde, 0xad, 0xbe, 0xef}), sif.OptPartitionMetadata(sif.FsSquash, sif.PartSystem, "386"), ) if err != nil { t.Fatal(err) } if err := a.Setprim(tf.Name(), 1); err != nil { t.Fatal(err) } } sif-2.8.3/internal/app/siftool/mount.go000066400000000000000000000011411432675271000200550ustar00rootroot00000000000000// Copyright (c) 2022, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package siftool import ( "context" "github.com/sylabs/sif/v2/pkg/user" ) // Mount mounts the primary system partition of the SIF file at path into mountPath. func (a *App) Mount(ctx context.Context, path, mountPath string) error { return user.Mount(ctx, path, mountPath, user.OptMountStdout(a.opts.out), user.OptMountStderr(a.opts.err), ) } sif-2.8.3/internal/app/siftool/testdata/000077500000000000000000000000001432675271000202005ustar00rootroot00000000000000sif-2.8.3/internal/app/siftool/testdata/TestApp_Dump/000077500000000000000000000000001432675271000225455ustar00rootroot00000000000000sif-2.8.3/internal/app/siftool/testdata/TestApp_Dump/One.golden000066400000000000000000000000041432675271000244520ustar00rootroot00000000000000sif-2.8.3/internal/app/siftool/testdata/TestApp_Dump/Three.golden000066400000000000000000000020361432675271000250070ustar00rootroot00000000000000-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 {"version":1,"header":{"digest":"sha256:635fa0a14a8ef0c0351ed3e985799ed1d4f75ce973dea3cc76c99710795cc3f1"},"objects":[{"relativeId":0,"descriptorDigest":"sha256:3634ad01db0dd5482ecf685267b53d6201690438ca27c3d7ea91c971a1f41f92","objectDigest":"sha256:004dfc8da678c309de28b5386a1e9efd57f536b150c40d29b31506aa0fb17ec2"},{"relativeId":1,"descriptorDigest":"sha256:04b5f87c9692a54f80d10fb6af00c779763aeca29d610348854bd97cd8bf66fd","objectDigest":"sha256:9f9c4e5e131934969b4ac8f495691c70b8c6c8e3f489c2c9ab5f1af82bce0604"}]} -----BEGIN PGP SIGNATURE----- wsBzBAEBCAAnBQJe+oD0CZCiDCfuf/e6hBYhBBIEXIwLEATQWN5L7aIMJ+5/97qE AAC46gf/VXyzZ649nttrX13JkM5kRVPlAIblBQxfoUxA1xwIXdRoM5ceDY0Em+YD 8b6Xl1w2sDTqo0R15cJSh8sf0ClFOvYpDQRNCwKx17k1Wd0gHcW4QVu6gJnlbNvN o/EJdEN2TkbCM2aFvj34DAIfErRBIEsCeDDvJ/6WUSySWbnydfNU2pCsnK4A7l2H KOXFzSaPijG9L/pU3O3vNZ+fXPffqHL9JVhs5Mt/Yo3oeoEnoVaKvJLGx/fyl+Gj 7qsfWFyHWzRCww9VFg/TCBeUku0CYRfXhxOgo4OuHNr8oo82rKDZU6+l3UZ2Sw8T +kLe/zUkaILocGOvhvKdi630OGGb/Q== =3Jq2 -----END PGP SIGNATURE-----sif-2.8.3/internal/app/siftool/testdata/TestApp_Dump/Two.golden000066400000000000000000000100001432675271000244770ustar00rootroot00000000000000hsqsgCb sHello from Sylabs! 'xcb]I`bFpy&8Vbf ^xc`F(WRQ`xc`( !sif-2.8.3/internal/app/siftool/testdata/TestApp_Header/000077500000000000000000000000001432675271000230305ustar00rootroot00000000000000sif-2.8.3/internal/app/siftool/testdata/TestApp_Header/Empty.golden000066400000000000000000000002531432675271000253200ustar00rootroot00000000000000Version: 01 Descriptors Free: 48 Descriptors Total: 48 Descriptors Offset: 4096 Descriptors Size: 27 KiB Data Offset: 32176 Data Size: 0 B sif-2.8.3/internal/app/siftool/testdata/TestApp_Header/EmptyID.golden000066400000000000000000000003441432675271000255360ustar00rootroot00000000000000Version: 01 ID: 3fa802cc-358b-45e3-bcc0-69dc7a45f9f8 Descriptors Free: 48 Descriptors Total: 48 Descriptors Offset: 4096 Descriptors Size: 27 KiB Data Offset: 32176 Data Size: 0 B sif-2.8.3/internal/app/siftool/testdata/TestApp_Header/EmptyLaunchScript.golden000066400000000000000000000003311432675271000276350ustar00rootroot00000000000000Launch Script: #!/usr/bin/env run-script Version: 01 Descriptors Free: 48 Descriptors Total: 48 Descriptors Offset: 4096 Descriptors Size: 27 KiB Data Offset: 32176 Data Size: 0 B sif-2.8.3/internal/app/siftool/testdata/TestApp_Header/OneGroup.golden000066400000000000000000000003251432675271000257600ustar00rootroot00000000000000Version: 01 Primary Architecture: 386 Descriptors Free: 46 Descriptors Total: 48 Descriptors Offset: 4096 Descriptors Size: 27 KiB Data Offset: 32176 Data Size: 9 KiB sif-2.8.3/internal/app/siftool/testdata/TestApp_Header/OneGroupSigned.golden000066400000000000000000000003261432675271000271130ustar00rootroot00000000000000Version: 01 Primary Architecture: 386 Descriptors Free: 45 Descriptors Total: 48 Descriptors Offset: 4096 Descriptors Size: 27 KiB Data Offset: 32176 Data Size: 10 KiB sif-2.8.3/internal/app/siftool/testdata/TestApp_Header/OneGroupSignedLegacy.golden000066400000000000000000000006551432675271000302450ustar00rootroot00000000000000Launch Script: #!/usr/bin/env run-singularity Version: 01 Primary Architecture: 386 ID: 6ecc76b7-a497-4f7f-9ebd-8da2a04c6be1 Created At: 2020-05-22 19:30:59 +0000 UTC Modified At: 2020-06-20 20:16:39 +0000 UTC Descriptors Free: 45 Descriptors Total: 48 Descriptors Offset: 4096 Descriptors Size: 27 KiB Data Offset: 32768 Data Size: 9 KiB sif-2.8.3/internal/app/siftool/testdata/TestApp_Header/OneGroupSignedLegacyAll.golden000066400000000000000000000006561432675271000306770ustar00rootroot00000000000000Launch Script: #!/usr/bin/env run-singularity Version: 01 Primary Architecture: 386 ID: 6ecc76b7-a497-4f7f-9ebd-8da2a04c6be1 Created At: 2020-05-22 19:30:59 +0000 UTC Modified At: 2020-06-20 20:17:15 +0000 UTC Descriptors Free: 44 Descriptors Total: 48 Descriptors Offset: 4096 Descriptors Size: 27 KiB Data Offset: 32768 Data Size: 13 KiB sif-2.8.3/internal/app/siftool/testdata/TestApp_Header/OneGroupSignedLegacyGroup.golden000066400000000000000000000006551432675271000312620ustar00rootroot00000000000000Launch Script: #!/usr/bin/env run-singularity Version: 01 Primary Architecture: 386 ID: 6ecc76b7-a497-4f7f-9ebd-8da2a04c6be1 Created At: 2020-05-22 19:30:59 +0000 UTC Modified At: 2020-06-20 20:16:55 +0000 UTC Descriptors Free: 45 Descriptors Total: 48 Descriptors Offset: 4096 Descriptors Size: 27 KiB Data Offset: 32768 Data Size: 9 KiB sif-2.8.3/internal/app/siftool/testdata/TestApp_Header/OneObjectCryptMessage.golden000066400000000000000000000002531432675271000304210ustar00rootroot00000000000000Version: 01 Descriptors Free: 47 Descriptors Total: 48 Descriptors Offset: 4096 Descriptors Size: 27 KiB Data Offset: 32176 Data Size: 4 B sif-2.8.3/internal/app/siftool/testdata/TestApp_Header/OneObjectGenericJSON.golden000066400000000000000000000002531432675271000300610ustar00rootroot00000000000000Version: 01 Descriptors Free: 47 Descriptors Total: 48 Descriptors Offset: 4096 Descriptors Size: 27 KiB Data Offset: 32176 Data Size: 2 B sif-2.8.3/internal/app/siftool/testdata/TestApp_Header/OneObjectSBOM.golden000066400000000000000000000002551432675271000265550ustar00rootroot00000000000000Version: 01 Descriptors Free: 47 Descriptors Total: 48 Descriptors Offset: 4096 Descriptors Size: 27 KiB Data Offset: 32176 Data Size: 454 B sif-2.8.3/internal/app/siftool/testdata/TestApp_Header/OneObjectTime.golden000066400000000000000000000004171432675271000267130ustar00rootroot00000000000000Version: 01 Created At: 2020-06-30 00:01:56 +0000 UTC Modified At: 2020-06-30 00:01:56 +0000 UTC Descriptors Free: 47 Descriptors Total: 48 Descriptors Offset: 4096 Descriptors Size: 27 KiB Data Offset: 32176 Data Size: 2 B sif-2.8.3/internal/app/siftool/testdata/TestApp_Header/TwoGroups.golden000066400000000000000000000003271432675271000261750ustar00rootroot00000000000000Version: 01 Primary Architecture: 386 Descriptors Free: 45 Descriptors Total: 48 Descriptors Offset: 4096 Descriptors Size: 27 KiB Data Offset: 32176 Data Size: 265 KiB sif-2.8.3/internal/app/siftool/testdata/TestApp_Header/TwoGroupsSigned.golden000066400000000000000000000003271432675271000273270ustar00rootroot00000000000000Version: 01 Primary Architecture: 386 Descriptors Free: 43 Descriptors Total: 48 Descriptors Offset: 4096 Descriptors Size: 27 KiB Data Offset: 32176 Data Size: 266 KiB sif-2.8.3/internal/app/siftool/testdata/TestApp_Header/TwoGroupsSignedLegacy.golden000066400000000000000000000006561432675271000304610ustar00rootroot00000000000000Launch Script: #!/usr/bin/env run-singularity Version: 01 Primary Architecture: 386 ID: 0b19ec2c-0b08-46c9-95ae-fa88cd9e48a1 Created At: 2020-05-22 19:30:59 +0000 UTC Modified At: 2020-06-20 20:16:44 +0000 UTC Descriptors Free: 44 Descriptors Total: 48 Descriptors Offset: 4096 Descriptors Size: 27 KiB Data Offset: 32768 Data Size: 13 KiB sif-2.8.3/internal/app/siftool/testdata/TestApp_Header/TwoGroupsSignedLegacyAll.golden000066400000000000000000000006561432675271000311120ustar00rootroot00000000000000Launch Script: #!/usr/bin/env run-singularity Version: 01 Primary Architecture: 386 ID: 0b19ec2c-0b08-46c9-95ae-fa88cd9e48a1 Created At: 2020-05-22 19:30:59 +0000 UTC Modified At: 2020-06-20 20:17:19 +0000 UTC Descriptors Free: 43 Descriptors Total: 48 Descriptors Offset: 4096 Descriptors Size: 27 KiB Data Offset: 32768 Data Size: 17 KiB sif-2.8.3/internal/app/siftool/testdata/TestApp_Header/TwoGroupsSignedLegacyGroup.golden000066400000000000000000000006561432675271000314760ustar00rootroot00000000000000Launch Script: #!/usr/bin/env run-singularity Version: 01 Primary Architecture: 386 ID: 0b19ec2c-0b08-46c9-95ae-fa88cd9e48a1 Created At: 2020-05-22 19:30:59 +0000 UTC Modified At: 2020-06-20 20:16:58 +0000 UTC Descriptors Free: 44 Descriptors Total: 48 Descriptors Offset: 4096 Descriptors Size: 27 KiB Data Offset: 32768 Data Size: 13 KiB sif-2.8.3/internal/app/siftool/testdata/TestApp_Info/000077500000000000000000000000001432675271000225335ustar00rootroot00000000000000sif-2.8.3/internal/app/siftool/testdata/TestApp_Info/CryptMessage.golden000066400000000000000000000003071432675271000263330ustar00rootroot00000000000000 Data Type: Cryptographic Message ID: 1 Group ID: 1 Linked ID: NONE Offset: 32176 Size: 4 Format Type: OpenPGP Message Type: Clear Signature sif-2.8.3/internal/app/siftool/testdata/TestApp_Info/DataPartitionEXT3.golden000066400000000000000000000003371432675271000271370ustar00rootroot00000000000000 Data Type: FS ID: 3 Group ID: 2 Linked ID: NONE Offset: 40960 Size: 262144 Filesystem Type: Ext3 Partition Type: System Architecture: amd64 sif-2.8.3/internal/app/siftool/testdata/TestApp_Info/DataPartitionRaw.golden000066400000000000000000000003271432675271000271440ustar00rootroot00000000000000 Data Type: FS ID: 1 Group ID: 1 Linked ID: NONE Offset: 32768 Size: 4 Filesystem Type: Raw Partition Type: System Architecture: 386 sif-2.8.3/internal/app/siftool/testdata/TestApp_Info/DataPartitionSquashFS.golden000066400000000000000000000003401432675271000301030ustar00rootroot00000000000000 Data Type: FS ID: 2 Group ID: 1 Linked ID: NONE Offset: 36864 Size: 4096 Filesystem Type: Squashfs Partition Type: *System Architecture: 386 sif-2.8.3/internal/app/siftool/testdata/TestApp_Info/DataSignature.golden000066400000000000000000000003041432675271000264550ustar00rootroot00000000000000 Data Type: Signature ID: 4 Group ID: NONE Linked ID: 1 (G) Offset: 303104 Size: 1054 Hash Type: SHA-256 Entity: 12045C8C0B1004D058DE4BEDA20C27EE7FF7BA84 sif-2.8.3/internal/app/siftool/testdata/TestApp_Info/GenericJSON.golden000066400000000000000000000002121432675271000257660ustar00rootroot00000000000000 Data Type: JSON.Generic ID: 1 Group ID: 1 Linked ID: NONE Offset: 32176 Size: 2 Name: data.json sif-2.8.3/internal/app/siftool/testdata/TestApp_Info/SBOM.golden000066400000000000000000000002111432675271000244570ustar00rootroot00000000000000 Data Type: SBOM ID: 1 Group ID: 1 Linked ID: NONE Offset: 32176 Size: 454 Format: cyclonedx-json sif-2.8.3/internal/app/siftool/testdata/TestApp_Info/Time.golden000066400000000000000000000003641432675271000246260ustar00rootroot00000000000000 Data Type: JSON.Generic ID: 1 Group ID: 1 Linked ID: NONE Offset: 32176 Size: 2 Created At: 2020-06-30 00:01:56 +0000 UTC Modified At: 2020-06-30 00:01:56 +0000 UTC Name: data.json sif-2.8.3/internal/app/siftool/testdata/TestApp_List/000077500000000000000000000000001432675271000225535ustar00rootroot00000000000000sif-2.8.3/internal/app/siftool/testdata/TestApp_List/Empty.golden000066400000000000000000000003261432675271000250440ustar00rootroot00000000000000------------------------------------------------------------------------------ ID |GROUP |LINK |SIF POSITION (start-end) |TYPE ------------------------------------------------------------------------------ sif-2.8.3/internal/app/siftool/testdata/TestApp_List/EmptyID.golden000066400000000000000000000003261432675271000252610ustar00rootroot00000000000000------------------------------------------------------------------------------ ID |GROUP |LINK |SIF POSITION (start-end) |TYPE ------------------------------------------------------------------------------ sif-2.8.3/internal/app/siftool/testdata/TestApp_List/EmptyLaunchScript.golden000066400000000000000000000003261432675271000273640ustar00rootroot00000000000000------------------------------------------------------------------------------ ID |GROUP |LINK |SIF POSITION (start-end) |TYPE ------------------------------------------------------------------------------ sif-2.8.3/internal/app/siftool/testdata/TestApp_List/OneGroup.golden000066400000000000000000000005521432675271000255050ustar00rootroot00000000000000------------------------------------------------------------------------------ ID |GROUP |LINK |SIF POSITION (start-end) |TYPE ------------------------------------------------------------------------------ 1 |1 |NONE |32768-32772 |FS (Raw/System/386) 2 |1 |NONE |36864-40960 |FS (Squashfs/*System/386) sif-2.8.3/internal/app/siftool/testdata/TestApp_List/OneGroupSigned.golden000066400000000000000000000006611432675271000266400ustar00rootroot00000000000000------------------------------------------------------------------------------ ID |GROUP |LINK |SIF POSITION (start-end) |TYPE ------------------------------------------------------------------------------ 1 |1 |NONE |32768-32772 |FS (Raw/System/386) 2 |1 |NONE |36864-40960 |FS (Squashfs/*System/386) 3 |NONE |1 (G) |40960-42014 |Signature (SHA-256) sif-2.8.3/internal/app/siftool/testdata/TestApp_List/OneGroupSignedLegacy.golden000066400000000000000000000006611432675271000277650ustar00rootroot00000000000000------------------------------------------------------------------------------ ID |GROUP |LINK |SIF POSITION (start-end) |TYPE ------------------------------------------------------------------------------ 1 |1 |NONE |32768-32772 |FS (Raw/System/386) 2 |1 |NONE |36864-36868 |FS (Squashfs/*System/386) 3 |1 |2 |40960-41569 |Signature (SHA-384) sif-2.8.3/internal/app/siftool/testdata/TestApp_List/OneGroupSignedLegacyAll.golden000066400000000000000000000007701432675271000304170ustar00rootroot00000000000000------------------------------------------------------------------------------ ID |GROUP |LINK |SIF POSITION (start-end) |TYPE ------------------------------------------------------------------------------ 1 |1 |NONE |32768-32772 |FS (Raw/System/386) 2 |1 |NONE |36864-36868 |FS (Squashfs/*System/386) 3 |1 |1 |40960-41569 |Signature (SHA-384) 4 |1 |2 |45056-45665 |Signature (SHA-384) sif-2.8.3/internal/app/siftool/testdata/TestApp_List/OneGroupSignedLegacyGroup.golden000066400000000000000000000006611432675271000310020ustar00rootroot00000000000000------------------------------------------------------------------------------ ID |GROUP |LINK |SIF POSITION (start-end) |TYPE ------------------------------------------------------------------------------ 1 |1 |NONE |32768-32772 |FS (Raw/System/386) 2 |1 |NONE |36864-36868 |FS (Squashfs/*System/386) 3 |NONE |1 (G) |40960-41569 |Signature (SHA-384) sif-2.8.3/internal/app/siftool/testdata/TestApp_List/OneObjectCryptMessage.golden000066400000000000000000000004711432675271000301460ustar00rootroot00000000000000------------------------------------------------------------------------------ ID |GROUP |LINK |SIF POSITION (start-end) |TYPE ------------------------------------------------------------------------------ 1 |1 |NONE |32176-32180 |Cryptographic Message (OpenPGP/Clear Signature) sif-2.8.3/internal/app/siftool/testdata/TestApp_List/OneObjectGenericJSON.golden000066400000000000000000000004261432675271000276060ustar00rootroot00000000000000------------------------------------------------------------------------------ ID |GROUP |LINK |SIF POSITION (start-end) |TYPE ------------------------------------------------------------------------------ 1 |1 |NONE |32176-32178 |JSON.Generic sif-2.8.3/internal/app/siftool/testdata/TestApp_List/OneObjectSBOM.golden000066400000000000000000000004371432675271000263020ustar00rootroot00000000000000------------------------------------------------------------------------------ ID |GROUP |LINK |SIF POSITION (start-end) |TYPE ------------------------------------------------------------------------------ 1 |1 |NONE |32176-32630 |SBOM (cyclonedx-json) sif-2.8.3/internal/app/siftool/testdata/TestApp_List/OneObjectTime.golden000066400000000000000000000004261432675271000264360ustar00rootroot00000000000000------------------------------------------------------------------------------ ID |GROUP |LINK |SIF POSITION (start-end) |TYPE ------------------------------------------------------------------------------ 1 |1 |NONE |32176-32178 |JSON.Generic sif-2.8.3/internal/app/siftool/testdata/TestApp_List/TwoGroups.golden000066400000000000000000000006641432675271000257240ustar00rootroot00000000000000------------------------------------------------------------------------------ ID |GROUP |LINK |SIF POSITION (start-end) |TYPE ------------------------------------------------------------------------------ 1 |1 |NONE |32768-32772 |FS (Raw/System/386) 2 |1 |NONE |36864-40960 |FS (Squashfs/*System/386) 3 |2 |NONE |40960-303104 |FS (Ext3/System/amd64) sif-2.8.3/internal/app/siftool/testdata/TestApp_List/TwoGroupsSigned.golden000066400000000000000000000011021432675271000270420ustar00rootroot00000000000000------------------------------------------------------------------------------ ID |GROUP |LINK |SIF POSITION (start-end) |TYPE ------------------------------------------------------------------------------ 1 |1 |NONE |32768-32772 |FS (Raw/System/386) 2 |1 |NONE |36864-40960 |FS (Squashfs/*System/386) 3 |2 |NONE |40960-303104 |FS (Ext3/System/amd64) 4 |NONE |1 (G) |303104-304158 |Signature (SHA-256) 5 |NONE |2 (G) |304158-305013 |Signature (SHA-256) sif-2.8.3/internal/app/siftool/testdata/TestApp_List/TwoGroupsSignedLegacy.golden000066400000000000000000000007731432675271000302040ustar00rootroot00000000000000------------------------------------------------------------------------------ ID |GROUP |LINK |SIF POSITION (start-end) |TYPE ------------------------------------------------------------------------------ 1 |1 |NONE |32768-32772 |FS (Raw/System/386) 2 |1 |NONE |36864-36868 |FS (Squashfs/*System/386) 3 |2 |NONE |40960-40964 |FS (Ext3/System/amd64) 4 |1 |2 |45056-45665 |Signature (SHA-384) sif-2.8.3/internal/app/siftool/testdata/TestApp_List/TwoGroupsSignedLegacyAll.golden000066400000000000000000000011021432675271000306200ustar00rootroot00000000000000------------------------------------------------------------------------------ ID |GROUP |LINK |SIF POSITION (start-end) |TYPE ------------------------------------------------------------------------------ 1 |1 |NONE |32768-32772 |FS (Raw/System/386) 2 |1 |NONE |36864-36868 |FS (Squashfs/*System/386) 3 |2 |NONE |40960-40964 |FS (Ext3/System/amd64) 4 |1 |1 |45056-45665 |Signature (SHA-384) 5 |1 |2 |49152-49761 |Signature (SHA-384) sif-2.8.3/internal/app/siftool/testdata/TestApp_List/TwoGroupsSignedLegacyGroup.golden000066400000000000000000000007731432675271000312210ustar00rootroot00000000000000------------------------------------------------------------------------------ ID |GROUP |LINK |SIF POSITION (start-end) |TYPE ------------------------------------------------------------------------------ 1 |1 |NONE |32768-32772 |FS (Raw/System/386) 2 |1 |NONE |36864-36868 |FS (Squashfs/*System/386) 3 |2 |NONE |40960-40964 |FS (Ext3/System/amd64) 4 |NONE |1 (G) |45056-45665 |Signature (SHA-384) sif-2.8.3/internal/app/siftool/unmount.go000066400000000000000000000010611432675271000204210ustar00rootroot00000000000000// Copyright (c) 2022, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package siftool import ( "context" "github.com/sylabs/sif/v2/pkg/user" ) // Unmounts the filesystem at mountPath. func (a *App) Unmount(ctx context.Context, mountPath string) error { return user.Unmount(ctx, mountPath, user.OptUnmountStdout(a.opts.out), user.OptUnmountStderr(a.opts.err), ) } sif-2.8.3/pkg/000077500000000000000000000000001432675271000130755ustar00rootroot00000000000000sif-2.8.3/pkg/integrity/000077500000000000000000000000001432675271000151135ustar00rootroot00000000000000sif-2.8.3/pkg/integrity/clearsign.go000066400000000000000000000050331432675271000174120ustar00rootroot00000000000000// Copyright (c) 2020-2022, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the LICENSE.md file // distributed with the sources of this project regarding your rights to use or distribute this // software. package integrity import ( "bytes" "crypto" "errors" "io" "time" "github.com/ProtonMail/go-crypto/openpgp" "github.com/ProtonMail/go-crypto/openpgp/clearsign" "github.com/ProtonMail/go-crypto/openpgp/packet" ) var errClearsignedMsgNotFound = errors.New("clearsigned message not found") type clearsignEncoder struct { e *openpgp.Entity config *packet.Config } // newClearsignEncoder returns an encoder that signs messages in clear-sign format using entity e. // If timeFunc is not nil, it is used to generate signature timestamps. func newClearsignEncoder(e *openpgp.Entity, timeFunc func() time.Time) *clearsignEncoder { return &clearsignEncoder{ e: e, config: &packet.Config{ Time: timeFunc, }, } } // signMessage signs the message from r in clear-sign format, and writes the result to w. On // success, the hash function is returned. func (en *clearsignEncoder) signMessage(w io.Writer, r io.Reader) (crypto.Hash, error) { plaintext, err := clearsign.Encode(w, en.e.PrivateKey, en.config) if err != nil { return 0, err } defer plaintext.Close() _, err = io.Copy(plaintext, r) return en.config.Hash(), err } type clearsignDecoder struct { kr openpgp.KeyRing } // newClearsignDecoder returns a decoder that verifies messages in clear-sign format using key // material from kr. func newClearsignDecoder(kr openpgp.KeyRing) *clearsignDecoder { return &clearsignDecoder{ kr: kr, } } // verifyMessage reads a message from r, verifies its signature, and returns the message contents. // On success, the signing entity is set in vr. func (de *clearsignDecoder) verifyMessage(r io.Reader, h crypto.Hash, vr *VerifyResult) ([]byte, error) { data, err := io.ReadAll(r) if err != nil { return nil, err } // Decode clearsign block. b, _ := clearsign.Decode(data) if b == nil { return nil, errClearsignedMsgNotFound } // Hash functions specified for OpenPGP in RFC4880, excluding those that are not currently // recommended by NIST. expectedHashes := []crypto.Hash{ crypto.SHA224, crypto.SHA256, crypto.SHA384, crypto.SHA512, } // Check signature. vr.e, err = openpgp.CheckDetachedSignatureAndHash( de.kr, bytes.NewReader(b.Bytes), b.ArmoredSignature.Body, expectedHashes, nil, ) if err != nil { return nil, err } return b.Plaintext, err } sif-2.8.3/pkg/integrity/clearsign_test.go000066400000000000000000000117731432675271000204610ustar00rootroot00000000000000// Copyright (c) 2020-2022, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the LICENSE.md file // distributed with the sources of this project regarding your rights to use or distribute this // software. package integrity import ( "bufio" "bytes" "crypto" "errors" "io" "reflect" "strings" "testing" "github.com/ProtonMail/go-crypto/openpgp" pgperrors "github.com/ProtonMail/go-crypto/openpgp/errors" "github.com/ProtonMail/go-crypto/openpgp/packet" "github.com/sebdah/goldie/v2" ) var testMessage = `{"One":1,"Two":2} ` func Test_clearsignEncoder_signMessage(t *testing.T) { e := getTestEntity(t) encrypted := getTestEntity(t) encrypted.PrivateKey.Encrypted = true tests := []struct { name string en *clearsignEncoder wantErr bool wantHash crypto.Hash }{ { name: "EncryptedKey", en: newClearsignEncoder(encrypted, fixedTime), wantErr: true, }, { name: "OK", en: newClearsignEncoder(e, fixedTime), wantHash: crypto.SHA256, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { b := bytes.Buffer{} ht, err := tt.en.signMessage(&b, strings.NewReader(testMessage)) if got, want := err, tt.wantErr; (got != nil) != want { t.Fatalf("got error %v, wantErr %v", got, want) } if err == nil { if got, want := ht, tt.wantHash; got != want { t.Errorf("got hash %v, want %v", got, want) } g := goldie.New(t, goldie.WithTestNameForDir(true)) g.Assert(t, tt.name, b.Bytes()) } }) } } func Test_clearsignDecoder_verifyMessage(t *testing.T) { e := getTestEntity(t) // This is used to corrupt the plaintext. corruptClearsign := func(w io.Writer, s string) error { _, err := strings.NewReplacer(`{"One":1,"Two":2}`, `{"One":2,"Two":4}`).WriteString(w, s) return err } // This is used to corrupt the signature. corruptSignature := func(w io.Writer, s string) error { sc := bufio.NewScanner(strings.NewReader(s)) for sigFound, n := false, 0; sc.Scan(); { line := sc.Text() if sigFound { if n == 1 { // Introduce some corruption line = line[:len(line)-1] } n++ } else if line == "-----BEGIN PGP SIGNATURE-----" { sigFound = true } if _, err := io.WriteString(w, line+"\n"); err != nil { return err } } return nil } tests := []struct { name string hash crypto.Hash corrupter func(w io.Writer, s string) error de *clearsignDecoder wantErr error wantEntity *openpgp.Entity }{ { name: "UnknownIssuer", de: newClearsignDecoder(openpgp.EntityList{}), wantErr: pgperrors.ErrUnknownIssuer, }, { name: "CorruptedClearsign", corrupter: corruptClearsign, de: newClearsignDecoder(openpgp.EntityList{e}), wantErr: pgperrors.SignatureError("hash tag doesn't match"), }, { name: "CorruptedSignature", corrupter: corruptSignature, de: newClearsignDecoder(openpgp.EntityList{e}), wantErr: pgperrors.StructuralError("signature subpacket truncated"), }, { name: "DefaultHash", de: newClearsignDecoder(openpgp.EntityList{e}), wantEntity: e, }, { name: "SHA1", hash: crypto.SHA1, de: newClearsignDecoder(openpgp.EntityList{e}), wantErr: pgperrors.StructuralError("hash algorithm mismatch with cleartext message headers"), }, { name: "SHA224", hash: crypto.SHA224, de: newClearsignDecoder(openpgp.EntityList{e}), wantEntity: e, }, { name: "SHA256", hash: crypto.SHA256, de: newClearsignDecoder(openpgp.EntityList{e}), wantEntity: e, }, { name: "SHA384", hash: crypto.SHA384, de: newClearsignDecoder(openpgp.EntityList{e}), wantEntity: e, }, { name: "SHA512", hash: crypto.SHA512, de: newClearsignDecoder(openpgp.EntityList{e}), wantEntity: e, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { b := bytes.Buffer{} // Sign and encode message. en := clearsignEncoder{ e: e, config: &packet.Config{ DefaultHash: tt.hash, Time: fixedTime, }, } h, err := en.signMessage(&b, strings.NewReader(testMessage)) if err != nil { t.Fatal(err) } // Introduce corruption, if applicable. if tt.corrupter != nil { s := b.String() b.Reset() if err := tt.corrupter(&b, s); err != nil { t.Fatal(err) } } // Decode and verify message. var vr VerifyResult message, err := tt.de.verifyMessage(bytes.NewReader(b.Bytes()), h, &vr) if got, want := err, tt.wantErr; !errors.Is(got, want) { t.Fatalf("got error %v, want %v", got, want) } if err == nil { if got, want := string(message), testMessage; got != want { t.Errorf("got message %v, want %v", got, want) } if got, want := vr.e, tt.wantEntity; !reflect.DeepEqual(got, want) { t.Errorf("got entity %+v, want %+v", got, want) } } }) } } sif-2.8.3/pkg/integrity/digest.go000066400000000000000000000073721432675271000167320ustar00rootroot00000000000000// Copyright (c) 2020-2022, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the LICENSE.md file // distributed with the sources of this project regarding your rights to use or distribute this // software. package integrity import ( "bytes" "crypto" "encoding/hex" "encoding/json" "errors" "fmt" "io" "strings" ) var ( errHashUnavailable = errors.New("hash algorithm unavailable") errHashUnsupported = errors.New("hash algorithm unsupported") errDigestMalformed = errors.New("digest malformed") ) // Hash functions supported for digests. var supportedDigestAlgorithms = map[crypto.Hash]string{ crypto.SHA224: "sha224", crypto.SHA256: "sha256", crypto.SHA384: "sha384", crypto.SHA512: "sha512", crypto.SHA512_224: "sha512_224", crypto.SHA512_256: "sha512_256", } // hashValue calculates a digest by applying hash function h to the contents read from r. If h is // not available, errHashUnavailable is returned. func hashValue(h crypto.Hash, r io.Reader) ([]byte, error) { if !h.Available() { return nil, errHashUnavailable } w := h.New() if _, err := io.Copy(w, r); err != nil { return nil, err } return w.Sum(nil), nil } type digest struct { hash crypto.Hash value []byte } // newDigest returns a new digest. If h is not supported, errHashUnsupported is returned. If digest // is malformed, errDigestMalformed is returned. func newDigest(h crypto.Hash, value []byte) (digest, error) { if _, ok := supportedDigestAlgorithms[h]; !ok { return digest{}, errHashUnsupported } if len(value) != h.Size() { return digest{}, errDigestMalformed } return digest{h, value}, nil } // newDigestReader returns a new digest calculated by applying h to r. func newDigestReader(h crypto.Hash, r io.Reader) (digest, error) { value, err := hashValue(h, r) if err != nil { return digest{}, err } return newDigest(h, value) } // newLegacyDigest parses legacy signature plaintext b, and returns a digest based on the hash type // ht and the digest value read from the plaintext. // // For reference, the plaintext of legacy signatures is comprised of the string "SIFHASH:\n", // followed by a digest value. For example: // // SIFHASH: // 2f0b3dca0ec42683d306338f68689aba29cdb83625b8cc0b8a789f8de92342495a6264b0c134e706630636bf90c6f331 func newLegacyDigest(ht crypto.Hash, b []byte) (digest, error) { b = bytes.TrimPrefix(b, []byte("SIFHASH:\n")) b = bytes.TrimSuffix(b, []byte("\n")) // Decode hex input. value := make([]byte, hex.DecodedLen(len(b))) if _, err := hex.Decode(value, b); err != nil { return digest{}, err } return newDigest(ht, value) } // matches returns whether the digest in d matches r. func (d digest) matches(r io.Reader) (bool, error) { value, err := hashValue(d.hash, r) if err != nil { return false, err } return bytes.Equal(d.value, value), nil } // MarshalJSON marshals d into string of format "alg:value". func (d digest) MarshalJSON() ([]byte, error) { n, ok := supportedDigestAlgorithms[d.hash] if !ok { return nil, errHashUnsupported } return json.Marshal(fmt.Sprintf("%s:%x", n, d.value)) } // UnmarshalJSON unmarshals d from a string of format "alg:value". func (d *digest) UnmarshalJSON(data []byte) error { var s string if err := json.Unmarshal(data, &s); err != nil { return fmt.Errorf("%w: %v", errDigestMalformed, err) } parts := strings.Split(s, ":") if len(parts) != 2 { return errDigestMalformed } name := parts[0] value := parts[1] v, err := hex.DecodeString(value) if err != nil { return fmt.Errorf("%w: %v", errDigestMalformed, err) } for h, n := range supportedDigestAlgorithms { if n == name { digest, err := newDigest(h, v) if err != nil { return err } *d = digest return nil } } return errHashUnsupported } sif-2.8.3/pkg/integrity/digest_test.go000066400000000000000000000176271432675271000177750ustar00rootroot00000000000000// Copyright (c) 2020-2022, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the LICENSE.md file // distributed with the sources of this project regarding your rights to use or distribute this // software. package integrity import ( "bytes" "crypto" "encoding/hex" "encoding/json" "errors" "io" "reflect" "strings" "testing" "github.com/sebdah/goldie/v2" ) func TestNewLegacyDigest(t *testing.T) { tests := []struct { name string ht crypto.Hash text string wantError error wantDigest digest }{ { name: "HashUnsupported", ht: 0, wantError: errHashUnsupported, }, { name: "DigestMalformed", ht: crypto.SHA256, text: "1234", wantError: errDigestMalformed, }, { name: "HexLength", ht: crypto.SHA256, text: "12345", wantError: hex.ErrLength, }, { name: "SHA256", ht: crypto.SHA256, text: "SIFHASH:\n9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\n", wantDigest: digest{ hash: crypto.SHA256, value: []byte{ 0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0x0b, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0x0a, 0x08, }, }, }, { name: "SHA384", ht: crypto.SHA384, text: "SIFHASH:\n768412320f7b0aa5812fce428dc4706b3cae50e02a64caa16a782249bfe8efc4b7ef1ccb126255d196047dfedf17a0a9\n", wantDigest: digest{ hash: crypto.SHA384, value: []byte{ 0x76, 0x84, 0x12, 0x32, 0x0f, 0x7b, 0x0a, 0xa5, 0x81, 0x2f, 0xce, 0x42, 0x8d, 0xc4, 0x70, 0x6b, 0x3c, 0xae, 0x50, 0xe0, 0x2a, 0x64, 0xca, 0xa1, 0x6a, 0x78, 0x22, 0x49, 0xbf, 0xe8, 0xef, 0xc4, 0xb7, 0xef, 0x1c, 0xcb, 0x12, 0x62, 0x55, 0xd1, 0x96, 0x04, 0x7d, 0xfe, 0xdf, 0x17, 0xa0, 0xa9, }, }, }, { name: "SHA512", ht: crypto.SHA512, text: "SIFHASH:\nee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff\n", //nolint:lll wantDigest: digest{ hash: crypto.SHA512, value: []byte{ 0xee, 0x26, 0xb0, 0xdd, 0x4a, 0xf7, 0xe7, 0x49, 0xaa, 0x1a, 0x8e, 0xe3, 0xc1, 0x0a, 0xe9, 0x92, 0x3f, 0x61, 0x89, 0x80, 0x77, 0x2e, 0x47, 0x3f, 0x88, 0x19, 0xa5, 0xd4, 0x94, 0x0e, 0x0d, 0xb2, 0x7a, 0xc1, 0x85, 0xf8, 0xa0, 0xe1, 0xd5, 0xf8, 0x4f, 0x88, 0xbc, 0x88, 0x7f, 0xd6, 0x7b, 0x14, 0x37, 0x32, 0xc3, 0x04, 0xcc, 0x5f, 0xa9, 0xad, 0x8e, 0x6f, 0x57, 0xf5, 0x00, 0x28, 0xa8, 0xff, }, }, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { d, err := newLegacyDigest(tt.ht, []byte(tt.text)) if got, want := err, tt.wantError; !errors.Is(got, want) { t.Fatalf("got error %v, want %v", got, want) } if err == nil { if got, want := d, tt.wantDigest; !reflect.DeepEqual(got, want) { t.Errorf("got digest %v, want %v", got, want) } } }) } } func TestDigest_MarshalJSON(t *testing.T) { tests := []struct { name string hash crypto.Hash value string wantErr error }{ { name: "HashUnsupportedMD5", hash: crypto.MD5, wantErr: errHashUnsupported, }, { name: "HashUnsupportedSHA1", hash: crypto.SHA1, wantErr: errHashUnsupported, }, { name: "SHA224", hash: crypto.SHA224, value: "95041dd60ab08c0bf5636d50be85fe9790300f39eb84602858a9b430", }, { name: "SHA256", hash: crypto.SHA256, value: "a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447", }, { name: "SHA384", hash: crypto.SHA384, value: "6b3b69ff0a404f28d75e98a066d3fc64fffd9940870cc68bece28545b9a75086b343d7a1366838083e4b8f3ca6fd3c80", }, { name: "SHA512", hash: crypto.SHA512, value: "db3974a97f2407b7cae1ae637c0030687a11913274d578492558e39c16c017de84eacdc8c62fe34ee4e12b4b1428817f09b6a2760c3f8a664ceae94d2434a593", //nolint:lll }, { name: "SHA512_224", hash: crypto.SHA512_224, value: "06001bf08dfb17d2b54925116823be230e98b5c6c278303bc4909a8c", }, { name: "SHA512_256", hash: crypto.SHA512_256, value: "3d37fe58435e0d87323dee4a2c1b339ef954de63716ee79f5747f94d974f913f", }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { value, err := hex.DecodeString(tt.value) if err != nil { t.Fatal(err) } input := digest{tt.hash, value} b := bytes.Buffer{} err = json.NewEncoder(&b).Encode(input) if got, want := err, tt.wantErr; !errors.Is(got, want) { t.Fatalf("got error %v, want %v", got, want) } if err == nil { g := goldie.New(t, goldie.WithTestNameForDir(true)) g.Assert(t, tt.name, b.Bytes()) } }) } } func TestDigest_UnmarshalJSON(t *testing.T) { tests := []struct { name string r io.Reader wantHash crypto.Hash wantValue string wantErr error }{ { name: "Empty", r: strings.NewReader(""), wantErr: io.EOF, }, { name: "MissingValue", r: strings.NewReader("{}"), wantErr: errDigestMalformed, }, { name: "BadValue", r: strings.NewReader(`"bad"`), wantErr: errDigestMalformed, }, { name: "HashUnsupportedMD5", r: strings.NewReader(`"md5:b0804ec967f48520697662a204f5fe72"`), wantErr: errHashUnsupported, }, { name: "HashUnsupportedSHA1", r: strings.NewReader(`"sha1:597f6a540010f94c15d71806a99a2c8710e747bd"`), wantErr: errHashUnsupported, }, { name: "DigestMalformedNotHex", r: strings.NewReader(`"sha256:oops"`), wantErr: errDigestMalformed, }, { name: "DigestMalformedIncorrectLen", r: strings.NewReader(`"sha256:597f"`), wantErr: errDigestMalformed, }, { name: "SHA224", r: strings.NewReader(`"sha224:95041dd60ab08c0bf5636d50be85fe9790300f39eb84602858a9b430"`), wantHash: crypto.SHA224, wantValue: "95041dd60ab08c0bf5636d50be85fe9790300f39eb84602858a9b430", }, { name: "SHA256", r: strings.NewReader(`"sha256:a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447"`), wantHash: crypto.SHA256, wantValue: "a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447", }, { name: "SHA384", r: strings.NewReader(`"sha384:6b3b69ff0a404f28d75e98a066d3fc64fffd9940870cc68bece28545b9a75086b343d7a1366838083e4b8f3ca6fd3c80"`), //nolint:lll wantHash: crypto.SHA384, wantValue: "6b3b69ff0a404f28d75e98a066d3fc64fffd9940870cc68bece28545b9a75086b343d7a1366838083e4b8f3ca6fd3c80", }, { name: "SHA512", r: strings.NewReader(`"sha512:db3974a97f2407b7cae1ae637c0030687a11913274d578492558e39c16c017de84eacdc8c62fe34ee4e12b4b1428817f09b6a2760c3f8a664ceae94d2434a593"`), //nolint:lll wantHash: crypto.SHA512, wantValue: "db3974a97f2407b7cae1ae637c0030687a11913274d578492558e39c16c017de84eacdc8c62fe34ee4e12b4b1428817f09b6a2760c3f8a664ceae94d2434a593", //nolint:lll }, { name: "SHA512_224", r: strings.NewReader(`"sha512_224:06001bf08dfb17d2b54925116823be230e98b5c6c278303bc4909a8c"`), wantHash: crypto.SHA512_224, wantValue: "06001bf08dfb17d2b54925116823be230e98b5c6c278303bc4909a8c", }, { name: "SHA512_256", r: strings.NewReader(`"sha512_256:3d37fe58435e0d87323dee4a2c1b339ef954de63716ee79f5747f94d974f913f"`), wantHash: crypto.SHA512_256, wantValue: "3d37fe58435e0d87323dee4a2c1b339ef954de63716ee79f5747f94d974f913f", }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { var d digest err := json.NewDecoder(tt.r).Decode(&d) if got, want := err, tt.wantErr; !errors.Is(got, want) { t.Fatalf("got error %v, want %v", got, want) } if got, want := d.hash, tt.wantHash; got != want { t.Errorf("got hash %v, want %v", got, want) } if got, want := hex.EncodeToString(d.value), tt.wantValue; got != want { t.Errorf("got value %v, want %v", got, want) } }) } } sif-2.8.3/pkg/integrity/doc.go000066400000000000000000000027441432675271000162160ustar00rootroot00000000000000// Copyright (c) 2020-2022, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the LICENSE.md file // distributed with the sources of this project regarding your rights to use or distribute this // software. /* Package integrity implements functions to add, examine, and verify digital signatures in a SIF image. # Sign To add one or more digital signatures to a SIF, create a Signer, and supply a signing PGP entity: s, err := integrity.NewSigner(f, OptSignWithEntity(e)) By default, the returned Signer will add one digital signature per group of objects in f. To override this behavior, supply additional options. For example, to apply a signature to object group 1 only: s, err := integrity.NewSigner(f, OptSignWithEntity(e), OptSignGroup(1)) Finally, to apply the signature(s): err := s.Sign() # Verify To examine and/or verify digital signatures in a SIF, create a Verifier: v, err := NewVerifier(f) If you intend to perform cryptographic verification, you must provide a source of key material: v, err := NewVerifier(f, OptVerifyWithKeyRing(kr)) By default, the returned Verifier will consider non-legacy signatures for all object groups. To override this behavior, supply additional options. For example, to consider non-legacy signatures on object group 1 only: v, err := NewVerifier(f, OptVerifyWithKeyRing(kr), OptVerifyGroup(1)) Finally, to perform cryptographic verification: err := v.Verify() */ package integrity sif-2.8.3/pkg/integrity/main_test.go000066400000000000000000000026141432675271000174300ustar00rootroot00000000000000// Copyright (c) 2020-2021, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the LICENSE.md file // distributed with the sources of this project regarding your rights to use or distribute this // software. package integrity import ( "os" "path/filepath" "testing" "time" "github.com/ProtonMail/go-crypto/openpgp" "github.com/sylabs/sif/v2/pkg/sif" ) var corpus = filepath.Join("..", "..", "test", "images") // fixedTime returns a fixed time value, useful for ensuring tests are deterministic. func fixedTime() time.Time { return time.Unix(1504657553, 0) } // loadContainer loads a container from path for read-only access. func loadContainer(t *testing.T, path string) *sif.FileImage { t.Helper() f, err := sif.LoadContainerFromPath(path, sif.OptLoadWithFlag(os.O_RDONLY)) if err != nil { t.Fatal(err) } t.Cleanup(func() { if err := f.UnloadContainer(); err != nil { t.Error(err) } }) return f } // getTestEntity returns a fixed test PGP entity. func getTestEntity(t *testing.T) *openpgp.Entity { t.Helper() f, err := os.Open(filepath.Join("..", "..", "test", "keys", "private.asc")) if err != nil { t.Fatal(err) } defer f.Close() el, err := openpgp.ReadArmoredKeyRing(f) if err != nil { t.Fatal(err) } if got, want := len(el), 1; got != want { t.Fatalf("got %v entities, want %v", got, want) } return el[0] } sif-2.8.3/pkg/integrity/metadata.go000066400000000000000000000171251432675271000172300ustar00rootroot00000000000000// Copyright (c) 2020-2021, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the LICENSE.md file // distributed with the sources of this project regarding your rights to use or distribute this // software. package integrity import ( "crypto" "errors" "fmt" "io" "github.com/sylabs/sif/v2/pkg/sif" ) var ( errObjectNotSigned = errors.New("object not signed") errSignedObjectNotFound = errors.New("signed object not found") errMinimumIDInvalid = errors.New("minimum ID value invalid") ) // ErrHeaderIntegrity is the error returned when the integrity of the SIF global header is // compromised. var ErrHeaderIntegrity = errors.New("header integrity compromised") // DescriptorIntegrityError records an error in cryptographic verification of a data object // descriptor. type DescriptorIntegrityError struct { ID uint32 // Data object ID. } func (e *DescriptorIntegrityError) Error() string { if e.ID == 0 { return "data object descriptor integrity compromised" } return fmt.Sprintf("data object descriptor integrity compromised: %v", e.ID) } // Is compares e against target. If target is a DescriptorIntegrityError and matches e or target // has a zero value ID, true is returned. func (e *DescriptorIntegrityError) Is(target error) bool { //nolint:errorlint // don't compare wrapped errors in Is() t, ok := target.(*DescriptorIntegrityError) if !ok { return false } return e.ID == t.ID || t.ID == 0 } // ObjectIntegrityError records an error in cryptographic verification of a data object. type ObjectIntegrityError struct { ID uint32 // Data object ID. } func (e *ObjectIntegrityError) Error() string { if e.ID == 0 { return "data object integrity compromised" } return fmt.Sprintf("data object integrity compromised: %v", e.ID) } // Is compares e against target. If target is a ObjectIntegrityError and matches e or target has a // zero value ID, true is returned. func (e *ObjectIntegrityError) Is(target error) bool { //nolint:errorlint // don't compare wrapped errors in Is() t, ok := target.(*ObjectIntegrityError) if !ok { return false } return e.ID == t.ID || t.ID == 0 } type headerMetadata struct { Digest digest `json:"digest"` } // getHeaderMetadata returns headerMetadata for the fields read from r, using hash algorithm h. func getHeaderMetadata(r io.Reader, h crypto.Hash) (headerMetadata, error) { d, err := newDigestReader(h, r) if err != nil { return headerMetadata{}, err } return headerMetadata{Digest: d}, nil } // matches verifies the fields read fromr matche the metadata in hm. // // If the SIF global header does not match, ErrHeaderIntegrity is returned. func (hm headerMetadata) matches(r io.Reader) error { if ok, err := hm.Digest.matches(r); err != nil { return err } else if !ok { return ErrHeaderIntegrity } return nil } type objectMetadata struct { RelativeID uint32 `json:"relativeId"` DescriptorDigest digest `json:"descriptorDigest"` ObjectDigest digest `json:"objectDigest"` id uint32 // absolute object ID (minID + RelativeID) } // getObjectMetadata returns objectMetadata for object with relativeID, using digests calculated // over descr and data using hash algorithm h. func getObjectMetadata(relativeID uint32, descr, data io.Reader, h crypto.Hash) (objectMetadata, error) { om := objectMetadata{RelativeID: relativeID} // Calculate digest on object descriptor. d, err := newDigestReader(h, descr) if err != nil { return objectMetadata{}, err } om.DescriptorDigest = d // Calculate digest on object data. d, err = newDigestReader(h, data) if err != nil { return objectMetadata{}, err } om.ObjectDigest = d return om, nil } // populateAbsoluteID populates the absolute object ID of om based on minID. func (om *objectMetadata) populateAbsoluteID(minID uint32) { om.id = minID + om.RelativeID } // matches verifies the object described by od matches the metadata in om. // // If the data object descriptor does not match, a DescriptorIntegrityError is returned. If the // data object does not match, a ObjectIntegrityError is returned. func (om objectMetadata) matches(od sif.Descriptor) error { if ok, err := om.DescriptorDigest.matches(od.GetIntegrityReader()); err != nil { return err } else if !ok { return &DescriptorIntegrityError{ID: od.ID()} } if ok, err := om.ObjectDigest.matches(od.GetReader()); err != nil { return err } else if !ok { return &ObjectIntegrityError{ID: od.ID()} } return nil } type mdVersion int const ( metadataVersion1 mdVersion = iota + 1 ) type imageMetadata struct { Version mdVersion `json:"version"` Header headerMetadata `json:"header"` Objects []objectMetadata `json:"objects"` } // getImageMetadata returns populated imageMetadata for object descriptors ods in f, using hash // algorithm h. func getImageMetadata(f *sif.FileImage, minID uint32, ods []sif.Descriptor, h crypto.Hash) (imageMetadata, error) { im := imageMetadata{Version: metadataVersion1} // Add header metadata. hm, err := getHeaderMetadata(f.GetHeaderIntegrityReader(), h) if err != nil { return imageMetadata{}, err } im.Header = hm // Add object descriptor/data metadata. for _, od := range ods { id := od.ID() if id < minID { // shouldn't really be possible... return imageMetadata{}, errMinimumIDInvalid } om, err := getObjectMetadata(id-minID, od.GetIntegrityReader(), od.GetReader(), h) if err != nil { return imageMetadata{}, err } im.Objects = append(im.Objects, om) } im.populateAbsoluteObjectIDs(minID) return im, nil } // populateAbsoluteObjectIDs populates the absolute object ID of each object in im by adding minID // to the relative ID of each object in im. func (im *imageMetadata) populateAbsoluteObjectIDs(minID uint32) { for i := range im.Objects { im.Objects[i].populateAbsoluteID(minID) } } // objectIDsMatch verifies the object IDs described by ods match exactly the object IDs described // by im. func (im imageMetadata) objectIDsMatch(ods []sif.Descriptor) error { ids := make(map[uint32]bool) for _, om := range im.Objects { ids[om.id] = false } // Check each object in ods exists in ids, and mark as seen. for _, od := range ods { id := od.ID() if _, ok := ids[id]; !ok { return fmt.Errorf("object %d: %w", id, errObjectNotSigned) } ids[id] = true } // Check that all objects in ids were seen. for id, seen := range ids { if !seen { return fmt.Errorf("object %d: %w", id, errSignedObjectNotFound) } } return nil } // metadataForObject retrieves the objectMetadata for object specified by id. func (im imageMetadata) metadataForObject(id uint32) (objectMetadata, error) { for _, om := range im.Objects { if om.id == id { return om, nil } } return objectMetadata{}, fmt.Errorf("object %d: %w", id, errObjectNotSigned) } // matches verifies the header and objects described by ods match the metadata in im. // // If the SIF global header does not match, ErrHeaderIntegrity is returned. If the data object // descriptor does not match, a DescriptorIntegrityError is returned. If the data object does not // match, a ObjectIntegrityError is returned. func (im imageMetadata) matches(f *sif.FileImage, ods []sif.Descriptor) ([]sif.Descriptor, error) { verified := make([]sif.Descriptor, 0, len(ods)) // Verify header metadata. if err := im.Header.matches(f.GetHeaderIntegrityReader()); err != nil { return verified, err } // Verify data object metadata. for _, od := range ods { om, err := im.metadataForObject(od.ID()) if err != nil { return verified, err } if err := om.matches(od); err != nil { return verified, err } verified = append(verified, od) } return verified, nil } sif-2.8.3/pkg/integrity/metadata_test.go000066400000000000000000000135471432675271000202730ustar00rootroot00000000000000// Copyright (c) 2020-2022, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the LICENSE.md file // distributed with the sources of this project regarding your rights to use or distribute this // software. package integrity import ( "bytes" "crypto" _ "crypto/sha256" _ "crypto/sha512" "encoding/json" "errors" "io" "os" "path/filepath" "strings" "testing" "github.com/sebdah/goldie/v2" "github.com/sylabs/sif/v2/pkg/sif" ) func TestGetHeaderMetadata(t *testing.T) { b, err := os.ReadFile(filepath.Join("testdata", "sources", "header.bin")) if err != nil { t.Fatal(err) } tests := []struct { name string header io.Reader hash crypto.Hash wantErr error }{ {name: "HashUnavailable", header: bytes.NewReader(b), hash: crypto.MD4, wantErr: errHashUnavailable}, {name: "HashUnsupportedMD5", header: bytes.NewReader(b), hash: crypto.MD5, wantErr: errHashUnsupported}, {name: "HashUnsupportedSHA1", header: bytes.NewReader(b), hash: crypto.SHA1, wantErr: errHashUnsupported}, {name: "SHA224", header: bytes.NewReader(b), hash: crypto.SHA224}, {name: "SHA256", header: bytes.NewReader(b), hash: crypto.SHA256}, {name: "SHA384", header: bytes.NewReader(b), hash: crypto.SHA384}, {name: "SHA512", header: bytes.NewReader(b), hash: crypto.SHA512}, {name: "SHA512_224", header: bytes.NewReader(b), hash: crypto.SHA512_224}, {name: "SHA512_256", header: bytes.NewReader(b), hash: crypto.SHA512_256}, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { md, err := getHeaderMetadata(tt.header, tt.hash) if got, want := err, tt.wantErr; !errors.Is(got, want) { t.Fatalf("got error %v, want %v", got, want) } if err == nil { b := bytes.Buffer{} if err := json.NewEncoder(&b).Encode(md); err != nil { t.Fatal(err) } g := goldie.New(t, goldie.WithTestNameForDir(true)) g.Assert(t, tt.name, b.Bytes()) } }) } } func TestGetObjectMetadata(t *testing.T) { // Byte stream that represents integrity-protected fields of an arbitrary descriptor with // relative ID of zero. rid0, err := os.ReadFile(filepath.Join("testdata", "sources", "descr-rid0.bin")) if err != nil { t.Fatal(err) } // Byte stream that represents integrity-protected fields of an arbitrary descriptor with // relative ID of one. rid1, err := os.ReadFile(filepath.Join("testdata", "sources", "descr-rid1.bin")) if err != nil { t.Fatal(err) } tests := []struct { name string relativeID uint32 descr io.Reader data io.Reader hash crypto.Hash wantErr error }{ {name: "HashUnavailable", descr: bytes.NewReader(rid0), hash: crypto.MD4, wantErr: errHashUnavailable}, {name: "HashUnsupportedMD5", descr: bytes.NewReader(rid0), hash: crypto.MD5, wantErr: errHashUnsupported}, {name: "HashUnsupportedSHA1", descr: bytes.NewReader(rid0), hash: crypto.SHA1, wantErr: errHashUnsupported}, {name: "RelativeID", relativeID: 1, descr: bytes.NewReader(rid1), data: strings.NewReader("blah"), hash: crypto.SHA256}, //nolint:lll {name: "SHA224", descr: bytes.NewReader(rid0), data: strings.NewReader("blah"), hash: crypto.SHA224}, {name: "SHA256", descr: bytes.NewReader(rid0), data: strings.NewReader("blah"), hash: crypto.SHA256}, {name: "SHA384", descr: bytes.NewReader(rid0), data: strings.NewReader("blah"), hash: crypto.SHA384}, {name: "SHA512", descr: bytes.NewReader(rid0), data: strings.NewReader("blah"), hash: crypto.SHA512}, {name: "SHA512_224", descr: bytes.NewReader(rid0), data: strings.NewReader("blah"), hash: crypto.SHA512_224}, {name: "SHA512_256", descr: bytes.NewReader(rid0), data: strings.NewReader("blah"), hash: crypto.SHA512_256}, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { md, err := getObjectMetadata(tt.relativeID, tt.descr, tt.data, tt.hash) if got, want := err, tt.wantErr; !errors.Is(got, want) { t.Fatalf("got error %v, want %v", got, want) } if err == nil { b := bytes.Buffer{} if err := json.NewEncoder(&b).Encode(md); err != nil { t.Fatal(err) } g := goldie.New(t, goldie.WithTestNameForDir(true)) g.Assert(t, tt.name, b.Bytes()) } }) } } func TestGetImageMetadata(t *testing.T) { f := loadContainer(t, filepath.Join(corpus, "one-group.sif")) od1, err := f.GetDescriptor(sif.WithID(1)) if err != nil { t.Fatal(err) } od2, err := f.GetDescriptor(sif.WithID(2)) if err != nil { t.Fatal(err) } tests := []struct { name string minID uint32 ods []sif.Descriptor hash crypto.Hash wantErr error }{ {name: "HashUnavailable", hash: crypto.MD4, wantErr: errHashUnavailable}, {name: "HashUnsupportedMD5", hash: crypto.MD5, wantErr: errHashUnsupported}, {name: "HashUnsupportedSHA1", hash: crypto.SHA1, wantErr: errHashUnsupported}, {name: "MinimumIDInvalid", minID: 2, ods: []sif.Descriptor{od1}, hash: crypto.SHA256, wantErr: errMinimumIDInvalid}, {name: "Object1", minID: 1, ods: []sif.Descriptor{od1}, hash: crypto.SHA256}, {name: "Object2", minID: 1, ods: []sif.Descriptor{od2}, hash: crypto.SHA256}, {name: "SHA224", minID: 1, ods: []sif.Descriptor{od1, od2}, hash: crypto.SHA224}, {name: "SHA256", minID: 1, ods: []sif.Descriptor{od1, od2}, hash: crypto.SHA256}, {name: "SHA384", minID: 1, ods: []sif.Descriptor{od1, od2}, hash: crypto.SHA384}, {name: "SHA512", minID: 1, ods: []sif.Descriptor{od1, od2}, hash: crypto.SHA512}, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { md, err := getImageMetadata(f, tt.minID, tt.ods, tt.hash) if got, want := err, tt.wantErr; !errors.Is(got, want) { t.Fatalf("got error %v, want %v", got, want) } if err == nil { b := bytes.Buffer{} if err := json.NewEncoder(&b).Encode(md); err != nil { t.Fatal(err) } g := goldie.New(t, goldie.WithTestNameForDir(true)) g.Assert(t, tt.name, b.Bytes()) } }) } } sif-2.8.3/pkg/integrity/result.go000066400000000000000000000022161432675271000167610ustar00rootroot00000000000000// Copyright (c) 2020-2021, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the LICENSE.md file // distributed with the sources of this project regarding your rights to use or distribute this // software. package integrity import ( "github.com/ProtonMail/go-crypto/openpgp" "github.com/sylabs/sif/v2/pkg/sif" ) // VerifyResult describes the results of an individual signature validation. type VerifyResult struct { sig sif.Descriptor verified []sif.Descriptor e *openpgp.Entity err error } // Signature returns the signature object associated with the result. func (r VerifyResult) Signature() sif.Descriptor { return r.sig } // Verified returns the data objects that were verified. func (r VerifyResult) Verified() []sif.Descriptor { return r.verified } // Entity returns the signing entity, or nil if the signing entity could not be determined. func (r VerifyResult) Entity() *openpgp.Entity { return r.e } // Error returns an error describing the reason verification failed, or nil if verification was // successful. func (r VerifyResult) Error() error { return r.err } sif-2.8.3/pkg/integrity/select.go000066400000000000000000000133201432675271000167200ustar00rootroot00000000000000// Copyright (c) 2020-2022, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the LICENSE.md file // distributed with the sources of this project regarding your rights to use or distribute this // software. package integrity import ( "bytes" "errors" "fmt" "sort" "github.com/ProtonMail/go-crypto/openpgp/clearsign" "github.com/sylabs/sif/v2/pkg/sif" ) var ( errGroupNotFound = errors.New("group not found") errNoGroupsFound = errors.New("no groups found") ) // insertSorted inserts unique vals into the sorted slice s. func insertSorted(s []uint32, vals ...uint32) []uint32 { for _, val := range vals { val := val i := sort.Search(len(s), func(i int) bool { return s[i] >= val }) if i < len(s) && s[i] == val { continue } s = append(s, 0) copy(s[i+1:], s[i:]) s[i] = val } return s } // getGroupObjects returns all descriptors in f that are contained in the object group with // identifier groupID. If no such object group is found, errGroupNotFound is returned. func getGroupObjects(f *sif.FileImage, groupID uint32) ([]sif.Descriptor, error) { ods, err := f.GetDescriptors(sif.WithGroupID(groupID)) if err == nil && len(ods) == 0 { err = errGroupNotFound } return ods, err } // SignatureNotFoundError records an error attempting to locate one or more signatures for a data // object or data object group. type SignatureNotFoundError struct { ID uint32 // ID of the object/group for which signature was not found. IsGroup bool // If true, ID is a group ID. Otherwise, ID is an object ID. } func (e *SignatureNotFoundError) Error() string { if e.ID == 0 { return "signature not found" } if e.IsGroup { return fmt.Sprintf("signature not found for object group %v", e.ID) } return fmt.Sprintf("signature not found for object %v", e.ID) } // Is compares e against target. If target is a SignatureNotFoundError and matches e or target has // a zero value ID, true is returned. func (e *SignatureNotFoundError) Is(target error) bool { //nolint:errorlint // don't compare wrapped errors in Is() t, ok := target.(*SignatureNotFoundError) if !ok { return false } if e.ID == t.ID && e.IsGroup == t.IsGroup { return true } return t.ID == 0 } // getObjectSignatures returns all descriptors in f that contain signature objects linked to the // object with identifier id. If no such signatures are found, a SignatureNotFoundError is // returned. func getObjectSignatures(f *sif.FileImage, id uint32) ([]sif.Descriptor, error) { sigs, err := f.GetDescriptors( sif.WithDataType(sif.DataSignature), sif.WithLinkedID(id), ) if err != nil { return nil, err } if len(sigs) == 0 { return nil, &SignatureNotFoundError{ID: id} } return sigs, nil } // isLegacySignature returns true if data contains a legacy signature. func isLegacySignature(data []byte) bool { // Legacy signatures always encoded in clear-sign format. b, _ := clearsign.Decode(data) if b == nil { return false } // The plaintext of legacy signatures always begins with "SIFHASH", and non-legacy signatures // never do, as they are JSON. return bytes.HasPrefix(b.Plaintext, []byte("SIFHASH:\n")) } // getGroupSignatures returns descriptors in f that contain signature objects linked to the object // group with identifier groupID. If legacy is true, only legacy signatures are considered. // Otherwise, only non-legacy signatures are considered. If no such signatures are found, a // SignatureNotFoundError is returned. func getGroupSignatures(f *sif.FileImage, groupID uint32, legacy bool) ([]sif.Descriptor, error) { // Get list of signature blocks linked to group, taking legacy flag into consideration. sigs, err := f.GetDescriptors( sif.WithDataType(sif.DataSignature), sif.WithLinkedGroupID(groupID), func(od sif.Descriptor) (bool, error) { b, err := od.GetData() if err != nil { return false, err } return isLegacySignature(b) == legacy, err }, ) if err != nil { return nil, err } if len(sigs) == 0 { return nil, &SignatureNotFoundError{IsGroup: true, ID: groupID} } return sigs, nil } // getGroupMinObjectID returns the minimum ID from the set of descriptors in f that are contained // in the object group with identifier groupID. If no such object group is found, errGroupNotFound // is returned. func getGroupMinObjectID(f *sif.FileImage, groupID uint32) (uint32, error) { minID := ^uint32(0) f.WithDescriptors(func(od sif.Descriptor) bool { if od.GroupID() != groupID { return false } if id := od.ID(); id < minID { minID = id } return false }) if minID == ^uint32(0) { return 0, errGroupNotFound } return minID, nil } // getGroupIDs returns all identifiers for the groups contained in f, sorted by ID. If no groups // are present, errNoGroupsFound is returned. func getGroupIDs(f *sif.FileImage) ([]uint32, error) { var groupIDs []uint32 f.WithDescriptors(func(od sif.Descriptor) bool { if groupID := od.GroupID(); groupID != 0 { groupIDs = insertSorted(groupIDs, groupID) } return false }) if len(groupIDs) == 0 { return nil, errNoGroupsFound } return groupIDs, nil } // getFingerprints returns a sorted list of unique fingerprints contained in sigs. func getFingerprints(sigs []sif.Descriptor) ([][]byte, error) { fps := make([][]byte, 0, len(sigs)) for _, sig := range sigs { _, fp, err := sig.SignatureMetadata() if err != nil { return nil, err } if len(fp) == 0 { continue } // Check if fingerprint is already in list. i := sort.Search(len(fps), func(i int) bool { return bytes.Compare(fps[i], fp) >= 0 }) if i < len(fps) && bytes.Equal(fps[i], fp) { continue } // Insert into (sorted) list. fps = append(fps, []byte{}) copy(fps[i+1:], fps[i:]) fps[i] = fp } return fps, nil } sif-2.8.3/pkg/integrity/sign.go000066400000000000000000000251321432675271000164050ustar00rootroot00000000000000// Copyright (c) 2020-2022, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the LICENSE.md file // distributed with the sources of this project regarding your rights to use or distribute this // software. package integrity import ( "bytes" "crypto" "encoding/json" "errors" "fmt" "io" "sort" "time" "github.com/ProtonMail/go-crypto/openpgp" "github.com/sylabs/sif/v2/pkg/sif" ) var ( errNoObjectsSpecified = errors.New("no objects specified") errUnexpectedGroupID = errors.New("unexpected group ID") errNilFileImage = errors.New("nil file image") ) // ErrNoKeyMaterial is the error returned when no key material was provided. var ErrNoKeyMaterial = errors.New("key material not provided") type encoder interface { // signMessage signs the message from r, and writes the result to w. On success, the signature // hash function is returned. signMessage(w io.Writer, r io.Reader) (ht crypto.Hash, err error) } type groupSigner struct { en encoder // Message encoder. f *sif.FileImage // SIF image to sign. id uint32 // Group ID. ods []sif.Descriptor // Descriptors of object(s) to sign. mdHash crypto.Hash // Hash type for metadata. fp []byte // Fingerprint of signing entity. } // groupSignerOpt are used to configure gs. type groupSignerOpt func(gs *groupSigner) error // optSignGroupObjects specifies the signature include objects with the specified ids. func optSignGroupObjects(ids ...uint32) groupSignerOpt { return func(gs *groupSigner) error { if len(ids) == 0 { return errNoObjectsSpecified } for _, id := range ids { od, err := gs.f.GetDescriptor(sif.WithID(id)) if err != nil { return err } if err := gs.addObject(od); err != nil { return err } } return nil } } // optSignGroupMetadataHash sets h as the metadata hash function. func optSignGroupMetadataHash(h crypto.Hash) groupSignerOpt { return func(gs *groupSigner) error { gs.mdHash = h return nil } } // optSignGroupFingerprint sets fp as the fingerprint of the signing entity. func optSignGroupFingerprint(fp []byte) groupSignerOpt { return func(gs *groupSigner) error { gs.fp = fp return nil } } // newGroupSigner returns a new groupSigner to add a digital signature using en for the specified // group to f, according to opts. // // By default, all data objects in the group will be signed. To override this behavior, use // optSignGroupObjects(). To override the default metadata hash algorithm, use // optSignGroupMetadataHash(). // // By default, the fingerprint of the signing entity is not set. To override this behavior, use // optSignGroupFingerprint. func newGroupSigner(en encoder, f *sif.FileImage, groupID uint32, opts ...groupSignerOpt) (*groupSigner, error) { if groupID == 0 { return nil, sif.ErrInvalidGroupID } gs := groupSigner{ en: en, f: f, id: groupID, mdHash: crypto.SHA256, } // Apply options. for _, opt := range opts { if err := opt(&gs); err != nil { return nil, err } } // If no object descriptors specified, select all in group. if len(gs.ods) == 0 { ods, err := getGroupObjects(f, groupID) if err != nil { return nil, err } for _, od := range ods { if err := gs.addObject(od); err != nil { return nil, err } } } return &gs, nil } // addObject adds od to the list of object descriptors to be signed. func (gs *groupSigner) addObject(od sif.Descriptor) error { if groupID := od.GroupID(); groupID != gs.id { return fmt.Errorf("%w (%v)", errUnexpectedGroupID, groupID) } // Insert into sorted descriptor list, if not already present. i := sort.Search(len(gs.ods), func(i int) bool { return gs.ods[i].ID() >= od.ID() }) if i < len(gs.ods) && gs.ods[i].ID() == od.ID() { return nil } gs.ods = append(gs.ods, sif.Descriptor{}) copy(gs.ods[i+1:], gs.ods[i:]) gs.ods[i] = od return nil } // sign creates a digital signature as specified by gs. func (gs *groupSigner) sign() (sif.DescriptorInput, error) { // Get minimum object ID in group. Object IDs in the image metadata will be relative to this. minID, err := getGroupMinObjectID(gs.f, gs.id) if err != nil { return sif.DescriptorInput{}, err } // Get metadata for the image. md, err := getImageMetadata(gs.f, minID, gs.ods, gs.mdHash) if err != nil { return sif.DescriptorInput{}, fmt.Errorf("failed to get image metadata: %w", err) } // Encode image metadata. enc, err := json.Marshal(md) if err != nil { return sif.DescriptorInput{}, fmt.Errorf("failed to encode image metadata: %w", err) } // Sign image metadata. b := bytes.Buffer{} ht, err := gs.en.signMessage(&b, bytes.NewReader(enc)) if err != nil { return sif.DescriptorInput{}, fmt.Errorf("failed to sign message: %w", err) } // Prepare SIF data object descriptor. return sif.NewDescriptorInput(sif.DataSignature, &b, sif.OptNoGroup(), sif.OptLinkedGroupID(gs.id), sif.OptSignatureMetadata(ht, gs.fp), ) } type signOpts struct { e *openpgp.Entity groupIDs []uint32 objectIDs [][]uint32 timeFunc func() time.Time deterministic bool } // SignerOpt are used to configure so. type SignerOpt func(so *signOpts) error // OptSignWithEntity specifies e as the entity to use to generate signature(s). func OptSignWithEntity(e *openpgp.Entity) SignerOpt { return func(so *signOpts) error { so.e = e return nil } } // OptSignGroup specifies that a signature be applied to cover all objects in the group with the // specified groupID. This may be called multiple times to add multiple group signatures. func OptSignGroup(groupID uint32) SignerOpt { return func(so *signOpts) error { so.groupIDs = append(so.groupIDs, groupID) return nil } } // OptSignObjects specifies that one or more signature(s) be applied to cover objects with the // specified ids. One signature will be applied for each group ID associated with the object(s). // This may be called multiple times to add multiple signatures. func OptSignObjects(ids ...uint32) SignerOpt { return func(so *signOpts) error { if len(ids) == 0 { return errNoObjectsSpecified } so.objectIDs = append(so.objectIDs, ids) return nil } } // OptSignWithTime specifies fn as the func to obtain signature timestamp(s). Unless // OptSignDeterministic is supplied, fn is also used to set SIF timestamps. func OptSignWithTime(fn func() time.Time) SignerOpt { return func(so *signOpts) error { so.timeFunc = fn return nil } } // OptSignDeterministic sets SIF header/descriptor fields to values that support deterministic // modification of images. This does not affect the signature timestamps; to specify deterministic // signature timestamps, use OptSignWithTime. func OptSignDeterministic() SignerOpt { return func(so *signOpts) error { so.deterministic = true return nil } } // withGroupedObjects splits the objects represented by ids into object groups, and calls fn once // per object group. func withGroupedObjects(f *sif.FileImage, ids []uint32, fn func(uint32, []uint32) error) error { var groupIDs []uint32 groupObjectIDs := make(map[uint32][]uint32) for _, id := range ids { od, err := f.GetDescriptor(sif.WithID(id)) if err != nil { return err } // Note the group ID if it hasn't been seen before, and append the object ID to the // appropriate group in the map. groupID := od.GroupID() if _, ok := groupObjectIDs[groupID]; !ok { groupIDs = append(groupIDs, groupID) } groupObjectIDs[groupID] = append(groupObjectIDs[groupID], id) } sort.Slice(groupIDs, func(i, j int) bool { return groupIDs[i] < groupIDs[j] }) for _, groupID := range groupIDs { if err := fn(groupID, groupObjectIDs[groupID]); err != nil { return err } } return nil } // Signer describes a SIF image signer. type Signer struct { f *sif.FileImage opts signOpts signers []*groupSigner } // NewSigner returns a Signer to add digital signature(s) to f, according to opts. Key material // must be provided, or an error wrapping ErrNoKeyMaterial is returned. // // To use key material from an OpenPGP entity, use OptSignWithEntity. // // By default, one digital signature is added per object group in f. To override this behavior, // consider using OptSignGroup and/or OptSignObjects. // // By default, signature, header and descriptor timestamps are set to the current time. To override // this behavior, consider using OptSignWithTime or OptSignDeterministic. func NewSigner(f *sif.FileImage, opts ...SignerOpt) (*Signer, error) { if f == nil { return nil, fmt.Errorf("integrity: %w", errNilFileImage) } so := signOpts{ timeFunc: time.Now, } // Apply options. for _, opt := range opts { if err := opt(&so); err != nil { return nil, fmt.Errorf("integrity: %w", err) } } s := Signer{ f: f, opts: so, } var commonOpts []groupSignerOpt // Get message encoder. var en encoder switch { case so.e != nil: en = newClearsignEncoder(so.e, so.timeFunc) commonOpts = append(commonOpts, optSignGroupFingerprint(so.e.PrimaryKey.Fingerprint)) default: return nil, fmt.Errorf("integrity: %w", ErrNoKeyMaterial) } // Add signer for each groupID. for _, groupID := range so.groupIDs { gs, err := newGroupSigner(en, f, groupID, commonOpts...) if err != nil { return nil, fmt.Errorf("integrity: %w", err) } s.signers = append(s.signers, gs) } // Add signer(s) for each list of object IDs. for _, ids := range so.objectIDs { err := withGroupedObjects(f, ids, func(groupID uint32, ids []uint32) error { opts := commonOpts opts = append(opts, optSignGroupObjects(ids...)) gs, err := newGroupSigner(en, f, groupID, opts...) if err != nil { return err } s.signers = append(s.signers, gs) return nil }) if err != nil { return nil, fmt.Errorf("integrity: %w", err) } } // If no signers specified, add one per object group. if len(s.signers) == 0 { ids, err := getGroupIDs(f) if err != nil { return nil, fmt.Errorf("integrity: %w", err) } for _, id := range ids { gs, err := newGroupSigner(en, f, id, commonOpts...) if err != nil { return nil, fmt.Errorf("integrity: %w", err) } s.signers = append(s.signers, gs) } } return &s, nil } // Sign adds digital signatures as specified by s. func (s *Signer) Sign() error { for _, gs := range s.signers { di, err := gs.sign() if err != nil { return fmt.Errorf("integrity: %w", err) } var opts []sif.AddOpt if s.opts.deterministic { opts = append(opts, sif.OptAddDeterministic()) } else { opts = append(opts, sif.OptAddWithTime(s.opts.timeFunc())) } if err := s.f.AddObject(di, opts...); err != nil { return fmt.Errorf("integrity: failed to add object: %w", err) } } return nil } sif-2.8.3/pkg/integrity/sign_test.go000066400000000000000000000350071432675271000174460ustar00rootroot00000000000000// Copyright (c) 2020-2022, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the LICENSE.md file // distributed with the sources of this project regarding your rights to use or distribute this // software. package integrity import ( "bytes" "crypto" "errors" "os" "path/filepath" "reflect" "testing" "github.com/ProtonMail/go-crypto/openpgp" "github.com/sebdah/goldie/v2" "github.com/sylabs/sif/v2/pkg/sif" ) func TestOptSignGroupObjects(t *testing.T) { twoGroupImage := loadContainer(t, filepath.Join(corpus, "two-groups.sif")) tests := []struct { name string groupID uint32 ids []uint32 wantErr error }{ { name: "NoObjectsSpecified", ids: []uint32{}, wantErr: errNoObjectsSpecified, }, { name: "InvalidObjectID", ids: []uint32{0}, wantErr: sif.ErrInvalidObjectID, }, { name: "UnexpectedGroupID", groupID: 1, ids: []uint32{3}, wantErr: errUnexpectedGroupID, }, { name: "ObjectNotFound", groupID: 1, ids: []uint32{4}, wantErr: sif.ErrObjectNotFound, }, { name: "Object1", groupID: 1, ids: []uint32{1}, }, { name: "Object2", groupID: 1, ids: []uint32{2}, }, { name: "Object3", groupID: 2, ids: []uint32{3}, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { gs := groupSigner{f: twoGroupImage, id: tt.groupID} err := optSignGroupObjects(tt.ids...)(&gs) if got, want := err, tt.wantErr; !errors.Is(got, want) { t.Fatalf("got error %v, want %v", got, want) } if err == nil { var got []uint32 for _, od := range gs.ods { got = append(got, od.ID()) } if want := tt.ids; !reflect.DeepEqual(got, want) { t.Errorf("got objects %v, want %v", got, want) } } }) } } func TestNewGroupSigner(t *testing.T) { emptyImage := loadContainer(t, filepath.Join(corpus, "empty.sif")) twoGroupImage := loadContainer(t, filepath.Join(corpus, "two-groups.sif")) tests := []struct { name string fi *sif.FileImage groupID uint32 opts []groupSignerOpt wantErr error wantObjects []uint32 wantMDHash crypto.Hash wantFP []byte }{ { name: "InvalidGroupID", fi: emptyImage, groupID: 0, wantErr: sif.ErrInvalidGroupID, }, { name: "NoObjects", fi: emptyImage, groupID: 1, wantErr: sif.ErrNoObjects, }, { name: "NoObjectsSpecified", fi: emptyImage, groupID: 1, opts: []groupSignerOpt{optSignGroupObjects()}, wantErr: errNoObjectsSpecified, }, { name: "GroupNotFound", fi: twoGroupImage, groupID: 3, wantErr: errGroupNotFound, }, { name: "Group1", fi: twoGroupImage, groupID: 1, wantObjects: []uint32{1, 2}, wantMDHash: crypto.SHA256, }, { name: "Group2", fi: twoGroupImage, groupID: 2, wantObjects: []uint32{3}, wantMDHash: crypto.SHA256, }, { name: "OptSignGroupObject1", fi: twoGroupImage, groupID: 1, opts: []groupSignerOpt{optSignGroupObjects(1)}, wantObjects: []uint32{1}, wantMDHash: crypto.SHA256, }, { name: "OptSignGroupObject2", fi: twoGroupImage, groupID: 1, opts: []groupSignerOpt{optSignGroupObjects(2)}, wantObjects: []uint32{2}, wantMDHash: crypto.SHA256, }, { name: "OptSignGroupObject3", fi: twoGroupImage, groupID: 2, opts: []groupSignerOpt{optSignGroupObjects(3)}, wantObjects: []uint32{3}, wantMDHash: crypto.SHA256, }, { name: "OptSignGroupMetadataHash", fi: twoGroupImage, groupID: 1, opts: []groupSignerOpt{optSignGroupMetadataHash(crypto.SHA1)}, wantObjects: []uint32{1, 2}, wantMDHash: crypto.SHA1, }, { name: "OptSignGroupMetadataHash", fi: twoGroupImage, groupID: 1, opts: []groupSignerOpt{ optSignGroupFingerprint([]byte{ 0x12, 0x04, 0x5c, 0x8c, 0x0b, 0x10, 0x04, 0xd0, 0x58, 0xde, 0x4b, 0xed, 0xa2, 0x0c, 0x27, 0xee, 0x7f, 0xf7, 0xba, 0x84, }), }, wantObjects: []uint32{1, 2}, wantMDHash: crypto.SHA256, wantFP: []byte{ 0x12, 0x04, 0x5c, 0x8c, 0x0b, 0x10, 0x04, 0xd0, 0x58, 0xde, 0x4b, 0xed, 0xa2, 0x0c, 0x27, 0xee, 0x7f, 0xf7, 0xba, 0x84, }, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { en := newClearsignEncoder(getTestEntity(t), fixedTime) s, err := newGroupSigner(en, tt.fi, tt.groupID, tt.opts...) if got, want := err, tt.wantErr; !errors.Is(got, want) { t.Fatalf("got error %v, want %v", got, want) } if err == nil { if got, want := s.en, en; got != want { t.Errorf("got encoder %v, want %v", got, want) } if got, want := s.f, tt.fi; got != want { t.Errorf("got FileImage %v, want %v", got, want) } if got, want := s.id, tt.groupID; got != want { t.Errorf("got group ID %v, want %v", got, want) } var got []uint32 for _, od := range s.ods { got = append(got, od.ID()) } if want := tt.wantObjects; !reflect.DeepEqual(got, want) { t.Errorf("got objects %v, want %v", got, want) } if got, want := s.mdHash, tt.wantMDHash; got != want { t.Errorf("got metadata hash %v, want %v", got, want) } if got, want := s.fp, tt.wantFP; !bytes.Equal(got, want) { t.Errorf("got fingerprint %v, want %v", got, want) } } }) } } func TestGroupSigner_Sign(t *testing.T) { twoGroups := loadContainer(t, filepath.Join(corpus, "two-groups.sif")) d1, err := twoGroups.GetDescriptor(sif.WithID(1)) if err != nil { t.Fatal(err) } d2, err := twoGroups.GetDescriptor(sif.WithID(2)) if err != nil { t.Fatal(err) } d3, err := twoGroups.GetDescriptor(sif.WithID(3)) if err != nil { t.Fatal(err) } e := getTestEntity(t) clearsign := newClearsignEncoder(e, fixedTime) encrypted := getTestEntity(t) encrypted.PrivateKey.Encrypted = true clearsignEncrypted := newClearsignEncoder(encrypted, fixedTime) tests := []struct { name string gs groupSigner wantErr bool }{ { name: "HashUnavailable", gs: groupSigner{ en: clearsign, f: twoGroups, id: 1, ods: []sif.Descriptor{d1}, mdHash: crypto.MD4, fp: e.PrimaryKey.Fingerprint, }, wantErr: true, }, { name: "EncryptedKey", gs: groupSigner{ en: clearsignEncrypted, f: twoGroups, id: 1, ods: []sif.Descriptor{d1}, mdHash: crypto.SHA1, fp: encrypted.PrimaryKey.Fingerprint, }, wantErr: true, }, { name: "Object1", gs: groupSigner{ en: clearsign, f: twoGroups, id: 1, ods: []sif.Descriptor{d1}, mdHash: crypto.SHA256, fp: e.PrimaryKey.Fingerprint, }, }, { name: "Object2", gs: groupSigner{ en: clearsign, f: twoGroups, id: 1, ods: []sif.Descriptor{d2}, mdHash: crypto.SHA256, fp: e.PrimaryKey.Fingerprint, }, }, { name: "Group1", gs: groupSigner{ en: clearsign, f: twoGroups, id: 1, ods: []sif.Descriptor{d1, d2}, mdHash: crypto.SHA256, fp: e.PrimaryKey.Fingerprint, }, }, { name: "Group2", gs: groupSigner{ en: clearsign, f: twoGroups, id: 2, ods: []sif.Descriptor{d3}, mdHash: crypto.SHA256, fp: e.PrimaryKey.Fingerprint, }, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { di, err := tt.gs.sign() if (err != nil) != tt.wantErr { t.Fatalf("got error %v, want %v", err, tt.wantErr) } if err == nil { var buf sif.Buffer fi, err := sif.CreateContainer(&buf, sif.OptCreateDeterministic(), sif.OptCreateWithDescriptors(di), ) if err != nil { t.Fatal(err) } t.Cleanup(func() { if err := fi.UnloadContainer(); err != nil { t.Error(err) } }) if err := fi.UnloadContainer(); err != nil { t.Error(err) } g := goldie.New(t, goldie.WithTestNameForDir(true)) g.Assert(t, tt.name, buf.Bytes()) } }) } } func TestNewSigner(t *testing.T) { emptyImage := loadContainer(t, filepath.Join(corpus, "empty.sif")) oneGroupImage := loadContainer(t, filepath.Join(corpus, "one-group.sif")) twoGroupImage := loadContainer(t, filepath.Join(corpus, "two-groups.sif")) e := getTestEntity(t) tests := []struct { name string fi *sif.FileImage opts []SignerOpt wantErr error wantGroupObjects map[uint32][]uint32 wantEntity *openpgp.Entity }{ { name: "NilFileImage", fi: nil, opts: []SignerOpt{ OptSignWithEntity(e), }, wantErr: errNilFileImage, }, { name: "NoGroupsFound", fi: emptyImage, opts: []SignerOpt{ OptSignWithEntity(e), }, wantErr: errNoGroupsFound, }, { name: "InvalidGroupID", fi: emptyImage, opts: []SignerOpt{ OptSignWithEntity(e), OptSignGroup(0), }, wantErr: sif.ErrInvalidGroupID, }, { name: "NoObjectsSpecified", fi: emptyImage, opts: []SignerOpt{ OptSignWithEntity(e), OptSignObjects(), }, wantErr: errNoObjectsSpecified, }, { name: "NoObjects", fi: emptyImage, opts: []SignerOpt{ OptSignWithEntity(e), OptSignObjects(1), }, wantErr: sif.ErrNoObjects, }, { name: "InvalidObjectID", fi: oneGroupImage, opts: []SignerOpt{ OptSignWithEntity(e), OptSignObjects(0), }, wantErr: sif.ErrInvalidObjectID, }, { name: "OneGroupDefaultObjects", fi: oneGroupImage, opts: []SignerOpt{ OptSignWithEntity(e), }, wantGroupObjects: map[uint32][]uint32{1: {1, 2}}, }, { name: "TwoGroupDefaultObjects", fi: twoGroupImage, opts: []SignerOpt{ OptSignWithEntity(e), }, wantGroupObjects: map[uint32][]uint32{1: {1, 2}, 2: {3}}, }, { name: "OptSignGroup1", fi: twoGroupImage, opts: []SignerOpt{ OptSignWithEntity(e), OptSignGroup(1), }, wantGroupObjects: map[uint32][]uint32{1: {1, 2}}, }, { name: "OptSignGroup2", fi: twoGroupImage, opts: []SignerOpt{ OptSignWithEntity(e), OptSignGroup(2), }, wantGroupObjects: map[uint32][]uint32{2: {3}}, }, { name: "OptSignObject1", fi: twoGroupImage, opts: []SignerOpt{ OptSignWithEntity(e), OptSignObjects(1), }, wantGroupObjects: map[uint32][]uint32{1: {1}}, }, { name: "OptSignObject2", fi: twoGroupImage, opts: []SignerOpt{ OptSignWithEntity(e), OptSignObjects(2), }, wantGroupObjects: map[uint32][]uint32{1: {2}}, }, { name: "OptSignObject3", fi: twoGroupImage, opts: []SignerOpt{ OptSignWithEntity(e), OptSignObjects(3), }, wantGroupObjects: map[uint32][]uint32{2: {3}}, }, { name: "OptSignObjects", fi: twoGroupImage, opts: []SignerOpt{ OptSignWithEntity(e), OptSignObjects(1, 2, 3), }, wantGroupObjects: map[uint32][]uint32{1: {1, 2}, 2: {3}}, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { s, err := NewSigner(tt.fi, tt.opts...) if got, want := err, tt.wantErr; !errors.Is(got, want) { t.Fatalf("got error %v, want %v", got, want) } if err == nil { if got, want := s.f, tt.fi; got != want { t.Errorf("got FileImage %v, want %v", got, want) } if got, want := len(s.signers), len(tt.wantGroupObjects); got != want { t.Errorf("got %v signers, want %v", got, want) } for _, signer := range s.signers { groupID := signer.id if want, ok := tt.wantGroupObjects[groupID]; !ok { t.Errorf("unexpected signer for group ID %v", groupID) } else { var got []uint32 for _, od := range signer.ods { got = append(got, od.ID()) } if !reflect.DeepEqual(got, want) { t.Errorf("got objects %v, want %v", got, want) } } } } }) } } func TestSigner_Sign(t *testing.T) { e := getTestEntity(t) encrypted := getTestEntity(t) encrypted.PrivateKey.Encrypted = true tests := []struct { name string inputFile string opts []SignerOpt wantErr bool }{ { name: "EncryptedKey", inputFile: "one-group.sif", opts: []SignerOpt{OptSignWithEntity(encrypted)}, wantErr: true, }, { name: "OneGroup", inputFile: "one-group.sif", opts: []SignerOpt{ OptSignWithEntity(e), OptSignWithTime(fixedTime), }, }, { name: "TwoGroups", inputFile: "two-groups.sif", opts: []SignerOpt{ OptSignWithEntity(e), OptSignWithTime(fixedTime), }, }, { name: "OptSignGroup1", inputFile: "two-groups.sif", opts: []SignerOpt{ OptSignWithEntity(e), OptSignWithTime(fixedTime), OptSignGroup(1), }, }, { name: "OptSignGroup2", inputFile: "two-groups.sif", opts: []SignerOpt{ OptSignWithEntity(e), OptSignWithTime(fixedTime), OptSignGroup(2), }, }, { name: "OptSignObject1", inputFile: "two-groups.sif", opts: []SignerOpt{ OptSignWithEntity(e), OptSignWithTime(fixedTime), OptSignObjects(1), }, }, { name: "OptSignObject2", inputFile: "two-groups.sif", opts: []SignerOpt{ OptSignWithEntity(e), OptSignWithTime(fixedTime), OptSignObjects(2), }, }, { name: "OptSignObject3", inputFile: "two-groups.sif", opts: []SignerOpt{ OptSignWithEntity(e), OptSignWithTime(fixedTime), OptSignObjects(3), }, }, { name: "OptSignObjects", inputFile: "two-groups.sif", opts: []SignerOpt{ OptSignWithEntity(e), OptSignWithTime(fixedTime), OptSignObjects(1, 2, 3), }, }, { name: "OptSignDeterministic", inputFile: "one-group.sif", opts: []SignerOpt{ OptSignWithEntity(e), OptSignWithTime(fixedTime), OptSignDeterministic(), }, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { b, err := os.ReadFile(filepath.Join(corpus, tt.inputFile)) if err != nil { t.Fatal(err) } buf := sif.NewBuffer(b) f, err := sif.LoadContainer(buf) if err != nil { t.Fatal(err) } t.Cleanup(func() { if err := f.UnloadContainer(); err != nil { t.Error(err) } }) s, err := NewSigner(f, tt.opts...) if err != nil { t.Fatal(err) } if err := s.Sign(); (err != nil) != tt.wantErr { t.Fatalf("got error %v, wantErr %v", err, tt.wantErr) } g := goldie.New(t, goldie.WithTestNameForDir(true)) g.Assert(t, tt.name, buf.Bytes()) }) } } sif-2.8.3/pkg/integrity/testdata/000077500000000000000000000000001432675271000167245ustar00rootroot00000000000000sif-2.8.3/pkg/integrity/testdata/TestDigest_MarshalJSON/000077500000000000000000000000001432675271000231445ustar00rootroot00000000000000sif-2.8.3/pkg/integrity/testdata/TestDigest_MarshalJSON/SHA224.golden000066400000000000000000000001021432675271000251720ustar00rootroot00000000000000"sha224:95041dd60ab08c0bf5636d50be85fe9790300f39eb84602858a9b430" sif-2.8.3/pkg/integrity/testdata/TestDigest_MarshalJSON/SHA256.golden000066400000000000000000000001121432675271000252000ustar00rootroot00000000000000"sha256:a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447" sif-2.8.3/pkg/integrity/testdata/TestDigest_MarshalJSON/SHA384.golden000066400000000000000000000001521432675271000252060ustar00rootroot00000000000000"sha384:6b3b69ff0a404f28d75e98a066d3fc64fffd9940870cc68bece28545b9a75086b343d7a1366838083e4b8f3ca6fd3c80" sif-2.8.3/pkg/integrity/testdata/TestDigest_MarshalJSON/SHA512.golden000066400000000000000000000002121432675271000251740ustar00rootroot00000000000000"sha512:db3974a97f2407b7cae1ae637c0030687a11913274d578492558e39c16c017de84eacdc8c62fe34ee4e12b4b1428817f09b6a2760c3f8a664ceae94d2434a593" sif-2.8.3/pkg/integrity/testdata/TestDigest_MarshalJSON/SHA512_224.golden000066400000000000000000000001061432675271000255650ustar00rootroot00000000000000"sha512_224:06001bf08dfb17d2b54925116823be230e98b5c6c278303bc4909a8c" sif-2.8.3/pkg/integrity/testdata/TestDigest_MarshalJSON/SHA512_256.golden000066400000000000000000000001161432675271000255730ustar00rootroot00000000000000"sha512_256:3d37fe58435e0d87323dee4a2c1b339ef954de63716ee79f5747f94d974f913f" sif-2.8.3/pkg/integrity/testdata/TestGetHeaderMetadata/000077500000000000000000000000001432675271000230555ustar00rootroot00000000000000sif-2.8.3/pkg/integrity/testdata/TestGetHeaderMetadata/SHA224.golden000066400000000000000000000001151432675271000251070ustar00rootroot00000000000000{"digest":"sha224:c9a483d5cf14145c7bc90d60e0e80ef0e92453611035b1bc000e5ce2"} sif-2.8.3/pkg/integrity/testdata/TestGetHeaderMetadata/SHA256.golden000066400000000000000000000001251432675271000251150ustar00rootroot00000000000000{"digest":"sha256:a18d8228cd6f87e7fc3559bdf6bf2e32b3663c1b1107ffddcad06f6c2a15598e"} sif-2.8.3/pkg/integrity/testdata/TestGetHeaderMetadata/SHA384.golden000066400000000000000000000001651432675271000251230ustar00rootroot00000000000000{"digest":"sha384:10778862b725bec7b752ae1d0f13dbefbe9082afcfa690138216c0618aa149b39d157e76a925686317b4634afa0c7743"} sif-2.8.3/pkg/integrity/testdata/TestGetHeaderMetadata/SHA512.golden000066400000000000000000000002251432675271000251110ustar00rootroot00000000000000{"digest":"sha512:6a6d35be79ba7885a2785b5384c242c2b782ec686cfe1473af16db743f6164a1637e2d562bba867a5885ef0faf5a64f75970008b96bbe545d4e875e83e922bee"} sif-2.8.3/pkg/integrity/testdata/TestGetHeaderMetadata/SHA512_224.golden000066400000000000000000000001211432675271000254730ustar00rootroot00000000000000{"digest":"sha512_224:d5f9767e096056fcf381b801e2b0b80b33acdc09b7a7e3a1c504231e"} sif-2.8.3/pkg/integrity/testdata/TestGetHeaderMetadata/SHA512_256.golden000066400000000000000000000001311432675271000255010ustar00rootroot00000000000000{"digest":"sha512_256:eb199aeab4047ca6430890372769681045c20b1a0a4a78b595ab62dbdfc9285f"} sif-2.8.3/pkg/integrity/testdata/TestGetImageMetadata/000077500000000000000000000000001432675271000227075ustar00rootroot00000000000000sif-2.8.3/pkg/integrity/testdata/TestGetImageMetadata/Object1.golden000066400000000000000000000004771432675271000254000ustar00rootroot00000000000000{"version":1,"header":{"digest":"sha256:635fa0a14a8ef0c0351ed3e985799ed1d4f75ce973dea3cc76c99710795cc3f1"},"objects":[{"relativeId":0,"descriptorDigest":"sha256:3634ad01db0dd5482ecf685267b53d6201690438ca27c3d7ea91c971a1f41f92","objectDigest":"sha256:004dfc8da678c309de28b5386a1e9efd57f536b150c40d29b31506aa0fb17ec2"}]} sif-2.8.3/pkg/integrity/testdata/TestGetImageMetadata/Object2.golden000066400000000000000000000004771432675271000254010ustar00rootroot00000000000000{"version":1,"header":{"digest":"sha256:635fa0a14a8ef0c0351ed3e985799ed1d4f75ce973dea3cc76c99710795cc3f1"},"objects":[{"relativeId":1,"descriptorDigest":"sha256:04b5f87c9692a54f80d10fb6af00c779763aeca29d610348854bd97cd8bf66fd","objectDigest":"sha256:9f9c4e5e131934969b4ac8f495691c70b8c6c8e3f489c2c9ab5f1af82bce0604"}]} sif-2.8.3/pkg/integrity/testdata/TestGetImageMetadata/SHA224.golden000066400000000000000000000007361432675271000247520ustar00rootroot00000000000000{"version":1,"header":{"digest":"sha224:88ecbdbaa9bf8410c9362213ddae5e6771fd2525da3eb390300fb666"},"objects":[{"relativeId":0,"descriptorDigest":"sha224:8ac2ffbf24282ce5f49fc591eee1e0a879b0ae2a9fa813b897b94113","objectDigest":"sha224:071bce5faa03c2016d3e1e086ccb60b6ea3cabc493c9aa1013594efd"},{"relativeId":1,"descriptorDigest":"sha224:15f2307c74c24b5aff01556df7642009a968ad717514da242821b1cb","objectDigest":"sha224:1f26e23245e830c5aa90735377d43535b0a080d03981ea58e4b94372"}]} sif-2.8.3/pkg/integrity/testdata/TestGetImageMetadata/SHA256.golden000066400000000000000000000010061432675271000247460ustar00rootroot00000000000000{"version":1,"header":{"digest":"sha256:635fa0a14a8ef0c0351ed3e985799ed1d4f75ce973dea3cc76c99710795cc3f1"},"objects":[{"relativeId":0,"descriptorDigest":"sha256:3634ad01db0dd5482ecf685267b53d6201690438ca27c3d7ea91c971a1f41f92","objectDigest":"sha256:004dfc8da678c309de28b5386a1e9efd57f536b150c40d29b31506aa0fb17ec2"},{"relativeId":1,"descriptorDigest":"sha256:04b5f87c9692a54f80d10fb6af00c779763aeca29d610348854bd97cd8bf66fd","objectDigest":"sha256:9f9c4e5e131934969b4ac8f495691c70b8c6c8e3f489c2c9ab5f1af82bce0604"}]} sif-2.8.3/pkg/integrity/testdata/TestGetImageMetadata/SHA384.golden000066400000000000000000000012461432675271000247560ustar00rootroot00000000000000{"version":1,"header":{"digest":"sha384:92bdcff06f2cde591d8af1f0ab80687e5eccf1dfe8fe7a8a8ef4b80d28bd2c7a77bcc4abfbcfdb3fc84ec7992ed54334"},"objects":[{"relativeId":0,"descriptorDigest":"sha384:ed532e8496b916182a4185a3d12f3a6d4c59d204965ef1a732013d1c05050291ec29b48b06ba100a948468868023fb82","objectDigest":"sha384:f8722c6694c4997334525090678b2148f6263502c3eb144a44e8be0d2bfd039f4067a3f8152f94ab3af7c63acfe78ce6"},{"relativeId":1,"descriptorDigest":"sha384:de7f8e386d3c1679711711f31812d12a913b0f5202503e46e9e1c5fd36c49908c5e288a3c08e6b72285dba177596668d","objectDigest":"sha384:da6cf2d305a04a53623df94d5e74bc22aee7961a5a62b289f99db693e5a980ee276526f254f6504f9e66621ce821b977"}]} sif-2.8.3/pkg/integrity/testdata/TestGetImageMetadata/SHA512.golden000066400000000000000000000015061432675271000247460ustar00rootroot00000000000000{"version":1,"header":{"digest":"sha512:503a1101d5a7f66e440f157576597c5ab9a3517b025259da985402f3b7de7c90c6034b8b5d3da992a9cae5b47dd355fce3f9932e92bc47422134cc5b7347e1e7"},"objects":[{"relativeId":0,"descriptorDigest":"sha512:4ccbff33c9be45cf2ba25412de4a87bd623fd48bf00598b756ea5a12a4eddb83aa93176a91a875b595750837b3ba77b5dfbbbd90e2126ca4d4828763db6dc591","objectDigest":"sha512:808e1f67ffbdbdae30946529b920a1ad6d49c0c50423bc0c9d41ece566e291b6c3e6b6839f3095fbab6bc15a5b971b07d4b8b2f22b982ce3c2b8fd05eef7e1b3"},{"relativeId":1,"descriptorDigest":"sha512:39bc9e8ecf3192e0656f0b4de529b7e6b1b0d892a6b19fd6f619bf476558bc15255ead250e440c3fe69bbf061b49c0ca0d7de18616c0b3172b2ca2d0753ef331","objectDigest":"sha512:c948c053d5494e944dc251ba774882c58c6b18dae241caa84123c779170d1ed0a22ced3af76d67c7090b668fa7d80531e5b9e3f4677b1b5aae64e8d0d24999bf"}]} sif-2.8.3/pkg/integrity/testdata/TestGetObjectMetadata/000077500000000000000000000000001432675271000230735ustar00rootroot00000000000000sif-2.8.3/pkg/integrity/testdata/TestGetObjectMetadata/RelativeID.golden000066400000000000000000000003071432675271000262550ustar00rootroot00000000000000{"relativeId":1,"descriptorDigest":"sha256:a1e6ca1d0cce1fbd71b186ac7a5c5a805c833ecc419a78d017558e79c0862790","objectDigest":"sha256:8b7df143d91c716ecfa5fc1730022f6b421b05cedee8fd52b1fc65a96030ad52"} sif-2.8.3/pkg/integrity/testdata/TestGetObjectMetadata/SHA224.golden000066400000000000000000000002671432675271000251350ustar00rootroot00000000000000{"relativeId":0,"descriptorDigest":"sha224:c46f4657fbd892b26d9880c3ee8078b2af2f9e7647910b233f30a75f","objectDigest":"sha224:0570b8b9a94b1429b3ba5f4b744a174633fcbfedf6f022cf33da3e99"} sif-2.8.3/pkg/integrity/testdata/TestGetObjectMetadata/SHA256.golden000066400000000000000000000003071432675271000251350ustar00rootroot00000000000000{"relativeId":0,"descriptorDigest":"sha256:46712c8c573850880fceb4302c729b2bd0cfb402d3422faa5ca9026b7ab89b9b","objectDigest":"sha256:8b7df143d91c716ecfa5fc1730022f6b421b05cedee8fd52b1fc65a96030ad52"} sif-2.8.3/pkg/integrity/testdata/TestGetObjectMetadata/SHA384.golden000066400000000000000000000004071432675271000251400ustar00rootroot00000000000000{"relativeId":0,"descriptorDigest":"sha384:426b1caa794407dd42407ace724ac9372db4ad9e7fad70539ecf078481b0f68133dc1ccad812eef70d80968ec6c7b05c","objectDigest":"sha384:0ca752991753e9fcc29ebc053c86bffb8b266b69ecf82e01fbdf380d65a1e38cbdf72f623721fbe01eb8909cb7e0bb59"} sif-2.8.3/pkg/integrity/testdata/TestGetObjectMetadata/SHA512.golden000066400000000000000000000005071432675271000251320ustar00rootroot00000000000000{"relativeId":0,"descriptorDigest":"sha512:20f27d88e15dfd9469dfbc55aa5bdb9f48293a6cba38aa920ab511cd4d12d94d294dba3c75f50ad999d226f24c1624f65b7eaab1dd2fe4da6c2c468e3b72a984","objectDigest":"sha512:39ca2b1f97c7d1d223dcb2b22cbe20c36f920aeefd201d0bf68ffc08db6d9ac608a0a202fb536d944c9d1f50cf9bd61b5bc84217212f0727a8db8a01c2fa54b7"} sif-2.8.3/pkg/integrity/testdata/TestGetObjectMetadata/SHA512_224.golden000066400000000000000000000002771432675271000255250ustar00rootroot00000000000000{"relativeId":0,"descriptorDigest":"sha512_224:ba5b52f4337756f9efb0c7d35f16e0365ba5845b0dd9df5e9edfce3a","objectDigest":"sha512_224:b1d15ae18bb05265b44e9e0137f08078f53f5b239a78c49c2cfc2c9c"} sif-2.8.3/pkg/integrity/testdata/TestGetObjectMetadata/SHA512_256.golden000066400000000000000000000003171432675271000255250ustar00rootroot00000000000000{"relativeId":0,"descriptorDigest":"sha512_256:aef151cf86aaab28a4e086c9e1f9d19c8f85e4eb794336d909a6844ce7fb52ef","objectDigest":"sha512_256:9a801762c512490303535d35c221e2dc1d24f5094d038041dc4303ba7ac04f0e"} sif-2.8.3/pkg/integrity/testdata/TestGroupSigner_Sign/000077500000000000000000000000001432675271000230105ustar00rootroot00000000000000sif-2.8.3/pkg/integrity/testdata/TestGroupSigner_Sign/Group1.golden000066400000000000000000001007161432675271000253640ustar00rootroot00000000000000SIF_MAGIC0100 n n/0m}@} n n\ XK '-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 {"version":1,"header":{"digest":"sha256:635fa0a14a8ef0c0351ed3e985799ed1d4f75ce973dea3cc76c99710795cc3f1"},"objects":[{"relativeId":0,"descriptorDigest":"sha256:3634ad01db0dd5482ecf685267b53d6201690438ca27c3d7ea91c971a1f41f92","objectDigest":"sha256:004dfc8da678c309de28b5386a1e9efd57f536b150c40d29b31506aa0fb17ec2"},{"relativeId":1,"descriptorDigest":"sha256:04b5f87c9692a54f80d10fb6af00c779763aeca29d610348854bd97cd8bf66fd","objectDigest":"sha256:9f9c4e5e131934969b4ac8f495691c70b8c6c8e3f489c2c9ab5f1af82bce0604"}]} -----BEGIN PGP SIGNATURE----- wsBzBAEBCAAnBQJZr0CRCZCiDCfuf/e6hBYhBBIEXIwLEATQWN5L7aIMJ+5/97qE AACSlwgAoCApIsp01PrpgncNBYs9HKmB5/erkD47ZL4B3Kw5+hWoB45eGQIIs/gZ vJlG8ph7yOzm0xRE2DlogRVbDA+b8bzcdeGylpTfDDuhVgBrMpJ1CsvxWpwJEyCI rZBSgsRrp/qnR7WKtRdYx9xI9kUHT5r/XKm1or1BF5DYzx6JusNAcsMyUNjYd+Vb yQVyQ/wM4P509DgxK7btJUhGwVS5cV0J69TTzQo0zMEDxUtvkKwnMZiJdxPji+3h Uamm2ZxoOHoiEhGc1odPbhxWvxbXrlpyrKVUqyX7XFtPNbXMV3h5B0RvgThDMFcL JYY/FrUAgkPqCk0LWnfir0xPSGEY0Q== =zbzg -----END PGP SIGNATURE-----sif-2.8.3/pkg/integrity/testdata/TestGroupSigner_Sign/Group2.golden000066400000000000000000001004071432675271000253620ustar00rootroot00000000000000SIF_MAGIC0100 n n/0m}W@}WW n n\ XK '-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 {"version":1,"header":{"digest":"sha256:635fa0a14a8ef0c0351ed3e985799ed1d4f75ce973dea3cc76c99710795cc3f1"},"objects":[{"relativeId":0,"descriptorDigest":"sha256:b356c9810f880c619f95c34909387de498895fc2054ef590ebaeab5c9b50c995","objectDigest":"sha256:d2dd40e7ff6b6753d84c1a85061189e61d4de9688d5531537ff96ff09b1f12dc"}]} -----BEGIN PGP SIGNATURE----- wsBzBAEBCAAnBQJZr0CRCZCiDCfuf/e6hBYhBBIEXIwLEATQWN5L7aIMJ+5/97qE AACJxQf8CtS1b8BP6+qNM6vjGyb3aUJf8wokzZ2yHkIvjdoSExm0EbeQdhQtuHH9 28enU5OMzCpBHMHG0IzvJddc0IV/J2GiT6Itw1iVNKJyOlF8MztfR9TmtF0WBx7w WUQXQJQVed6JJlAhzzYrxFx5aPIQZh0ESF90yhiE2akcoOrkUvkWIdP84c7OB3eY iMmpHX1JayC8DAmrdduyFcwVReBN4P/ivL+hHx/5c8l5i9VR6tbUMnJbJ5u5uYZk mApkzqqVYvGulKjDa5w3BDpFhnjVJkfxZvzJ305+xPW7yg7oOt/ZEYmTMTxgH4nJ j2l5IVeWcBY5fxSZf9PF+9nGHCSRVA== =khH/ -----END PGP SIGNATURE-----sif-2.8.3/pkg/integrity/testdata/TestGroupSigner_Sign/Object1.golden000066400000000000000000001004071432675271000254730ustar00rootroot00000000000000SIF_MAGIC0100 n n/0m}W@}WW n n\ XK '-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 {"version":1,"header":{"digest":"sha256:635fa0a14a8ef0c0351ed3e985799ed1d4f75ce973dea3cc76c99710795cc3f1"},"objects":[{"relativeId":0,"descriptorDigest":"sha256:3634ad01db0dd5482ecf685267b53d6201690438ca27c3d7ea91c971a1f41f92","objectDigest":"sha256:004dfc8da678c309de28b5386a1e9efd57f536b150c40d29b31506aa0fb17ec2"}]} -----BEGIN PGP SIGNATURE----- wsBzBAEBCAAnBQJZr0CRCZCiDCfuf/e6hBYhBBIEXIwLEATQWN5L7aIMJ+5/97qE AAC5nAf+KR04E3a+svrNL8d/lFl53AThhefVouAFt8cw077pGO/dmrVKHE6toQYc EBofYwmyEketCZEcPFyX034GKKqmpp2F9sp2US7NGMZwInOs2LES7tK+w08IF7um XXzeu5s5/NFFBzHgpvLnixHCIoxKMo3ApR2tnPwETMaUCe07lAVzKVbrQP4Ck4Bd ZsKBt5qYzw8t5iT1iGXRJa9OKK1lJ9POkKCDqf/HnIr2D285rECJ6i1zpDdObrsq PRsXqQZykLJvXoiL56PIWg2LV1+KyNzRbwUcC54prx0mBCjj1JTkrlo0QM3oGKXr 1NdRlzLcYIBvgHgiTFesem6NLRb3WQ== =Yub4 -----END PGP SIGNATURE-----sif-2.8.3/pkg/integrity/testdata/TestGroupSigner_Sign/Object2.golden000066400000000000000000001004071432675271000254740ustar00rootroot00000000000000SIF_MAGIC0100 n n/0m}W@}WW n n\ XK '-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 {"version":1,"header":{"digest":"sha256:635fa0a14a8ef0c0351ed3e985799ed1d4f75ce973dea3cc76c99710795cc3f1"},"objects":[{"relativeId":1,"descriptorDigest":"sha256:04b5f87c9692a54f80d10fb6af00c779763aeca29d610348854bd97cd8bf66fd","objectDigest":"sha256:9f9c4e5e131934969b4ac8f495691c70b8c6c8e3f489c2c9ab5f1af82bce0604"}]} -----BEGIN PGP SIGNATURE----- wsBzBAEBCAAnBQJZr0CRCZCiDCfuf/e6hBYhBBIEXIwLEATQWN5L7aIMJ+5/97qE AAALQgf/eYOr82kNe7xffd8J1rkGdQForUw4SiKwjYbd2yH376JmvOfRanc1HxNi +76dAQDSF0mkOKSymknRGCHa/ccH44NFQMuPRlXY9dmjX00Mr5tiO6oblXEAoV4a fhdQd0bG76aiIbNVvMVcJB8uqJaaSqpoV8RhBbVGBjUhSaLbSRbCfD9+qHMokK+p i/5Yby/BxURc/bB6wV1Xh+QQBEt+9FQ6rCBPUag0WzrvMTyWfH7wBkXKOOI0Wm72 sIw+3l3s/Qq47qGQO4Ouuy3iB6At8Guyok8/cQIyRvRhjtzIokZXI/ZKOtOMxD8i 7lw4O7LcXLnnheSq3u7vQCJhwB40Rg== =gByO -----END PGP SIGNATURE-----sif-2.8.3/pkg/integrity/testdata/TestSigner_Sign/000077500000000000000000000000001432675271000217735ustar00rootroot00000000000000sif-2.8.3/pkg/integrity/testdata/TestSigner_Sign/EncryptedKey.golden000066400000000000000000001200001432675271000255640ustar00rootroot00000000000000SIF_MAGIC0101 n n.0m}P"@T n n01@ n n01hsqsgCb sHello from Sylabs! 'xcb]I`bFpy&8Vbf ^xc`F(WRQ`xc`( !sif-2.8.3/pkg/integrity/testdata/TestSigner_Sign/OneGroup.golden000066400000000000000000001220361432675271000247270ustar00rootroot00000000000000SIF_MAGIC0101 n@Y-0m}n&@T n n01@ n n01@@Y@Y\ XK 'hsqsgCb sHello from Sylabs! 'xcb]I`bFpy&8Vbf ^xc`F(WRQ`xc`( !-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 {"version":1,"header":{"digest":"sha256:635fa0a14a8ef0c0351ed3e985799ed1d4f75ce973dea3cc76c99710795cc3f1"},"objects":[{"relativeId":0,"descriptorDigest":"sha256:3634ad01db0dd5482ecf685267b53d6201690438ca27c3d7ea91c971a1f41f92","objectDigest":"sha256:004dfc8da678c309de28b5386a1e9efd57f536b150c40d29b31506aa0fb17ec2"},{"relativeId":1,"descriptorDigest":"sha256:04b5f87c9692a54f80d10fb6af00c779763aeca29d610348854bd97cd8bf66fd","objectDigest":"sha256:9f9c4e5e131934969b4ac8f495691c70b8c6c8e3f489c2c9ab5f1af82bce0604"}]} -----BEGIN PGP SIGNATURE----- wsBzBAEBCAAnBQJZr0CRCZCiDCfuf/e6hBYhBBIEXIwLEATQWN5L7aIMJ+5/97qE AACSlwgAoCApIsp01PrpgncNBYs9HKmB5/erkD47ZL4B3Kw5+hWoB45eGQIIs/gZ vJlG8ph7yOzm0xRE2DlogRVbDA+b8bzcdeGylpTfDDuhVgBrMpJ1CsvxWpwJEyCI rZBSgsRrp/qnR7WKtRdYx9xI9kUHT5r/XKm1or1BF5DYzx6JusNAcsMyUNjYd+Vb yQVyQ/wM4P509DgxK7btJUhGwVS5cV0J69TTzQo0zMEDxUtvkKwnMZiJdxPji+3h Uamm2ZxoOHoiEhGc1odPbhxWvxbXrlpyrKVUqyX7XFtPNbXMV3h5B0RvgThDMFcL JYY/FrUAgkPqCk0LWnfir0xPSGEY0Q== =zbzg -----END PGP SIGNATURE-----sif-2.8.3/pkg/integrity/testdata/TestSigner_Sign/OptSignDeterministic.golden000066400000000000000000001220361432675271000273000ustar00rootroot00000000000000SIF_MAGIC0101 n n-0m}n&@T n n01@ n n01@ n n\ XK 'hsqsgCb sHello from Sylabs! 'xcb]I`bFpy&8Vbf ^xc`F(WRQ`xc`( !-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 {"version":1,"header":{"digest":"sha256:635fa0a14a8ef0c0351ed3e985799ed1d4f75ce973dea3cc76c99710795cc3f1"},"objects":[{"relativeId":0,"descriptorDigest":"sha256:3634ad01db0dd5482ecf685267b53d6201690438ca27c3d7ea91c971a1f41f92","objectDigest":"sha256:004dfc8da678c309de28b5386a1e9efd57f536b150c40d29b31506aa0fb17ec2"},{"relativeId":1,"descriptorDigest":"sha256:04b5f87c9692a54f80d10fb6af00c779763aeca29d610348854bd97cd8bf66fd","objectDigest":"sha256:9f9c4e5e131934969b4ac8f495691c70b8c6c8e3f489c2c9ab5f1af82bce0604"}]} -----BEGIN PGP SIGNATURE----- wsBzBAEBCAAnBQJZr0CRCZCiDCfuf/e6hBYhBBIEXIwLEATQWN5L7aIMJ+5/97qE AACSlwgAoCApIsp01PrpgncNBYs9HKmB5/erkD47ZL4B3Kw5+hWoB45eGQIIs/gZ vJlG8ph7yOzm0xRE2DlogRVbDA+b8bzcdeGylpTfDDuhVgBrMpJ1CsvxWpwJEyCI rZBSgsRrp/qnR7WKtRdYx9xI9kUHT5r/XKm1or1BF5DYzx6JusNAcsMyUNjYd+Vb yQVyQ/wM4P509DgxK7btJUhGwVS5cV0J69TTzQo0zMEDxUtvkKwnMZiJdxPji+3h Uamm2ZxoOHoiEhGc1odPbhxWvxbXrlpyrKVUqyX7XFtPNbXMV3h5B0RvgThDMFcL JYY/FrUAgkPqCk0LWnfir0xPSGEY0Q== =zbzg -----END PGP SIGNATURE-----sif-2.8.3/pkg/integrity/testdata/TestSigner_Sign/OptSignGroup1.golden000066400000000000000000011220361432675271000256530ustar00rootroot00000000000000SIF_MAGIC0101 n@Y,0m}n&@T n n01@ n n01@ n n02@@Y@Y\ XK 'hsqsgCb sHello from Sylabs! 'xcb]I`bFpy&8Vbf ^xc`F(WRQ`xc`( ! @8 aDbsDbSнDb 8jE5/home/adam/src/sif/loopfs%=Kt 0|j нDb@8? нDbнDbнDbA:Db8Db8Db@нDbнDbнDb AнDb8Db8Db8DbmDb1DbDb dV . ..  lost+found  hello.txt ...Hello from Sylabs! -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 {"version":1,"header":{"digest":"sha256:635fa0a14a8ef0c0351ed3e985799ed1d4f75ce973dea3cc76c99710795cc3f1"},"objects":[{"relativeId":0,"descriptorDigest":"sha256:3634ad01db0dd5482ecf685267b53d6201690438ca27c3d7ea91c971a1f41f92","objectDigest":"sha256:004dfc8da678c309de28b5386a1e9efd57f536b150c40d29b31506aa0fb17ec2"},{"relativeId":1,"descriptorDigest":"sha256:04b5f87c9692a54f80d10fb6af00c779763aeca29d610348854bd97cd8bf66fd","objectDigest":"sha256:9f9c4e5e131934969b4ac8f495691c70b8c6c8e3f489c2c9ab5f1af82bce0604"}]} -----BEGIN PGP SIGNATURE----- wsBzBAEBCAAnBQJZr0CRCZCiDCfuf/e6hBYhBBIEXIwLEATQWN5L7aIMJ+5/97qE AACSlwgAoCApIsp01PrpgncNBYs9HKmB5/erkD47ZL4B3Kw5+hWoB45eGQIIs/gZ vJlG8ph7yOzm0xRE2DlogRVbDA+b8bzcdeGylpTfDDuhVgBrMpJ1CsvxWpwJEyCI rZBSgsRrp/qnR7WKtRdYx9xI9kUHT5r/XKm1or1BF5DYzx6JusNAcsMyUNjYd+Vb yQVyQ/wM4P509DgxK7btJUhGwVS5cV0J69TTzQo0zMEDxUtvkKwnMZiJdxPji+3h Uamm2ZxoOHoiEhGc1odPbhxWvxbXrlpyrKVUqyX7XFtPNbXMV3h5B0RvgThDMFcL JYY/FrUAgkPqCk0LWnfir0xPSGEY0Q== =zbzg -----END PGP SIGNATURE-----sif-2.8.3/pkg/integrity/testdata/TestSigner_Sign/OptSignGroup2.golden000066400000000000000000011215271432675271000256600ustar00rootroot00000000000000SIF_MAGIC0101 n@Y,0m}%@T n n01@ n n01@ n n02@WW@Y@Y\ XK 'hsqsgCb sHello from Sylabs! 'xcb]I`bFpy&8Vbf ^xc`F(WRQ`xc`( ! @8 aDbsDbSнDb 8jE5/home/adam/src/sif/loopfs%=Kt 0|j нDb@8? нDbнDbнDbA:Db8Db8Db@нDbнDbнDb AнDb8Db8Db8DbmDb1DbDb dV . ..  lost+found  hello.txt ...Hello from Sylabs! -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 {"version":1,"header":{"digest":"sha256:635fa0a14a8ef0c0351ed3e985799ed1d4f75ce973dea3cc76c99710795cc3f1"},"objects":[{"relativeId":0,"descriptorDigest":"sha256:b356c9810f880c619f95c34909387de498895fc2054ef590ebaeab5c9b50c995","objectDigest":"sha256:d2dd40e7ff6b6753d84c1a85061189e61d4de9688d5531537ff96ff09b1f12dc"}]} -----BEGIN PGP SIGNATURE----- wsBzBAEBCAAnBQJZr0CRCZCiDCfuf/e6hBYhBBIEXIwLEATQWN5L7aIMJ+5/97qE AACJxQf8CtS1b8BP6+qNM6vjGyb3aUJf8wokzZ2yHkIvjdoSExm0EbeQdhQtuHH9 28enU5OMzCpBHMHG0IzvJddc0IV/J2GiT6Itw1iVNKJyOlF8MztfR9TmtF0WBx7w WUQXQJQVed6JJlAhzzYrxFx5aPIQZh0ESF90yhiE2akcoOrkUvkWIdP84c7OB3eY iMmpHX1JayC8DAmrdduyFcwVReBN4P/ivL+hHx/5c8l5i9VR6tbUMnJbJ5u5uYZk mApkzqqVYvGulKjDa5w3BDpFhnjVJkfxZvzJ305+xPW7yg7oOt/ZEYmTMTxgH4nJ j2l5IVeWcBY5fxSZf9PF+9nGHCSRVA== =khH/ -----END PGP SIGNATURE-----sif-2.8.3/pkg/integrity/testdata/TestSigner_Sign/OptSignObject1.golden000066400000000000000000011215271432675271000257710ustar00rootroot00000000000000SIF_MAGIC0101 n@Y,0m}%@T n n01@ n n01@ n n02@WW@Y@Y\ XK 'hsqsgCb sHello from Sylabs! 'xcb]I`bFpy&8Vbf ^xc`F(WRQ`xc`( ! @8 aDbsDbSнDb 8jE5/home/adam/src/sif/loopfs%=Kt 0|j нDb@8? нDbнDbнDbA:Db8Db8Db@нDbнDbнDb AнDb8Db8Db8DbmDb1DbDb dV . ..  lost+found  hello.txt ...Hello from Sylabs! -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 {"version":1,"header":{"digest":"sha256:635fa0a14a8ef0c0351ed3e985799ed1d4f75ce973dea3cc76c99710795cc3f1"},"objects":[{"relativeId":0,"descriptorDigest":"sha256:3634ad01db0dd5482ecf685267b53d6201690438ca27c3d7ea91c971a1f41f92","objectDigest":"sha256:004dfc8da678c309de28b5386a1e9efd57f536b150c40d29b31506aa0fb17ec2"}]} -----BEGIN PGP SIGNATURE----- wsBzBAEBCAAnBQJZr0CRCZCiDCfuf/e6hBYhBBIEXIwLEATQWN5L7aIMJ+5/97qE AAC5nAf+KR04E3a+svrNL8d/lFl53AThhefVouAFt8cw077pGO/dmrVKHE6toQYc EBofYwmyEketCZEcPFyX034GKKqmpp2F9sp2US7NGMZwInOs2LES7tK+w08IF7um XXzeu5s5/NFFBzHgpvLnixHCIoxKMo3ApR2tnPwETMaUCe07lAVzKVbrQP4Ck4Bd ZsKBt5qYzw8t5iT1iGXRJa9OKK1lJ9POkKCDqf/HnIr2D285rECJ6i1zpDdObrsq PRsXqQZykLJvXoiL56PIWg2LV1+KyNzRbwUcC54prx0mBCjj1JTkrlo0QM3oGKXr 1NdRlzLcYIBvgHgiTFesem6NLRb3WQ== =Yub4 -----END PGP SIGNATURE-----sif-2.8.3/pkg/integrity/testdata/TestSigner_Sign/OptSignObject2.golden000066400000000000000000011215271432675271000257720ustar00rootroot00000000000000SIF_MAGIC0101 n@Y,0m}%@T n n01@ n n01@ n n02@WW@Y@Y\ XK 'hsqsgCb sHello from Sylabs! 'xcb]I`bFpy&8Vbf ^xc`F(WRQ`xc`( ! @8 aDbsDbSнDb 8jE5/home/adam/src/sif/loopfs%=Kt 0|j нDb@8? нDbнDbнDbA:Db8Db8Db@нDbнDbнDb AнDb8Db8Db8DbmDb1DbDb dV . ..  lost+found  hello.txt ...Hello from Sylabs! -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 {"version":1,"header":{"digest":"sha256:635fa0a14a8ef0c0351ed3e985799ed1d4f75ce973dea3cc76c99710795cc3f1"},"objects":[{"relativeId":1,"descriptorDigest":"sha256:04b5f87c9692a54f80d10fb6af00c779763aeca29d610348854bd97cd8bf66fd","objectDigest":"sha256:9f9c4e5e131934969b4ac8f495691c70b8c6c8e3f489c2c9ab5f1af82bce0604"}]} -----BEGIN PGP SIGNATURE----- wsBzBAEBCAAnBQJZr0CRCZCiDCfuf/e6hBYhBBIEXIwLEATQWN5L7aIMJ+5/97qE AAALQgf/eYOr82kNe7xffd8J1rkGdQForUw4SiKwjYbd2yH376JmvOfRanc1HxNi +76dAQDSF0mkOKSymknRGCHa/ccH44NFQMuPRlXY9dmjX00Mr5tiO6oblXEAoV4a fhdQd0bG76aiIbNVvMVcJB8uqJaaSqpoV8RhBbVGBjUhSaLbSRbCfD9+qHMokK+p i/5Yby/BxURc/bB6wV1Xh+QQBEt+9FQ6rCBPUag0WzrvMTyWfH7wBkXKOOI0Wm72 sIw+3l3s/Qq47qGQO4Ouuy3iB6At8Guyok8/cQIyRvRhjtzIokZXI/ZKOtOMxD8i 7lw4O7LcXLnnheSq3u7vQCJhwB40Rg== =gByO -----END PGP SIGNATURE-----sif-2.8.3/pkg/integrity/testdata/TestSigner_Sign/OptSignObject3.golden000066400000000000000000011215271432675271000257730ustar00rootroot00000000000000SIF_MAGIC0101 n@Y,0m}%@T n n01@ n n01@ n n02@WW@Y@Y\ XK 'hsqsgCb sHello from Sylabs! 'xcb]I`bFpy&8Vbf ^xc`F(WRQ`xc`( ! @8 aDbsDbSнDb 8jE5/home/adam/src/sif/loopfs%=Kt 0|j нDb@8? нDbнDbнDbA:Db8Db8Db@нDbнDbнDb AнDb8Db8Db8DbmDb1DbDb dV . ..  lost+found  hello.txt ...Hello from Sylabs! -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 {"version":1,"header":{"digest":"sha256:635fa0a14a8ef0c0351ed3e985799ed1d4f75ce973dea3cc76c99710795cc3f1"},"objects":[{"relativeId":0,"descriptorDigest":"sha256:b356c9810f880c619f95c34909387de498895fc2054ef590ebaeab5c9b50c995","objectDigest":"sha256:d2dd40e7ff6b6753d84c1a85061189e61d4de9688d5531537ff96ff09b1f12dc"}]} -----BEGIN PGP SIGNATURE----- wsBzBAEBCAAnBQJZr0CRCZCiDCfuf/e6hBYhBBIEXIwLEATQWN5L7aIMJ+5/97qE AACJxQf8CtS1b8BP6+qNM6vjGyb3aUJf8wokzZ2yHkIvjdoSExm0EbeQdhQtuHH9 28enU5OMzCpBHMHG0IzvJddc0IV/J2GiT6Itw1iVNKJyOlF8MztfR9TmtF0WBx7w WUQXQJQVed6JJlAhzzYrxFx5aPIQZh0ESF90yhiE2akcoOrkUvkWIdP84c7OB3eY iMmpHX1JayC8DAmrdduyFcwVReBN4P/ivL+hHx/5c8l5i9VR6tbUMnJbJ5u5uYZk mApkzqqVYvGulKjDa5w3BDpFhnjVJkfxZvzJ305+xPW7yg7oOt/ZEYmTMTxgH4nJ j2l5IVeWcBY5fxSZf9PF+9nGHCSRVA== =khH/ -----END PGP SIGNATURE-----sif-2.8.3/pkg/integrity/testdata/TestSigner_Sign/OptSignObjects.golden000066400000000000000000011235651432675271000260770ustar00rootroot00000000000000SIF_MAGIC0101 n@Y+0m})@T n n01@ n n01@ n n02@@Y@Y\ XK '@WW@Y@Y\ XK 'hsqsgCb sHello from Sylabs! 'xcb]I`bFpy&8Vbf ^xc`F(WRQ`xc`( ! @8 aDbsDbSнDb 8jE5/home/adam/src/sif/loopfs%=Kt 0|j нDb@8? нDbнDbнDbA:Db8Db8Db@нDbнDbнDb AнDb8Db8Db8DbmDb1DbDb dV . ..  lost+found  hello.txt ...Hello from Sylabs! -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 {"version":1,"header":{"digest":"sha256:635fa0a14a8ef0c0351ed3e985799ed1d4f75ce973dea3cc76c99710795cc3f1"},"objects":[{"relativeId":0,"descriptorDigest":"sha256:3634ad01db0dd5482ecf685267b53d6201690438ca27c3d7ea91c971a1f41f92","objectDigest":"sha256:004dfc8da678c309de28b5386a1e9efd57f536b150c40d29b31506aa0fb17ec2"},{"relativeId":1,"descriptorDigest":"sha256:04b5f87c9692a54f80d10fb6af00c779763aeca29d610348854bd97cd8bf66fd","objectDigest":"sha256:9f9c4e5e131934969b4ac8f495691c70b8c6c8e3f489c2c9ab5f1af82bce0604"}]} -----BEGIN PGP SIGNATURE----- wsBzBAEBCAAnBQJZr0CRCZCiDCfuf/e6hBYhBBIEXIwLEATQWN5L7aIMJ+5/97qE AACSlwgAoCApIsp01PrpgncNBYs9HKmB5/erkD47ZL4B3Kw5+hWoB45eGQIIs/gZ vJlG8ph7yOzm0xRE2DlogRVbDA+b8bzcdeGylpTfDDuhVgBrMpJ1CsvxWpwJEyCI rZBSgsRrp/qnR7WKtRdYx9xI9kUHT5r/XKm1or1BF5DYzx6JusNAcsMyUNjYd+Vb yQVyQ/wM4P509DgxK7btJUhGwVS5cV0J69TTzQo0zMEDxUtvkKwnMZiJdxPji+3h Uamm2ZxoOHoiEhGc1odPbhxWvxbXrlpyrKVUqyX7XFtPNbXMV3h5B0RvgThDMFcL JYY/FrUAgkPqCk0LWnfir0xPSGEY0Q== =zbzg -----END PGP SIGNATURE----------BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 {"version":1,"header":{"digest":"sha256:635fa0a14a8ef0c0351ed3e985799ed1d4f75ce973dea3cc76c99710795cc3f1"},"objects":[{"relativeId":0,"descriptorDigest":"sha256:b356c9810f880c619f95c34909387de498895fc2054ef590ebaeab5c9b50c995","objectDigest":"sha256:d2dd40e7ff6b6753d84c1a85061189e61d4de9688d5531537ff96ff09b1f12dc"}]} -----BEGIN PGP SIGNATURE----- wsBzBAEBCAAnBQJZr0CRCZCiDCfuf/e6hBYhBBIEXIwLEATQWN5L7aIMJ+5/97qE AACJxQf8CtS1b8BP6+qNM6vjGyb3aUJf8wokzZ2yHkIvjdoSExm0EbeQdhQtuHH9 28enU5OMzCpBHMHG0IzvJddc0IV/J2GiT6Itw1iVNKJyOlF8MztfR9TmtF0WBx7w WUQXQJQVed6JJlAhzzYrxFx5aPIQZh0ESF90yhiE2akcoOrkUvkWIdP84c7OB3eY iMmpHX1JayC8DAmrdduyFcwVReBN4P/ivL+hHx/5c8l5i9VR6tbUMnJbJ5u5uYZk mApkzqqVYvGulKjDa5w3BDpFhnjVJkfxZvzJ305+xPW7yg7oOt/ZEYmTMTxgH4nJ j2l5IVeWcBY5fxSZf9PF+9nGHCSRVA== =khH/ -----END PGP SIGNATURE-----sif-2.8.3/pkg/integrity/testdata/TestSigner_Sign/TwoGroups.golden000066400000000000000000011235651432675271000251530ustar00rootroot00000000000000SIF_MAGIC0101 n@Y+0m})@T n n01@ n n01@ n n02@@Y@Y\ XK '@WW@Y@Y\ XK 'hsqsgCb sHello from Sylabs! 'xcb]I`bFpy&8Vbf ^xc`F(WRQ`xc`( ! @8 aDbsDbSнDb 8jE5/home/adam/src/sif/loopfs%=Kt 0|j нDb@8? нDbнDbнDbA:Db8Db8Db@нDbнDbнDb AнDb8Db8Db8DbmDb1DbDb dV . ..  lost+found  hello.txt ...Hello from Sylabs! -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 {"version":1,"header":{"digest":"sha256:635fa0a14a8ef0c0351ed3e985799ed1d4f75ce973dea3cc76c99710795cc3f1"},"objects":[{"relativeId":0,"descriptorDigest":"sha256:3634ad01db0dd5482ecf685267b53d6201690438ca27c3d7ea91c971a1f41f92","objectDigest":"sha256:004dfc8da678c309de28b5386a1e9efd57f536b150c40d29b31506aa0fb17ec2"},{"relativeId":1,"descriptorDigest":"sha256:04b5f87c9692a54f80d10fb6af00c779763aeca29d610348854bd97cd8bf66fd","objectDigest":"sha256:9f9c4e5e131934969b4ac8f495691c70b8c6c8e3f489c2c9ab5f1af82bce0604"}]} -----BEGIN PGP SIGNATURE----- wsBzBAEBCAAnBQJZr0CRCZCiDCfuf/e6hBYhBBIEXIwLEATQWN5L7aIMJ+5/97qE AACSlwgAoCApIsp01PrpgncNBYs9HKmB5/erkD47ZL4B3Kw5+hWoB45eGQIIs/gZ vJlG8ph7yOzm0xRE2DlogRVbDA+b8bzcdeGylpTfDDuhVgBrMpJ1CsvxWpwJEyCI rZBSgsRrp/qnR7WKtRdYx9xI9kUHT5r/XKm1or1BF5DYzx6JusNAcsMyUNjYd+Vb yQVyQ/wM4P509DgxK7btJUhGwVS5cV0J69TTzQo0zMEDxUtvkKwnMZiJdxPji+3h Uamm2ZxoOHoiEhGc1odPbhxWvxbXrlpyrKVUqyX7XFtPNbXMV3h5B0RvgThDMFcL JYY/FrUAgkPqCk0LWnfir0xPSGEY0Q== =zbzg -----END PGP SIGNATURE----------BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 {"version":1,"header":{"digest":"sha256:635fa0a14a8ef0c0351ed3e985799ed1d4f75ce973dea3cc76c99710795cc3f1"},"objects":[{"relativeId":0,"descriptorDigest":"sha256:b356c9810f880c619f95c34909387de498895fc2054ef590ebaeab5c9b50c995","objectDigest":"sha256:d2dd40e7ff6b6753d84c1a85061189e61d4de9688d5531537ff96ff09b1f12dc"}]} -----BEGIN PGP SIGNATURE----- wsBzBAEBCAAnBQJZr0CRCZCiDCfuf/e6hBYhBBIEXIwLEATQWN5L7aIMJ+5/97qE AACJxQf8CtS1b8BP6+qNM6vjGyb3aUJf8wokzZ2yHkIvjdoSExm0EbeQdhQtuHH9 28enU5OMzCpBHMHG0IzvJddc0IV/J2GiT6Itw1iVNKJyOlF8MztfR9TmtF0WBx7w WUQXQJQVed6JJlAhzzYrxFx5aPIQZh0ESF90yhiE2akcoOrkUvkWIdP84c7OB3eY iMmpHX1JayC8DAmrdduyFcwVReBN4P/ivL+hHx/5c8l5i9VR6tbUMnJbJ5u5uYZk mApkzqqVYvGulKjDa5w3BDpFhnjVJkfxZvzJ305+xPW7yg7oOt/ZEYmTMTxgH4nJ j2l5IVeWcBY5fxSZf9PF+9nGHCSRVA== =khH/ -----END PGP SIGNATURE-----sif-2.8.3/pkg/integrity/testdata/Test_clearsignEncoder_signMessage/000077500000000000000000000000001432675271000255175ustar00rootroot00000000000000sif-2.8.3/pkg/integrity/testdata/Test_clearsignEncoder_signMessage/OK.golden000066400000000000000000000010521432675271000272200ustar00rootroot00000000000000-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 {"One":1,"Two":2} -----BEGIN PGP SIGNATURE----- wsBzBAEBCAAnBQJZr0CRCZCiDCfuf/e6hBYhBBIEXIwLEATQWN5L7aIMJ+5/97qE AACREgf8DNQbgGAyNKBnKtjx5pPBu0XUNNM0tEsaBM4RbWA68W0KzS6XefIeuRs4 73JQh+YcLUcb3kffyezb3VFn4wUjZmTJEwi6uPldSJYrex4mLSzKDukj7D4ZaAvc qk1rFfi/rwFTAPGA10j1UAwfHEcv+dNZc0LdIEcKNxL2SxACMMrs5LjncQcNBKLe ATlaMLV4fQ3MKDRdopi7U7tkH3vaF9QyrD2pxI0yCdRyRy0Xlc4i60zbE7wWPGJx E3EWFIylZyKzAbZlz4pSUWqts08+wAbWbwg0EPrsLd3ZV5LkO2aFlyqS1/A73sYb 4QRf6OiFm24EfTVcfbKeiTG/obwv/w== =OJk4 -----END PGP SIGNATURE-----sif-2.8.3/pkg/integrity/testdata/sources/000077500000000000000000000000001432675271000204075ustar00rootroot00000000000000sif-2.8.3/pkg/integrity/testdata/sources/descr-rid0.bin000066400000000000000000000010551432675271000230360ustar00rootroot00000000000000@sif-2.8.3/pkg/integrity/testdata/sources/descr-rid1.bin000066400000000000000000000010551432675271000230370ustar00rootroot00000000000000@sif-2.8.3/pkg/integrity/testdata/sources/header.bin000066400000000000000000000000751432675271000223330ustar00rootroot00000000000000#!/usr/bin/env run-singularity SIF_MAGIC01eNPNOsif-2.8.3/pkg/integrity/verify.go000066400000000000000000000445171432675271000167610ustar00rootroot00000000000000// Copyright (c) 2020-2022, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the LICENSE.md file // distributed with the sources of this project regarding your rights to use or distribute this // software. package integrity import ( "bytes" "crypto" "encoding/hex" "encoding/json" "errors" "fmt" "io" "sort" "strings" "github.com/ProtonMail/go-crypto/openpgp" "github.com/sylabs/sif/v2/pkg/sif" ) var ( errFingerprintMismatch = errors.New("fingerprint in descriptor does not correspond to signing entity") errNonGroupedObject = errors.New("non-signature object not associated with object group") ) // SignatureNotValidError records an error when an invalid signature is encountered. type SignatureNotValidError struct { ID uint32 // Signature object ID. Err error // Wrapped error. } func (e *SignatureNotValidError) Error() string { b := &strings.Builder{} if e.ID == 0 { fmt.Fprintf(b, "signature not valid") } else { fmt.Fprintf(b, "signature object %v not valid", e.ID) } if e.Err != nil { fmt.Fprintf(b, ": %v", e.Err) } return b.String() } func (e *SignatureNotValidError) Unwrap() error { return e.Err } // Is compares e against target. If target is a SignatureNotValidError and matches e or target has // a zero value ID, true is returned. func (e *SignatureNotValidError) Is(target error) bool { //nolint:errorlint // don't compare wrapped errors in Is() t, ok := target.(*SignatureNotValidError) if !ok { return false } return e.ID == t.ID || t.ID == 0 } // VerifyCallback is called immediately after a signature is verified. If r contains a non-nil // error, and the callback returns true, the error is ignored, and verification proceeds as if no // error occurred. type VerifyCallback func(r VerifyResult) (ignoreError bool) type groupVerifier struct { f *sif.FileImage // SIF image to verify. groupID uint32 // Object group ID. ods []sif.Descriptor // Object descriptors. subsetOK bool // If true, permit ods to be a subset of the objects in signatures. } // newGroupVerifier constructs a new group verifier, optionally limited to objects described by // ods. If no descriptors are supplied, verify all objects in group. func newGroupVerifier(f *sif.FileImage, groupID uint32, ods ...sif.Descriptor) (*groupVerifier, error) { v := groupVerifier{f: f, groupID: groupID, ods: ods} if len(ods) == 0 { ods, err := getGroupObjects(f, groupID) if err != nil { return nil, err } v.ods = ods } else { v.subsetOK = true } return &v, nil } // signatures returns descriptors in f that contain signature objects linked to the objects // specified by v. If no such signatures are found, a SignatureNotFoundError is returned. func (v *groupVerifier) signatures() ([]sif.Descriptor, error) { return getGroupSignatures(v.f, v.groupID, false) } // verifySignature performs cryptographic validation of the digital signature contained in sig // using decoder de, populating vr as appropriate. // // If an invalid signature is encountered, a SignatureNotValidError is returned. // // If verification of the SIF global header fails, ErrHeaderIntegrity is returned. If verification // of a data object descriptor fails, a DescriptorIntegrityError is returned. If verification of a // data object fails, a ObjectIntegrityError is returned. func (v *groupVerifier) verifySignature(sig sif.Descriptor, de decoder, vr *VerifyResult) error { ht, fp, err := sig.SignatureMetadata() if err != nil { return err } // Verify signature and decode message. b, err := de.verifyMessage(sig.GetReader(), ht, vr) if err != nil { return &SignatureNotValidError{ID: sig.ID(), Err: err} } // Unmarshal image metadata. var im imageMetadata if err = json.Unmarshal(b, &im); err != nil { return &SignatureNotValidError{ID: sig.ID(), Err: err} } // Get minimum object ID in group, and use this to populate absolute object IDs in im. minID, err := getGroupMinObjectID(v.f, v.groupID) if err != nil { return err } im.populateAbsoluteObjectIDs(minID) // Ensure signing entity matches fingerprint in descriptor. if e := vr.e; e != nil && !bytes.Equal(e.PrimaryKey.Fingerprint, fp) { return errFingerprintMismatch } // If an object subset is not permitted, verify our set of IDs match exactly what is in the // image metadata. if !v.subsetOK { if err := im.objectIDsMatch(v.ods); err != nil { return err } } // Verify header and object integrity. vr.verified, err = im.matches(v.f, v.ods) return err } type legacyGroupVerifier struct { f *sif.FileImage // SIF image to verify. groupID uint32 // Object group ID. ods []sif.Descriptor // Object descriptors. } // newLegacyGroupVerifier constructs a new legacy group verifier. func newLegacyGroupVerifier(f *sif.FileImage, groupID uint32) (*legacyGroupVerifier, error) { ods, err := getGroupObjects(f, groupID) if err != nil { return nil, err } return &legacyGroupVerifier{f: f, groupID: groupID, ods: ods}, nil } // signatures returns descriptors in f that contain signature objects linked to the objects // specified by v. If no such signatures are found, a SignatureNotFoundError is returned. func (v *legacyGroupVerifier) signatures() ([]sif.Descriptor, error) { return getGroupSignatures(v.f, v.groupID, true) } // verifySignature performs cryptographic validation of the digital signature contained in sig // using decoder de, populating vr as appropriate. // // If an invalid signature is encountered, a SignatureNotValidError is returned. // // If verification of a data object fails, a ObjectIntegrityError is returned. func (v *legacyGroupVerifier) verifySignature(sig sif.Descriptor, de decoder, vr *VerifyResult) error { // Verify signature and decode message. b, err := de.verifyMessage(sig.GetReader(), crypto.SHA256, vr) if err != nil { return &SignatureNotValidError{ID: sig.ID(), Err: err} } ht, fp, err := sig.SignatureMetadata() if err != nil { return err } // Ensure signing entity matches fingerprint in descriptor. if e := vr.e; e != nil { if !bytes.Equal(e.PrimaryKey.Fingerprint, fp) { return errFingerprintMismatch } } // Obtain digest from plaintext. d, err := newLegacyDigest(ht, b) if err != nil { return err } // Get reader covering all non-signature objects. rs := make([]io.Reader, 0, len(v.ods)) for _, od := range v.ods { rs = append(rs, od.GetReader()) } r := io.MultiReader(rs...) // Verify integrity of objects. if ok, err := d.matches(r); err != nil { return err } else if !ok { return &ObjectIntegrityError{} } vr.verified = v.ods return nil } type legacyObjectVerifier struct { f *sif.FileImage // SIF image to verify. od sif.Descriptor // Object descriptor. } // newLegacyObjectVerifier constructs a new legacy object verifier. func newLegacyObjectVerifier(f *sif.FileImage, od sif.Descriptor) *legacyObjectVerifier { return &legacyObjectVerifier{f: f, od: od} } // signatures returns descriptors in f that contain signature objects linked to the objects // specified by v. If no such signatures are found, a SignatureNotFoundError is returned. func (v *legacyObjectVerifier) signatures() ([]sif.Descriptor, error) { return getObjectSignatures(v.f, v.od.ID()) } // verifySignature performs cryptographic validation of the digital signature contained in sig // using decoder de, populating vr as appropriate. // // If an invalid signature is encountered, a SignatureNotValidError is returned. // // If verification of a data object fails, a ObjectIntegrityError is returned. func (v *legacyObjectVerifier) verifySignature(sig sif.Descriptor, de decoder, vr *VerifyResult) error { // Verify signature and decode message. b, err := de.verifyMessage(sig.GetReader(), crypto.SHA256, vr) if err != nil { return &SignatureNotValidError{ID: sig.ID(), Err: err} } ht, fp, err := sig.SignatureMetadata() if err != nil { return err } // Ensure signing entity matches fingerprint in descriptor. if e := vr.e; e != nil { if !bytes.Equal(e.PrimaryKey.Fingerprint, fp) { return errFingerprintMismatch } } // Obtain digest from plaintext. d, err := newLegacyDigest(ht, b) if err != nil { return err } // Verify object integrity. if ok, err := d.matches(v.od.GetReader()); err != nil { return err } else if !ok { return &ObjectIntegrityError{ID: v.od.ID()} } vr.verified = []sif.Descriptor{v.od} return nil } type decoder interface { // verifyMessage reads a message from r, verifies its signature, and returns the message // contents. verifyMessage(r io.Reader, h crypto.Hash, vr *VerifyResult) ([]byte, error) } type verifyTask interface { // signatures returns descriptors that contain signature objects linked to the task. If no such // signatures are found, a SignatureNotFoundError is returned. signatures() ([]sif.Descriptor, error) // verifySignature performs cryptographic validation of the digital signature contained in sig // using decoder de, populating vr as appropriate. // // If an invalid signature is encountered, a SignatureNotValidError is returned. // // If verification of the SIF global header fails, ErrHeaderIntegrity is returned. If // verification of a data object descriptor fails, a DescriptorIntegrityError is returned. If // verification of a data object fails, a ObjectIntegrityError is returned. verifySignature(sig sif.Descriptor, de decoder, vr *VerifyResult) error } type verifyOpts struct { kr openpgp.KeyRing groups []uint32 objects []uint32 isLegacy bool isLegacyAll bool cb VerifyCallback } // VerifierOpt are used to configure vo. type VerifierOpt func(vo *verifyOpts) error // OptVerifyWithKeyRing sets the keyring to use for verification to kr. func OptVerifyWithKeyRing(kr openpgp.KeyRing) VerifierOpt { return func(vo *verifyOpts) error { vo.kr = kr return nil } } // OptVerifyGroup adds a verification task for the group with the specified groupID. This may be // called multliple times to request verification of more than one group. func OptVerifyGroup(groupID uint32) VerifierOpt { return func(vo *verifyOpts) error { if groupID == 0 { return sif.ErrInvalidGroupID } vo.groups = insertSorted(vo.groups, groupID) return nil } } // OptVerifyObject adds a verification task for the object with the specified id. This may be // called multliple times to request verification of more than one object. func OptVerifyObject(id uint32) VerifierOpt { return func(vo *verifyOpts) error { if id == 0 { return sif.ErrInvalidObjectID } vo.objects = insertSorted(vo.objects, id) return nil } } // OptVerifyLegacy enables verification of legacy signatures. Non-legacy signatures will not be // considered. // // Note that legacy signatures do not provide integrity protection of metadata contained in the // global header or object descriptors. For the best security, use of non-legacy signatures is // required. func OptVerifyLegacy() VerifierOpt { return func(vo *verifyOpts) error { vo.isLegacy = true return nil } } // OptVerifyLegacyAll enables verification of legacy signatures, and adds verification tasks for // all non-signature objects that are part of a group. Non-legacy signatures will not be // considered. // // Note that legacy signatures do not provide integrity protection of metadata contained in the // global header or object descriptors. For the best security, use of non-legacy signatures is // required. func OptVerifyLegacyAll() VerifierOpt { return func(vo *verifyOpts) error { vo.isLegacy = true vo.isLegacyAll = true return nil } } // OptVerifyCallback registers cb as the verification callback, which is called after each // signature is verified. func OptVerifyCallback(cb VerifyCallback) VerifierOpt { return func(vo *verifyOpts) error { vo.cb = cb return nil } } // getTasks returns verification tasks corresponding to groupIDs and objectIDs. func getTasks(f *sif.FileImage, groupIDs, objectIDs []uint32) ([]verifyTask, error) { t := make([]verifyTask, 0, len(groupIDs)+len(objectIDs)) for _, groupID := range groupIDs { v, err := newGroupVerifier(f, groupID) if err != nil { return nil, err } t = append(t, v) } for _, id := range objectIDs { od, err := f.GetDescriptor(sif.WithID(id)) if err != nil { return nil, err } v, err := newGroupVerifier(f, od.GroupID(), od) if err != nil { return nil, err } t = append(t, v) } return t, nil } // getLegacyTasks returns legacy verification tasks corresponding to groupIDs and objectIDs. func getLegacyTasks(f *sif.FileImage, groupIDs, objectIDs []uint32) ([]verifyTask, error) { t := make([]verifyTask, 0, len(groupIDs)+len(objectIDs)) for _, groupID := range groupIDs { v, err := newLegacyGroupVerifier(f, groupID) if err != nil { return nil, err } t = append(t, v) } for _, id := range objectIDs { od, err := f.GetDescriptor(sif.WithID(id)) if err != nil { return nil, err } t = append(t, newLegacyObjectVerifier(f, od)) } return t, nil } // Verifier describes a SIF image verifier. type Verifier struct { f *sif.FileImage tasks []verifyTask kr openpgp.KeyRing cb VerifyCallback } // NewVerifier returns a Verifier to examine and/or verify digital signatures(s) in f according to // opts. // // Verify requires key material be provided. OptVerifyWithKeyRing can be used for this purpose. Key // material is not required for routines that do not perform cryptographic verification, such as // AnySignedBy or AllSignedBy. // // By default, the returned Verifier will consider non-legacy signatures for all object groups. To // override this behavior, consider using OptVerifyGroup, OptVerifyObject, OptVerifyLegacy, and/or // OptVerifyLegacyAll. func NewVerifier(f *sif.FileImage, opts ...VerifierOpt) (*Verifier, error) { if f == nil { return nil, fmt.Errorf("integrity: %w", errNilFileImage) } vo := verifyOpts{} // Apply options. for _, o := range opts { if err := o(&vo); err != nil { return nil, fmt.Errorf("integrity: %w", err) } } // If "legacy all" mode selected, add all non-signature objects that are in a group. if vo.isLegacyAll { f.WithDescriptors(func(od sif.Descriptor) bool { if od.DataType() != sif.DataSignature && od.GroupID() != 0 { vo.objects = insertSorted(vo.objects, od.ID()) } return false }) } // If no verification tasks specified, add one per object group if len(vo.groups) == 0 && len(vo.objects) == 0 { ids, err := getGroupIDs(f) if err != nil { return nil, fmt.Errorf("integrity: %w", err) } vo.groups = ids } // Get tasks. getTasksFunc := getTasks if vo.isLegacy { getTasksFunc = getLegacyTasks } t, err := getTasksFunc(f, vo.groups, vo.objects) if err != nil { return nil, fmt.Errorf("integrity: %w", err) } v := Verifier{ f: f, tasks: t, kr: vo.kr, cb: vo.cb, } return &v, nil } // fingerprints returns a sorted list of unique fingerprints of entities participating in the // verification tasks in v. If any is true, entities involved in at least one task are included. // Otherwise, only entities participatinging in all tasks are included. func (v *Verifier) fingerprints(any bool) ([][]byte, error) { m := make(map[string]int) // Build up a map containing fingerprints, and the number of tasks they are participating in. for _, t := range v.tasks { sigs, err := t.signatures() if err != nil && !errors.Is(err, &SignatureNotFoundError{}) { return nil, err } fps, err := getFingerprints(sigs) if err != nil { return nil, err } for _, fp := range fps { m[hex.EncodeToString(fp)]++ } } // Build up list of fingerprints. var fps [][]byte for fp, n := range m { if any || len(v.tasks) == n { b, err := hex.DecodeString(fp) if err != nil { panic(err) } fps = append(fps, b) } } sort.Slice(fps, func(i, j int) bool { return bytes.Compare(fps[i], fps[j]) < 0 }) return fps, nil } // AnySignedBy returns fingerprints for entities that have signed any of the objects specified by // verification tasks in v. // // Note that this routine does not perform cryptograhic validation. To ensure the image contains // cryptographically valid signatures, use Verify. func (v *Verifier) AnySignedBy() ([][]byte, error) { fps, err := v.fingerprints(true) if err != nil { return nil, fmt.Errorf("integrity: %w", err) } return fps, nil } // AllSignedBy returns fingerprints for entities that have signed all of the objects specified by // verification tasks in v. // // Note that this routine does not perform cryptograhic validation. To ensure the image contains // cryptographically valid signatures, use Verify. func (v *Verifier) AllSignedBy() ([][]byte, error) { fps, err := v.fingerprints(false) if err != nil { return nil, fmt.Errorf("integrity: %w", err) } return fps, nil } // Verify performs all cryptographic verification tasks specified by v. // // If key material was not provided when v was created, Verify returns an error wrapping // ErrNoKeyMaterial. // // If no signatures are found for a task specified by v, an error wrapping a SignatureNotFoundError // is returned. If an invalid signature is encountered, an error wrapping a SignatureNotValidError // is returned. // // If verification of the SIF global header fails, an error wrapping ErrHeaderIntegrity is // returned. If verification of a data object descriptor fails, an error wrapping a // DescriptorIntegrityError is returned. If verification of a data object fails, an error wrapping // a ObjectIntegrityError is returned. func (v *Verifier) Verify() error { // Get message decoder. var de decoder switch { case v.kr != nil: de = newClearsignDecoder(v.kr) default: return fmt.Errorf("integrity: %w", ErrNoKeyMaterial) } // All non-signature objects must be contained in an object group. ods, err := v.f.GetDescriptors(sif.WithNoGroup()) if err != nil { return fmt.Errorf("integrity: %w", err) } for _, od := range ods { if od.DataType() != sif.DataSignature { return fmt.Errorf("integrity: %w", errNonGroupedObject) } } // Verify signature(s) associated with each task. for _, t := range v.tasks { sigs, err := t.signatures() if err != nil { return fmt.Errorf("integrity: %w", err) } for _, sig := range sigs { vr := VerifyResult{sig: sig} // Verify signature. err := t.verifySignature(sig, de, &vr) // Call verify callback, if applicable. if v.cb != nil { vr.err = err if ignoreError := v.cb(vr); ignoreError { err = nil } } if err != nil { return fmt.Errorf("integrity: %w", err) } } } return nil } sif-2.8.3/pkg/integrity/verify_test.go000066400000000000000000000610221432675271000200060ustar00rootroot00000000000000// Copyright (c) 2020-2022, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the LICENSE.md file // distributed with the sources of this project regarding your rights to use or distribute this // software. package integrity import ( "crypto" "errors" "io" "path/filepath" "reflect" "strings" "testing" "github.com/ProtonMail/go-crypto/openpgp" pgperrors "github.com/ProtonMail/go-crypto/openpgp/errors" "github.com/sylabs/sif/v2/pkg/sif" ) func TestGroupVerifier_signatures(t *testing.T) { oneGroupImage := loadContainer(t, filepath.Join(corpus, "one-group.sif")) oneGroupSignedImage := loadContainer(t, filepath.Join(corpus, "one-group-signed.sif")) sigs, err := oneGroupSignedImage.GetDescriptors(sif.WithDataType(sif.DataSignature)) if err != nil { t.Fatal(err) } tests := []struct { name string f *sif.FileImage groupID uint32 wantSigs []sif.Descriptor wantErr error }{ { name: "Unsigned", f: oneGroupImage, groupID: 1, wantErr: &SignatureNotFoundError{}, }, { name: "Signed", f: oneGroupSignedImage, groupID: 1, wantSigs: sigs, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { v := &groupVerifier{ f: tt.f, groupID: tt.groupID, } sigs, err := v.signatures() if got, want := err, tt.wantErr; !errors.Is(got, want) { t.Fatalf("got error %v, want %v", got, want) } if got, want := sigs, tt.wantSigs; !reflect.DeepEqual(got, want) { t.Errorf("got signatures %v, want %v", got, want) } }) } } func TestGroupVerifier_verify(t *testing.T) { oneGroupSignedImage := loadContainer(t, filepath.Join(corpus, "one-group-signed.sif")) sig, err := oneGroupSignedImage.GetDescriptor(sif.WithDataType(sif.DataSignature)) if err != nil { t.Fatal(err) } verified, err := oneGroupSignedImage.GetDescriptors(sif.WithGroupID(1)) if err != nil { t.Fatal(err) } e := getTestEntity(t) tests := []struct { name string f *sif.FileImage groupID uint32 objectIDs []uint32 subsetOK bool sig sif.Descriptor de decoder wantErr error wantVerified []sif.Descriptor wantEntity *openpgp.Entity }{ { name: "SignedObjectNotFound", f: oneGroupSignedImage, groupID: 1, objectIDs: []uint32{1}, sig: sig, de: newClearsignDecoder(openpgp.EntityList{e}), wantErr: errSignedObjectNotFound, wantEntity: e, }, { name: "UnknownIssuer", f: oneGroupSignedImage, groupID: 1, objectIDs: []uint32{1, 2}, sig: sig, de: newClearsignDecoder(openpgp.EntityList{}), wantErr: &SignatureNotValidError{ ID: 3, Err: pgperrors.ErrUnknownIssuer, }, }, { name: "OneGroupSigned", f: oneGroupSignedImage, groupID: 1, objectIDs: []uint32{1, 2}, sig: sig, de: newClearsignDecoder(openpgp.EntityList{e}), wantVerified: verified, wantEntity: e, }, { name: "OneGroupSignedSubset", f: oneGroupSignedImage, groupID: 1, objectIDs: []uint32{1}, subsetOK: true, sig: sig, de: newClearsignDecoder(openpgp.EntityList{e}), wantVerified: verified[:1], wantEntity: e, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { ods := make([]sif.Descriptor, len(tt.objectIDs)) for i, id := range tt.objectIDs { od, err := tt.f.GetDescriptor(sif.WithID(id)) if err != nil { t.Fatal(err) } ods[i] = od } v := &groupVerifier{ f: tt.f, groupID: tt.groupID, ods: ods, subsetOK: tt.subsetOK, } var vr VerifyResult err := v.verifySignature(tt.sig, tt.de, &vr) if got, want := err, tt.wantErr; !errors.Is(got, want) { t.Errorf("got error %v, want %v", got, want) } if got, want := vr.Verified(), tt.wantVerified; !reflect.DeepEqual(got, want) { t.Errorf("got verified %v, want %v", got, want) } if got, want := vr.Entity(), tt.wantEntity; !reflect.DeepEqual(got, want) { t.Errorf("got entity %v, want %v", got, want) } }) } } func TestLegacyGroupVerifier_signatures(t *testing.T) { oneGroupImage := loadContainer(t, filepath.Join(corpus, "one-group.sif")) oneGroupSignedImage := loadContainer(t, filepath.Join(corpus, "one-group-signed-legacy-group.sif")) sigs, err := oneGroupSignedImage.GetDescriptors(sif.WithDataType(sif.DataSignature)) if err != nil { t.Fatal(err) } tests := []struct { name string f *sif.FileImage id uint32 wantSigs []sif.Descriptor wantErr error }{ { name: "Unsigned", f: oneGroupImage, id: 1, wantErr: &SignatureNotFoundError{}, }, { name: "Signed", f: oneGroupSignedImage, id: 1, wantSigs: sigs, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { v := &legacyGroupVerifier{ f: tt.f, groupID: 1, } sigs, err := v.signatures() if got, want := err, tt.wantErr; !errors.Is(got, want) { t.Fatalf("got error %v, want %v", got, want) } if got, want := sigs, tt.wantSigs; !reflect.DeepEqual(got, want) { t.Errorf("got signatures %v, want %v", got, want) } }) } } func TestLegacyGroupVerifier_verify(t *testing.T) { oneGroupSignedImage := loadContainer(t, filepath.Join(corpus, "one-group-signed-legacy-group.sif")) sig, err := oneGroupSignedImage.GetDescriptor(sif.WithDataType(sif.DataSignature)) if err != nil { t.Fatal(err) } verified, err := oneGroupSignedImage.GetDescriptors(sif.WithGroupID(1)) if err != nil { t.Fatal(err) } e := getTestEntity(t) tests := []struct { name string f *sif.FileImage groupID uint32 sig sif.Descriptor de decoder wantErr error wantVerified []sif.Descriptor wantEntity *openpgp.Entity }{ { name: "UnknownIssuer", f: oneGroupSignedImage, groupID: 1, sig: sig, de: newClearsignDecoder(openpgp.EntityList{}), wantErr: &SignatureNotValidError{ ID: 3, Err: pgperrors.ErrUnknownIssuer, }, }, { name: "OneGroupSigned", f: oneGroupSignedImage, groupID: 1, sig: sig, de: newClearsignDecoder(openpgp.EntityList{e}), wantVerified: verified, wantEntity: e, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { ods, err := getGroupObjects(tt.f, tt.groupID) if err != nil { t.Fatal(err) } v := &legacyGroupVerifier{ f: tt.f, groupID: tt.groupID, ods: ods, } var vr VerifyResult err = v.verifySignature(tt.sig, tt.de, &vr) if got, want := err, tt.wantErr; !errors.Is(got, want) { t.Errorf("got error %v, want %v", got, want) } if got, want := vr.Verified(), tt.wantVerified; !reflect.DeepEqual(got, want) { t.Errorf("got verified %v, want %v", got, want) } if got, want := vr.Entity(), tt.wantEntity; !reflect.DeepEqual(got, want) { t.Errorf("got entity %v, want %v", got, want) } }) } } func TestLegacyObjectVerifier_signatures(t *testing.T) { oneGroupImage := loadContainer(t, filepath.Join(corpus, "one-group.sif")) oneGroupSignedImage := loadContainer(t, filepath.Join(corpus, "one-group-signed-legacy-all.sif")) sigs, err := oneGroupSignedImage.GetDescriptors( sif.WithDataType(sif.DataSignature), sif.WithLinkedID(1), ) if err != nil { t.Fatal(err) } tests := []struct { name string f *sif.FileImage id uint32 wantSigs []sif.Descriptor wantErr error }{ { name: "Unsigned", f: oneGroupImage, id: 1, wantErr: &SignatureNotFoundError{}, }, { name: "Signed", f: oneGroupSignedImage, id: 1, wantSigs: sigs, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { od, err := tt.f.GetDescriptor(sif.WithID(tt.id)) if err != nil { t.Fatal(err) } v := &legacyObjectVerifier{ f: tt.f, od: od, } sigs, err := v.signatures() if got, want := err, tt.wantErr; !errors.Is(got, want) { t.Fatalf("got error %v, want %v", got, want) } if got, want := sigs, tt.wantSigs; !reflect.DeepEqual(got, want) { t.Errorf("got signatures %v, want %v", got, want) } }) } } func TestLegacyObjectVerifier_verify(t *testing.T) { oneGroupSignedImage := loadContainer(t, filepath.Join(corpus, "one-group-signed-legacy-all.sif")) sig, err := oneGroupSignedImage.GetDescriptor( sif.WithDataType(sif.DataSignature), sif.WithLinkedID(1), ) if err != nil { t.Fatal(err) } verified, err := oneGroupSignedImage.GetDescriptors(sif.WithID(1)) if err != nil { t.Fatal(err) } e := getTestEntity(t) tests := []struct { name string f *sif.FileImage id uint32 sig sif.Descriptor de decoder wantErr error wantVerified []sif.Descriptor wantEntity *openpgp.Entity }{ { name: "UnknownIssuer", f: oneGroupSignedImage, id: 1, sig: sig, de: newClearsignDecoder(openpgp.EntityList{}), wantErr: &SignatureNotValidError{ ID: 3, Err: pgperrors.ErrUnknownIssuer, }, }, { name: "OneGroupSigned", f: oneGroupSignedImage, id: 1, sig: sig, de: newClearsignDecoder(openpgp.EntityList{e}), wantVerified: verified, wantEntity: e, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { od, err := tt.f.GetDescriptor(sif.WithID(tt.id)) if err != nil { t.Fatal(err) } v := &legacyObjectVerifier{ f: tt.f, od: od, } var vr VerifyResult err = v.verifySignature(tt.sig, tt.de, &vr) if got, want := err, tt.wantErr; !errors.Is(got, want) { t.Errorf("got error %v, want %v", got, want) } if got, want := vr.Verified(), tt.wantVerified; !reflect.DeepEqual(got, want) { t.Errorf("got verified %v, want %v", got, want) } if got, want := vr.Entity(), tt.wantEntity; !reflect.DeepEqual(got, want) { t.Errorf("got entity %v, want %v", got, want) } }) } } func TestNewVerifier(t *testing.T) { emptyImage := loadContainer(t, filepath.Join(corpus, "empty.sif")) oneGroupImage := loadContainer(t, filepath.Join(corpus, "one-group.sif")) twoGroupImage := loadContainer(t, filepath.Join(corpus, "two-groups.sif")) kr := openpgp.EntityList{getTestEntity(t)} cb := func(r VerifyResult) bool { return false } tests := []struct { name string fi *sif.FileImage opts []VerifierOpt wantErr error wantKeyring openpgp.KeyRing wantGroups []uint32 wantObjects []uint32 wantLegacy bool wantLegacyAll bool wantCallback bool wantTasks int }{ { name: "NilFileImage", fi: nil, wantErr: errNilFileImage, }, { name: "NoGroupsFound", fi: emptyImage, opts: []VerifierOpt{}, wantErr: errNoGroupsFound, }, { name: "InvalidGroupID", fi: emptyImage, opts: []VerifierOpt{OptVerifyGroup(0)}, wantErr: sif.ErrInvalidGroupID, }, { name: "NoObjects", fi: emptyImage, opts: []VerifierOpt{OptVerifyWithKeyRing(kr), OptVerifyGroup(1)}, wantErr: sif.ErrNoObjects, }, { name: "GroupNotFound", fi: oneGroupImage, opts: []VerifierOpt{OptVerifyWithKeyRing(kr), OptVerifyGroup(2)}, wantErr: errGroupNotFound, }, { name: "GroupNotFoundLegacy", fi: oneGroupImage, opts: []VerifierOpt{OptVerifyWithKeyRing(kr), OptVerifyGroup(2), OptVerifyLegacy()}, wantErr: errGroupNotFound, }, { name: "InvalidObjectID", fi: emptyImage, opts: []VerifierOpt{OptVerifyObject(0)}, wantErr: sif.ErrInvalidObjectID, }, { name: "ObjectNotFound", fi: oneGroupImage, opts: []VerifierOpt{OptVerifyObject(3)}, wantErr: sif.ErrObjectNotFound, }, { name: "ObjectNotFoundLegacy", fi: oneGroupImage, opts: []VerifierOpt{OptVerifyObject(3), OptVerifyLegacy()}, wantErr: sif.ErrObjectNotFound, }, { name: "OneGroupDefaults", fi: oneGroupImage, opts: []VerifierOpt{}, wantGroups: []uint32{1}, wantTasks: 1, }, { name: "TwoGroupDefaults", fi: twoGroupImage, opts: []VerifierOpt{}, wantGroups: []uint32{1, 2}, wantTasks: 2, }, { name: "OptVerifyWithKeyRing", fi: twoGroupImage, opts: []VerifierOpt{OptVerifyWithKeyRing(kr)}, wantKeyring: kr, wantGroups: []uint32{1, 2}, wantTasks: 2, }, { name: "OptVerifyGroupDuplicate", fi: twoGroupImage, opts: []VerifierOpt{OptVerifyGroup(1), OptVerifyGroup(1)}, wantGroups: []uint32{1}, wantTasks: 1, }, { name: "OptVerifyGroup1", fi: twoGroupImage, opts: []VerifierOpt{OptVerifyGroup(1)}, wantGroups: []uint32{1}, wantTasks: 1, }, { name: "OptVerifyGroup2", fi: twoGroupImage, opts: []VerifierOpt{OptVerifyGroup(2)}, wantGroups: []uint32{2}, wantTasks: 1, }, { name: "OptVerifyGroups", fi: twoGroupImage, opts: []VerifierOpt{OptVerifyGroup(1), OptVerifyGroup(2)}, wantGroups: []uint32{1, 2}, wantTasks: 2, }, { name: "OptVerifyObjectDuplicate", fi: twoGroupImage, opts: []VerifierOpt{OptVerifyObject(1), OptVerifyObject(1)}, wantObjects: []uint32{1}, wantTasks: 1, }, { name: "OptVerifyObject1", fi: twoGroupImage, opts: []VerifierOpt{OptVerifyObject(1)}, wantObjects: []uint32{1}, wantTasks: 1, }, { name: "OptVerifyObject2", fi: twoGroupImage, opts: []VerifierOpt{OptVerifyObject(2)}, wantObjects: []uint32{2}, wantTasks: 1, }, { name: "OptVerifyObject3", fi: twoGroupImage, opts: []VerifierOpt{OptVerifyObject(3)}, wantObjects: []uint32{3}, wantTasks: 1, }, { name: "OptVerifyObjects", fi: twoGroupImage, opts: []VerifierOpt{OptVerifyObject(1), OptVerifyObject(2), OptVerifyObject(3)}, wantObjects: []uint32{1, 2, 3}, wantTasks: 3, }, { name: "OptVerifyLegacy", fi: twoGroupImage, opts: []VerifierOpt{OptVerifyLegacy()}, wantGroups: []uint32{1, 2}, wantLegacy: true, wantTasks: 2, }, { name: "OptVerifyLegacyGroup1", fi: twoGroupImage, opts: []VerifierOpt{OptVerifyLegacy(), OptVerifyGroup(1)}, wantGroups: []uint32{1}, wantLegacy: true, wantTasks: 1, }, { name: "OptVerifyLegacyObject1", fi: twoGroupImage, opts: []VerifierOpt{OptVerifyLegacy(), OptVerifyObject(1)}, wantObjects: []uint32{1}, wantLegacy: true, wantTasks: 1, }, { name: "OptVerifyLegacyAll", fi: twoGroupImage, opts: []VerifierOpt{OptVerifyLegacyAll()}, wantObjects: []uint32{1, 2, 3}, wantLegacy: true, wantLegacyAll: true, wantTasks: 3, }, { name: "OptVerifyCallback", fi: twoGroupImage, opts: []VerifierOpt{OptVerifyCallback(cb)}, wantGroups: []uint32{1, 2}, wantCallback: true, wantTasks: 2, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { v, err := NewVerifier(tt.fi, tt.opts...) if got, want := err, tt.wantErr; !errors.Is(got, want) { t.Fatalf("got error %v, want %v", got, want) } if err == nil { if got, want := v.f, tt.fi; got != want { t.Errorf("got FileImage %v, want %v", got, want) } if got, want := v.kr, tt.wantKeyring; !reflect.DeepEqual(got, want) { t.Errorf("got key ring %v, want %v", got, want) } if got, want := len(v.tasks), tt.wantTasks; got != want { t.Errorf("got %v tasks, want %v", got, want) } } }) } } type mockVerifier struct { sigs []sif.Descriptor sigsErr error verified []sif.Descriptor e *openpgp.Entity verifyErr error } func (v mockVerifier) signatures() ([]sif.Descriptor, error) { return v.sigs, v.sigsErr } func (v mockVerifier) verifySignature(sig sif.Descriptor, de decoder, vr *VerifyResult) error { vr.verified = v.verified vr.e = v.e return v.verifyErr } // getSignedDummy generates a dummy SIF container that contains a data object and one dummy // signature per fingerprint. func getSignedDummy(t *testing.T, fps ...[]byte) *sif.FileImage { t.Helper() di, err := sif.NewDescriptorInput(sif.DataGeneric, strings.NewReader("data"), sif.OptGroupID(1), ) if err != nil { t.Fatal(err) } dis := []sif.DescriptorInput{di} for _, fp := range fps { di, err := sif.NewDescriptorInput(sif.DataSignature, strings.NewReader("sig"), sif.OptSignatureMetadata(crypto.SHA256, fp), sif.OptNoGroup(), sif.OptLinkedGroupID(1), ) if err != nil { t.Fatal(err) } dis = append(dis, di) } var buf sif.Buffer fi, err := sif.CreateContainer(&buf, sif.OptCreateDeterministic(), sif.OptCreateWithDescriptors(dis...), ) if err != nil { t.Fatal(err) } t.Cleanup(func() { if err := fi.UnloadContainer(); err != nil { t.Error(err) } }) return fi } func TestVerifier_AnySignedBy(t *testing.T) { fp1 := []byte{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, } fp2 := []byte{ 0x13, 0x12, 0x11, 0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00, } fi := getSignedDummy(t, fp1, fp2) sigs, err := fi.GetDescriptors(sif.WithDataType(sif.DataSignature)) if err != nil { t.Fatal(err) } tests := []struct { name string tasks []verifyTask wantErr error wantFingerprints [][]byte }{ { name: "OneTaskEOF", tasks: []verifyTask{ mockVerifier{sigsErr: io.EOF}, }, wantErr: io.EOF, }, { name: "TwoTasksEOF", tasks: []verifyTask{ mockVerifier{sigs: sigs[:1]}, mockVerifier{sigsErr: io.EOF}, }, wantErr: io.EOF, }, { name: "OneTaskOneFP", tasks: []verifyTask{ mockVerifier{sigs: sigs[:1]}, }, wantFingerprints: [][]byte{fp1}, }, { name: "TwoTasksSameFP", tasks: []verifyTask{ mockVerifier{sigs: sigs[:1]}, mockVerifier{sigs: sigs[:1]}, }, wantFingerprints: [][]byte{fp1}, }, { name: "TwoTasksTwoFP", tasks: []verifyTask{ mockVerifier{sigs: sigs[:1]}, mockVerifier{sigs: sigs[1:]}, }, wantFingerprints: [][]byte{fp1, fp2}, }, { name: "KitchenSink", tasks: []verifyTask{ mockVerifier{}, mockVerifier{sigs: sigs[:1]}, mockVerifier{sigs: sigs[1:]}, mockVerifier{sigs: sigs}, }, wantFingerprints: [][]byte{fp1, fp2}, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { v := Verifier{tasks: tt.tasks} fp, err := v.AnySignedBy() if got, want := err, tt.wantErr; !errors.Is(got, want) { t.Fatalf("got error %v, want %v", got, want) } if got, want := fp, tt.wantFingerprints; !reflect.DeepEqual(got, want) { t.Fatalf("got fingerprints %v, want %v", got, want) } }) } } func TestVerifier_AllSignedBy(t *testing.T) { fp1 := []byte{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, } fp2 := []byte{ 0x13, 0x12, 0x11, 0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00, } fi := getSignedDummy(t, fp1, fp2) sigs, err := fi.GetDescriptors(sif.WithDataType(sif.DataSignature)) if err != nil { t.Fatal(err) } tests := []struct { name string tasks []verifyTask wantErr error wantFingerprints [][]byte }{ { name: "OneTaskEOF", tasks: []verifyTask{ mockVerifier{sigsErr: io.EOF}, }, wantErr: io.EOF, }, { name: "TwoTasksEOF", tasks: []verifyTask{ mockVerifier{sigs: sigs[:1]}, mockVerifier{sigsErr: io.EOF}, }, wantErr: io.EOF, }, { name: "OneTaskNoFP", tasks: []verifyTask{ mockVerifier{}, }, }, { name: "OneTaskOneFP", tasks: []verifyTask{ mockVerifier{sigs: sigs[:1]}, }, wantFingerprints: [][]byte{fp1}, }, { name: "TwoTasksSameFP", tasks: []verifyTask{ mockVerifier{sigs: sigs[:1]}, mockVerifier{sigs: sigs[:1]}, }, wantFingerprints: [][]byte{fp1}, }, { name: "TwoTasksTwoFP", tasks: []verifyTask{ mockVerifier{sigs: sigs[:1]}, mockVerifier{sigs: sigs[1:]}, }, }, { name: "KitchenSink", tasks: []verifyTask{ mockVerifier{}, mockVerifier{sigs: sigs[:1]}, mockVerifier{sigs: sigs[1:]}, mockVerifier{sigs: sigs}, }, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { v := Verifier{tasks: tt.tasks} fp, err := v.AllSignedBy() if got, want := err, tt.wantErr; !errors.Is(got, want) { t.Fatalf("got error %v, want %v", got, want) } if got, want := fp, tt.wantFingerprints; !reflect.DeepEqual(got, want) { t.Fatalf("got fingerprints %v, want %v", got, want) } }) } } func TestVerifier_Verify(t *testing.T) { oneGroupImage := loadContainer(t, filepath.Join(corpus, "one-group.sif")) oneGroupSignedImage := loadContainer(t, filepath.Join(corpus, "one-group-signed.sif")) verified, err := oneGroupSignedImage.GetDescriptors(sif.WithGroupID(1)) if err != nil { t.Fatal(err) } sig, err := oneGroupSignedImage.GetDescriptor(sif.WithDataType(sif.DataSignature)) if err != nil { t.Fatal(err) } e := getTestEntity(t) kr := openpgp.EntityList{e} tests := []struct { name string f *sif.FileImage tasks []verifyTask kr openpgp.KeyRing testCallback bool ignoreError bool wantCBSignature sif.Descriptor wantCBVerified []sif.Descriptor wantCBEntity *openpgp.Entity wantCBErr error wantErr error }{ { name: "NoKeyMaterial", f: oneGroupSignedImage, tasks: []verifyTask{ mockVerifier{}, }, wantErr: ErrNoKeyMaterial, }, { name: "SignatureNotFound", f: oneGroupImage, tasks: []verifyTask{ mockVerifier{ sigsErr: &SignatureNotFoundError{ID: 1, IsGroup: true}, }, }, kr: kr, wantErr: &SignatureNotFoundError{}, }, { name: "UnknownIssuer", f: oneGroupSignedImage, tasks: []verifyTask{ mockVerifier{ sigs: []sif.Descriptor{sig}, verifyErr: &SignatureNotValidError{ID: 3, Err: pgperrors.ErrUnknownIssuer}, }, }, kr: kr, wantErr: &SignatureNotValidError{}, }, { name: "UnknownIssuerIgnoreError", f: oneGroupSignedImage, tasks: []verifyTask{ mockVerifier{ sigs: []sif.Descriptor{sig}, verifyErr: &SignatureNotValidError{ID: 3, Err: pgperrors.ErrUnknownIssuer}, }, }, testCallback: true, ignoreError: true, kr: openpgp.EntityList{}, wantCBSignature: sig, wantCBErr: &SignatureNotValidError{ID: 3, Err: pgperrors.ErrUnknownIssuer}, wantErr: nil, }, { name: "OneGroupSigned", f: oneGroupSignedImage, tasks: []verifyTask{ mockVerifier{ sigs: []sif.Descriptor{sig}, verified: verified, }, }, kr: kr, }, { name: "OneGroupSignedWithCallback", f: oneGroupSignedImage, testCallback: true, tasks: []verifyTask{ mockVerifier{ sigs: []sif.Descriptor{sig}, verified: verified, e: e, }, }, kr: kr, wantCBSignature: sig, wantCBVerified: verified, wantCBEntity: e, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { v := Verifier{ f: tt.f, tasks: tt.tasks, kr: tt.kr, } // Test callback functionality, if requested. if tt.testCallback { v.cb = func(r VerifyResult) bool { if got, want := r.Signature(), tt.wantCBSignature; got != want { t.Errorf("got signature %v, want %v", got, want) } if got, want := r.Verified(), tt.wantCBVerified; !reflect.DeepEqual(got, want) { t.Errorf("got verified %v, want %v", got, want) } if got, want := r.Entity(), tt.wantCBEntity; got != want { t.Errorf("got entity %v, want %v", got, want) } if got, want := r.Error(), tt.wantCBErr; !errors.Is(got, want) { t.Errorf("got error %v, want %v", got, want) } return tt.ignoreError } } if got, want := v.Verify(), tt.wantErr; !errors.Is(got, want) { t.Fatalf("got error %v, want %v", got, want) } }) } } sif-2.8.3/pkg/sif/000077500000000000000000000000001432675271000136565ustar00rootroot00000000000000sif-2.8.3/pkg/sif/arch.go000066400000000000000000000041311432675271000151210ustar00rootroot00000000000000// Copyright (c) 2021-2022, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package sif var ( hdrArchUnknown archType = [...]byte{'0', '0', '\x00'} hdrArch386 archType = [...]byte{'0', '1', '\x00'} hdrArchAMD64 archType = [...]byte{'0', '2', '\x00'} hdrArchARM archType = [...]byte{'0', '3', '\x00'} hdrArchARM64 archType = [...]byte{'0', '4', '\x00'} hdrArchPPC64 archType = [...]byte{'0', '5', '\x00'} hdrArchPPC64le archType = [...]byte{'0', '6', '\x00'} hdrArchMIPS archType = [...]byte{'0', '7', '\x00'} hdrArchMIPSle archType = [...]byte{'0', '8', '\x00'} hdrArchMIPS64 archType = [...]byte{'0', '9', '\x00'} hdrArchMIPS64le archType = [...]byte{'1', '0', '\x00'} hdrArchS390x archType = [...]byte{'1', '1', '\x00'} hdrArchRISCV64 archType = [...]byte{'1', '2', '\x00'} ) type archType [3]byte // getSIFArch returns the archType corresponding to go runtime arch. func getSIFArch(arch string) archType { archMap := map[string]archType{ "386": hdrArch386, "amd64": hdrArchAMD64, "arm": hdrArchARM, "arm64": hdrArchARM64, "ppc64": hdrArchPPC64, "ppc64le": hdrArchPPC64le, "mips": hdrArchMIPS, "mipsle": hdrArchMIPSle, "mips64": hdrArchMIPS64, "mips64le": hdrArchMIPS64le, "s390x": hdrArchS390x, "riscv64": hdrArchRISCV64, } t, ok := archMap[arch] if !ok { return hdrArchUnknown } return t } // GoArch returns the go runtime arch corresponding to t. func (t archType) GoArch() string { archMap := map[archType]string{ hdrArch386: "386", hdrArchAMD64: "amd64", hdrArchARM: "arm", hdrArchARM64: "arm64", hdrArchPPC64: "ppc64", hdrArchPPC64le: "ppc64le", hdrArchMIPS: "mips", hdrArchMIPSle: "mipsle", hdrArchMIPS64: "mips64", hdrArchMIPS64le: "mips64le", hdrArchS390x: "s390x", hdrArchRISCV64: "riscv64", } arch, ok := archMap[t] if !ok { arch = "unknown" } return arch } sif-2.8.3/pkg/sif/arch_test.go000066400000000000000000000026001432675271000161570ustar00rootroot00000000000000// Copyright (c) 2021, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package sif import ( "testing" ) func TestGetSIFArch(t *testing.T) { tests := []struct { name string arch string wantArch archType }{ { name: "386", arch: "386", wantArch: hdrArch386, }, { name: "ARM64", arch: "arm64", wantArch: hdrArchARM64, }, { name: "Unknown", arch: "cray", wantArch: hdrArchUnknown, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got, want := getSIFArch(tt.arch), tt.wantArch; got != want { t.Errorf("got arch %v, want %v", got, want) } }) } } func TestArchType_GetGoArch(t *testing.T) { tests := []struct { name string arch archType wantArch string }{ { name: "386", arch: hdrArch386, wantArch: "386", }, { name: "ARM64", arch: hdrArchARM64, wantArch: "arm64", }, { name: "Unknown", arch: hdrArchUnknown, wantArch: "unknown", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got, want := tt.arch.GoArch(), tt.wantArch; got != want { t.Errorf("got arch %v, want %v", got, want) } }) } } sif-2.8.3/pkg/sif/buffer.go000066400000000000000000000046621432675271000154660ustar00rootroot00000000000000// Copyright (c) 2021-2022, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package sif import ( "errors" "io" ) // A Buffer is a variable-sized buffer of bytes that implements the sif.ReadWriter interface. The // zero value for Buffer is an empty buffer ready to use. type Buffer struct { buf []byte pos int64 } // NewBuffer creates and initializes a new Buffer using buf as its initial contents. func NewBuffer(buf []byte) *Buffer { return &Buffer{buf: buf} } var errNegativeOffset = errors.New("negative offset") // ReadAt implements the io.ReaderAt interface. func (b *Buffer) ReadAt(p []byte, off int64) (int, error) { if off < 0 { return 0, errNegativeOffset } if off >= int64(len(b.buf)) { return 0, io.EOF } n := copy(p, b.buf[off:]) if n < len(p) { return n, io.EOF } return n, nil } var errNegativePosition = errors.New("negative position") // Write implements the io.Writer interface. func (b *Buffer) Write(p []byte) (int, error) { if b.pos < 0 { return 0, errNegativePosition } if have, need := int64(len(b.buf))-b.pos, int64(len(p)); have < need { b.buf = append(b.buf, make([]byte, need-have)...) } n := copy(b.buf[b.pos:], p) b.pos += int64(n) return n, nil } var errInvalidWhence = errors.New("invalid whence") // Seek implements the io.Seeker interface. func (b *Buffer) Seek(offset int64, whence int) (int64, error) { var abs int64 switch whence { case io.SeekStart: abs = offset case io.SeekCurrent: abs = b.pos + offset case io.SeekEnd: abs = int64(len(b.buf)) + offset default: return 0, errInvalidWhence } if abs < 0 { return 0, errNegativePosition } b.pos = abs return abs, nil } var errTruncateRange = errors.New("truncation out of range") // Truncate discards all but the first n bytes from the buffer. func (b *Buffer) Truncate(n int64) error { if n < 0 || n > int64(len(b.buf)) { return errTruncateRange } b.buf = b.buf[:n] return nil } // Bytes returns the contents of the buffer. The slice is valid for use only until the next buffer // modification (that is, only until the next call to a method like ReadAt, Write, or Truncate). func (b *Buffer) Bytes() []byte { return b.buf } // Len returns the number of bytes in the buffer. func (b *Buffer) Len() int64 { return int64(len(b.buf)) } sif-2.8.3/pkg/sif/buffer_test.go000066400000000000000000000160101432675271000165130ustar00rootroot00000000000000// Copyright (c) 2021, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package sif import ( "bytes" "errors" "io" "testing" ) func TestBuffer_ReadAt(t *testing.T) { tests := []struct { name string buf []byte pos int64 p []byte off int64 wantErr error want []byte }{ { name: "OffsetNegative", buf: []byte{0x01, 0x02}, off: -1, wantErr: errNegativeOffset, }, { name: "OffsetEOF", buf: []byte{0x01, 0x02}, off: 2, wantErr: io.EOF, }, { name: "ShortRead", buf: []byte{0x01, 0x02}, p: make([]byte, 1), want: []byte{0x01}, }, { name: "FullRead", buf: []byte{0x01, 0x02}, p: make([]byte, 2), want: []byte{0x01, 0x02}, }, { name: "EOFRead", buf: []byte{0x01, 0x02}, p: make([]byte, 3), wantErr: io.EOF, want: []byte{0x01, 0x02}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { b := &Buffer{ buf: tt.buf, pos: tt.pos, } n, err := b.ReadAt(tt.p, tt.off) if got, want := err, tt.wantErr; !errors.Is(got, want) { t.Fatalf("got error %v, want %v", got, want) } if got, want := n, len(tt.want); got != want { t.Errorf("got n %v, want %v", got, want) } if got, want := tt.p[:n], tt.want; !bytes.Equal(got, want) { t.Errorf("got bytes %v, want %v", got, want) } }) } } func TestBuffer_Write(t *testing.T) { tests := []struct { name string buf []byte pos int64 p []byte wantErr error wantN int wantBuf []byte wantPos int64 }{ { name: "NegativePosition", pos: -1, wantErr: errNegativePosition, wantPos: -1, }, { name: "Overwrite", buf: []byte{0x01, 0x02}, pos: 0, p: []byte{0x03, 0x04}, wantN: 2, wantBuf: []byte{0x03, 0x04}, wantPos: 2, }, { name: "OverwriteAppend", buf: []byte{0x01, 0x02}, pos: 1, p: []byte{0x03, 0x04}, wantN: 2, wantBuf: []byte{0x01, 0x03, 0x04}, wantPos: 3, }, { name: "Append", buf: []byte{0x01, 0x02}, pos: 2, p: []byte{0x03, 0x04}, wantN: 2, wantBuf: []byte{0x01, 0x02, 0x03, 0x04}, wantPos: 4, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { b := &Buffer{ buf: tt.buf, pos: tt.pos, } n, err := b.Write(tt.p) if got, want := err, tt.wantErr; !errors.Is(got, want) { t.Fatalf("got error %v, want %v", got, want) } if got, want := n, tt.wantN; got != want { t.Errorf("got n %v, want %v", got, want) } if got, want := b.buf, tt.wantBuf; !bytes.Equal(got, want) { t.Errorf("got buffer %v, want %v", got, want) } if got, want := b.pos, tt.wantPos; got != want { t.Errorf("got position %v, want %v", got, want) } }) } } func TestBuffer_Seek(t *testing.T) { tests := []struct { name string buf []byte pos int64 offset int64 whence int wantErr error wantPos int64 }{ { name: "InvalidWhence", buf: []byte{0x01, 0x02}, offset: 0, whence: -1, wantErr: errInvalidWhence, }, { name: "StartError", buf: []byte{0x01, 0x02}, pos: 1, offset: -1, whence: io.SeekStart, wantErr: errNegativePosition, }, { name: "StartZero", buf: []byte{0x01, 0x02}, pos: 1, offset: 0, whence: io.SeekStart, wantPos: 0, }, { name: "StartTwo", buf: []byte{0x01, 0x02}, pos: 1, offset: 2, whence: io.SeekStart, wantPos: 2, }, { name: "CurrentError", buf: []byte{0x01, 0x02}, pos: 1, offset: -2, whence: io.SeekCurrent, wantErr: errNegativePosition, }, { name: "CurrentNegative", buf: []byte{0x01, 0x02}, pos: 1, offset: -1, whence: io.SeekCurrent, wantPos: 0, }, { name: "CurrentPositive", buf: []byte{0x01, 0x02}, pos: 1, offset: 1, whence: io.SeekCurrent, wantPos: 2, }, { name: "EndError", buf: []byte{0x01, 0x02}, pos: 1, offset: -3, whence: io.SeekEnd, wantErr: errNegativePosition, }, { name: "EndZero", buf: []byte{0x01, 0x02}, pos: 1, offset: 0, whence: io.SeekEnd, wantPos: 2, }, { name: "EndTwo", buf: []byte{0x01, 0x02}, pos: 1, offset: -2, whence: io.SeekEnd, wantPos: 0, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { b := &Buffer{ buf: tt.buf, pos: tt.pos, } pos, err := b.Seek(tt.offset, tt.whence) if got, want := err, tt.wantErr; !errors.Is(got, want) { t.Fatalf("got error %v, want %v", got, want) } if got, want := pos, tt.wantPos; got != want { t.Fatalf("got position %v, want %v", got, want) } }) } } func TestBuffer_Truncate(t *testing.T) { tests := []struct { name string buf []byte n int64 wantErr error want []byte }{ { name: "RangeNegative", buf: []byte{0x01, 0x02}, n: -1, wantErr: errTruncateRange, want: []byte{0x01, 0x02}, }, { name: "RangePositive", buf: []byte{0x01, 0x02}, n: 3, wantErr: errTruncateRange, want: []byte{0x01, 0x02}, }, { name: "Zero", buf: []byte{0x01, 0x02}, n: 0, want: []byte{}, }, { name: "One", buf: []byte{0x01, 0x02}, n: 1, want: []byte{0x01}, }, { name: "Two", buf: []byte{0x01, 0x02}, n: 2, want: []byte{0x01, 0x02}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { b := &Buffer{ buf: tt.buf, } err := b.Truncate(tt.n) if got, want := err, tt.wantErr; !errors.Is(got, want) { t.Fatalf("got error %v, want %v", got, want) } if got, want := b.buf, tt.want; !bytes.Equal(got, want) { t.Errorf("got buffer %v, want %v", got, want) } }) } } func TestBuffer_Bytes(t *testing.T) { tests := []struct { name string buf []byte want []byte }{ { name: "Nil", buf: nil, want: nil, }, { name: "Empty", buf: []byte{}, want: []byte{}, }, { name: "One", buf: []byte{0x01}, want: []byte{0x01}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { b := &Buffer{ buf: tt.buf, } if got, want := b.Bytes(), tt.want; !bytes.Equal(got, want) { t.Errorf("got bytes %v, want %v", got, want) } }) } } func TestBuffer_Len(t *testing.T) { tests := []struct { name string buf []byte want int64 }{ { name: "Nil", buf: nil, want: 0, }, { name: "Empty", buf: []byte{}, want: 0, }, { name: "One", buf: []byte{0x01}, want: 1, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { b := &Buffer{ buf: tt.buf, } if got, want := b.Len(), tt.want; got != want { t.Errorf("got length %v, want %v", got, want) } }) } } sif-2.8.3/pkg/sif/create.go000066400000000000000000000413471432675271000154610ustar00rootroot00000000000000// Copyright (c) 2018-2022, Sylabs Inc. All rights reserved. // Copyright (c) 2017, SingularityWare, LLC. All rights reserved. // Copyright (c) 2017, Yannick Cote All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package sif import ( "encoding/binary" "errors" "fmt" "io" "os" "time" "github.com/google/uuid" ) // nextAligned finds the next offset that satisfies alignment. func nextAligned(offset int64, alignment int) int64 { align64 := uint64(alignment) offset64 := uint64(offset) if align64 != 0 && offset64%align64 != 0 { offset64 = (offset64 & ^(align64 - 1)) + align64 } return int64(offset64) } // writeDataObjectAt writes the data object described by di to ws, using time t, recording details // in d. The object is written at the first position that satisfies the alignment requirements // described by di following offsetUnaligned. func writeDataObjectAt(ws io.WriteSeeker, offsetUnaligned int64, di DescriptorInput, t time.Time, d *rawDescriptor) error { //nolint:lll offset, err := ws.Seek(nextAligned(offsetUnaligned, di.opts.alignment), io.SeekStart) if err != nil { return err } n, err := io.Copy(ws, di.r) if err != nil { return err } if err := di.fillDescriptor(t, d); err != nil { return err } d.Used = true d.Offset = offset d.Size = n d.SizeWithPadding = offset - offsetUnaligned + n return nil } var ( errInsufficientCapacity = errors.New("insufficient descriptor capacity to add data object(s) to image") errPrimaryPartition = errors.New("image already contains a primary partition") ) // writeDataObject writes the data object described by di to f, using time t, recording details in // the descriptor at index i. func (f *FileImage) writeDataObject(i int, di DescriptorInput, t time.Time) error { if i >= len(f.rds) { return errInsufficientCapacity } // If this is a primary partition, verify there isn't another primary partition, and update the // architecture in the global header. if p, ok := di.opts.extra.(partition); ok && p.Parttype == PartPrimSys { if ds, err := f.GetDescriptors(WithPartitionType(PartPrimSys)); err == nil && len(ds) > 0 { return errPrimaryPartition } f.h.Arch = p.Arch } d := &f.rds[i] d.ID = uint32(i) + 1 if err := writeDataObjectAt(f.rw, f.h.DataOffset+f.h.DataSize, di, t, d); err != nil { return err } // Update minimum object ID map. if minID, ok := f.minIDs[d.GroupID]; !ok || d.ID < minID { f.minIDs[d.GroupID] = d.ID } f.h.DescriptorsFree-- f.h.DataSize += d.SizeWithPadding return nil } // writeDescriptors writes the descriptors in f to backing storage. func (f *FileImage) writeDescriptors() error { if _, err := f.rw.Seek(f.h.DescriptorsOffset, io.SeekStart); err != nil { return err } return binary.Write(f.rw, binary.LittleEndian, f.rds) } // writeHeader writes the global header in f to backing storage. func (f *FileImage) writeHeader() error { if _, err := f.rw.Seek(0, io.SeekStart); err != nil { return err } return binary.Write(f.rw, binary.LittleEndian, f.h) } // createOpts accumulates container creation options. type createOpts struct { launchScript [hdrLaunchLen]byte id uuid.UUID descriptorsOffset int64 descriptorCapacity int64 dis []DescriptorInput t time.Time closeOnUnload bool } // CreateOpt are used to specify container creation options. type CreateOpt func(*createOpts) error var errLaunchScriptLen = errors.New("launch script too large") // OptCreateWithLaunchScript specifies s as the launch script. func OptCreateWithLaunchScript(s string) CreateOpt { return func(co *createOpts) error { b := []byte(s) if len(b) >= len(co.launchScript) { return errLaunchScriptLen } copy(co.launchScript[:], b) return nil } } // OptCreateDeterministic sets header/descriptor fields to values that support deterministic // creation of images. func OptCreateDeterministic() CreateOpt { return func(co *createOpts) error { co.id = uuid.Nil co.t = time.Time{} return nil } } // OptCreateWithID specifies id as the unique ID. func OptCreateWithID(id string) CreateOpt { return func(co *createOpts) error { id, err := uuid.Parse(id) co.id = id return err } } // OptCreateWithDescriptorCapacity specifies that the created image should have the capacity for a // maximum of n descriptors. func OptCreateWithDescriptorCapacity(n int64) CreateOpt { return func(co *createOpts) error { co.descriptorCapacity = n return nil } } // OptCreateWithDescriptors appends dis to the list of descriptors. func OptCreateWithDescriptors(dis ...DescriptorInput) CreateOpt { return func(co *createOpts) error { co.dis = append(co.dis, dis...) return nil } } // OptCreateWithTime specifies t as the image creation time. func OptCreateWithTime(t time.Time) CreateOpt { return func(co *createOpts) error { co.t = t return nil } } // OptCreateWithCloseOnUnload specifies whether the ReadWriter should be closed by UnloadContainer. // By default, the ReadWriter will be closed if it implements the io.Closer interface. func OptCreateWithCloseOnUnload(b bool) CreateOpt { return func(co *createOpts) error { co.closeOnUnload = b return nil } } // createContainer creates a new SIF container file in rw, according to opts. func createContainer(rw ReadWriter, co createOpts) (*FileImage, error) { rds := make([]rawDescriptor, co.descriptorCapacity) rdsSize := int64(binary.Size(rds)) h := header{ LaunchScript: co.launchScript, Magic: hdrMagic, Version: CurrentVersion.bytes(), Arch: hdrArchUnknown, ID: co.id, CreatedAt: co.t.Unix(), ModifiedAt: co.t.Unix(), DescriptorsFree: co.descriptorCapacity, DescriptorsTotal: co.descriptorCapacity, DescriptorsOffset: co.descriptorsOffset, DescriptorsSize: rdsSize, DataOffset: co.descriptorsOffset + rdsSize, } f := &FileImage{ rw: rw, h: h, rds: rds, minIDs: make(map[uint32]uint32), } for i, di := range co.dis { if err := f.writeDataObject(i, di, co.t); err != nil { return nil, err } } if err := f.writeDescriptors(); err != nil { return nil, err } if err := f.writeHeader(); err != nil { return nil, err } return f, nil } // CreateContainer creates a new SIF container in rw, according to opts. One or more data objects // can optionally be specified using OptCreateWithDescriptors. // // On success, a FileImage is returned. The caller must call UnloadContainer to ensure resources // are released. By default, UnloadContainer will close rw if it implements the io.Closer // interface. To change this behavior, consider using OptCreateWithCloseOnUnload. // // By default, the image ID is set to a randomly generated value. To override this, consider using // OptCreateDeterministic or OptCreateWithID. // // By default, the image creation time is set to time.Now(). To override this, consider using // OptCreateDeterministic or OptCreateWithTime. // // By default, the image will support a maximum of 48 descriptors. To change this, consider using // OptCreateWithDescriptorCapacity. // // A launch script can optionally be set using OptCreateWithLaunchScript. func CreateContainer(rw ReadWriter, opts ...CreateOpt) (*FileImage, error) { id, err := uuid.NewRandom() if err != nil { return nil, err } co := createOpts{ id: id, descriptorsOffset: 4096, descriptorCapacity: 48, t: time.Now(), closeOnUnload: true, } for _, opt := range opts { if err := opt(&co); err != nil { return nil, fmt.Errorf("%w", err) } } f, err := createContainer(rw, co) if err != nil { return nil, fmt.Errorf("%w", err) } f.closeOnUnload = co.closeOnUnload return f, nil } // CreateContainerAtPath creates a new SIF container file at path, according to opts. One or more // data objects can optionally be specified using OptCreateWithDescriptors. // // On success, a FileImage is returned. The caller must call UnloadContainer to ensure resources // are released. // // By default, the image ID is set to a randomly generated value. To override this, consider using // OptCreateDeterministic or OptCreateWithID. // // By default, the image creation time is set to time.Now(). To override this, consider using // OptCreateDeterministic or OptCreateWithTime. // // By default, the image will support a maximum of 48 descriptors. To change this, consider using // OptCreateWithDescriptorCapacity. // // A launch script can optionally be set using OptCreateWithLaunchScript. func CreateContainerAtPath(path string, opts ...CreateOpt) (*FileImage, error) { fp, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o755) if err != nil { return nil, fmt.Errorf("%w", err) } f, err := CreateContainer(fp, opts...) if err != nil { fp.Close() os.Remove(fp.Name()) return nil, err } f.closeOnUnload = true return f, nil } func zeroData(fimg *FileImage, descr *rawDescriptor) error { // first, move to data object offset if _, err := fimg.rw.Seek(descr.Offset, io.SeekStart); err != nil { return err } var zero [4096]byte n := descr.Size upbound := int64(4096) for { if n < 4096 { upbound = n } if _, err := fimg.rw.Write(zero[:upbound]); err != nil { return err } n -= 4096 if n <= 0 { break } } return nil } func resetDescriptor(fimg *FileImage, index int) error { // If we remove the primary partition, set the global header Arch field to HdrArchUnknown // to indicate that the SIF file doesn't include a primary partition and no dependency // on any architecture exists. if fimg.rds[index].isPartitionOfType(PartPrimSys) { fimg.h.Arch = hdrArchUnknown } offset := fimg.h.DescriptorsOffset + int64(index)*int64(binary.Size(fimg.rds[0])) // first, move to descriptor offset if _, err := fimg.rw.Seek(offset, io.SeekStart); err != nil { return err } var emptyDesc rawDescriptor return binary.Write(fimg.rw, binary.LittleEndian, emptyDesc) } // addOpts accumulates object add options. type addOpts struct { t time.Time } // AddOpt are used to specify object add options. type AddOpt func(*addOpts) error // OptAddDeterministic sets header/descriptor fields to values that support deterministic // modification of images. func OptAddDeterministic() AddOpt { return func(ao *addOpts) error { ao.t = time.Time{} return nil } } // OptAddWithTime specifies t as the image modification time. func OptAddWithTime(t time.Time) AddOpt { return func(ao *addOpts) error { ao.t = t return nil } } // AddObject adds a new data object and its descriptor into the specified SIF file. // // By default, the image modification time is set to the current time. To override this, consider // using OptAddDeterministic or OptAddWithTime. func (f *FileImage) AddObject(di DescriptorInput, opts ...AddOpt) error { ao := addOpts{ t: time.Now(), } for _, opt := range opts { if err := opt(&ao); err != nil { return fmt.Errorf("%w", err) } } // Find an unused descriptor. i := 0 for _, rd := range f.rds { if !rd.Used { break } i++ } if err := f.writeDataObject(i, di, ao.t); err != nil { return fmt.Errorf("%w", err) } if err := f.writeDescriptors(); err != nil { return fmt.Errorf("%w", err) } f.h.ModifiedAt = ao.t.Unix() if err := f.writeHeader(); err != nil { return fmt.Errorf("%w", err) } return nil } // isLast return true if the data object associated with d is the last in f. func (f *FileImage) isLast(d *rawDescriptor) bool { isLast := true end := d.Offset + d.Size f.WithDescriptors(func(d Descriptor) bool { isLast = d.Offset()+d.Size() <= end return !isLast }) return isLast } // truncateAt truncates f at the start of the padded data object described by d. func (f *FileImage) truncateAt(d *rawDescriptor) error { start := d.Offset + d.Size - d.SizeWithPadding if err := f.rw.Truncate(start); err != nil { return err } return nil } // deleteOpts accumulates object deletion options. type deleteOpts struct { zero bool compact bool t time.Time } // DeleteOpt are used to specify object deletion options. type DeleteOpt func(*deleteOpts) error // OptDeleteZero specifies whether the deleted object should be zeroed. func OptDeleteZero(b bool) DeleteOpt { return func(do *deleteOpts) error { do.zero = b return nil } } // OptDeleteCompact specifies whether the image should be compacted following object deletion. func OptDeleteCompact(b bool) DeleteOpt { return func(do *deleteOpts) error { do.compact = b return nil } } // OptDeleteDeterministic sets header/descriptor fields to values that support deterministic // modification of images. func OptDeleteDeterministic() DeleteOpt { return func(do *deleteOpts) error { do.t = time.Time{} return nil } } // OptDeleteWithTime specifies t as the image modification time. func OptDeleteWithTime(t time.Time) DeleteOpt { return func(do *deleteOpts) error { do.t = t return nil } } var errCompactNotImplemented = errors.New("compact not implemented for non-last object") // DeleteObject deletes the data object with id, according to opts. // // To zero the data region of the deleted object, use OptDeleteZero. To compact the file following // object deletion, use OptDeleteCompact. // // By default, the image modification time is set to time.Now(). To override this, consider using // OptDeleteDeterministic or OptDeleteWithTime. func (f *FileImage) DeleteObject(id uint32, opts ...DeleteOpt) error { do := deleteOpts{ t: time.Now(), } for _, opt := range opts { if err := opt(&do); err != nil { return fmt.Errorf("%w", err) } } d, err := f.getDescriptor(WithID(id)) if err != nil { return fmt.Errorf("%w", err) } if do.compact && !f.isLast(d) { return fmt.Errorf("%w", errCompactNotImplemented) } if do.zero { if err := zeroData(f, d); err != nil { return fmt.Errorf("%w", err) } } if do.compact { if err := f.truncateAt(d); err != nil { return fmt.Errorf("%w", err) } f.h.DataSize -= d.SizeWithPadding } f.h.DescriptorsFree++ f.h.ModifiedAt = do.t.Unix() index := 0 for i, od := range f.rds { if od.ID == id { index = i break } } if err := resetDescriptor(f, index); err != nil { return fmt.Errorf("%w", err) } if err := f.writeHeader(); err != nil { return fmt.Errorf("%w", err) } return nil } // setOpts accumulates object set options. type setOpts struct { t time.Time } // SetOpt are used to specify object set options. type SetOpt func(*setOpts) error // OptSetDeterministic sets header/descriptor fields to values that support deterministic // modification of images. func OptSetDeterministic() SetOpt { return func(so *setOpts) error { so.t = time.Time{} return nil } } // OptSetWithTime specifies t as the image/object modification time. func OptSetWithTime(t time.Time) SetOpt { return func(so *setOpts) error { so.t = t return nil } } var ( errNotPartition = errors.New("data object not a partition") errNotSystem = errors.New("data object not a system partition") ) // SetPrimPart sets the specified system partition to be the primary one. // // By default, the image/object modification times are set to time.Now(). To override this, // consider using OptSetDeterministic or OptSetWithTime. func (f *FileImage) SetPrimPart(id uint32, opts ...SetOpt) error { so := setOpts{ t: time.Now(), } for _, opt := range opts { if err := opt(&so); err != nil { return fmt.Errorf("%w", err) } } descr, err := f.getDescriptor(WithID(id)) if err != nil { return fmt.Errorf("%w", err) } if descr.DataType != DataPartition { return fmt.Errorf("%w", errNotPartition) } fs, pt, arch, err := descr.getPartitionMetadata() if err != nil { return fmt.Errorf("%w", err) } // if already primary system partition, nothing to do if pt == PartPrimSys { return nil } if pt != PartSystem { return fmt.Errorf("%w", errNotSystem) } olddescr, err := f.getDescriptor(WithPartitionType(PartPrimSys)) if err != nil && !errors.Is(err, ErrObjectNotFound) { return fmt.Errorf("%w", err) } f.h.Arch = getSIFArch(arch) extra := partition{ Fstype: fs, Parttype: PartPrimSys, } copy(extra.Arch[:], arch) if err := descr.setExtra(extra); err != nil { return fmt.Errorf("%w", err) } if olddescr != nil { oldfs, _, oldarch, err := olddescr.getPartitionMetadata() if err != nil { return fmt.Errorf("%w", err) } oldextra := partition{ Fstype: oldfs, Parttype: PartSystem, Arch: getSIFArch(oldarch), } if err := olddescr.setExtra(oldextra); err != nil { return fmt.Errorf("%w", err) } } if err := f.writeDescriptors(); err != nil { return fmt.Errorf("%w", err) } f.h.ModifiedAt = so.t.Unix() if err := f.writeHeader(); err != nil { return fmt.Errorf("%w", err) } return nil } sif-2.8.3/pkg/sif/create_test.go000066400000000000000000000343141432675271000165140ustar00rootroot00000000000000// Copyright (c) 2018-2021, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package sif import ( "errors" "os" "testing" "time" "github.com/sebdah/goldie/v2" ) func TestNextAligned(t *testing.T) { cases := []struct { name string offset int64 align int expected int64 }{ {name: "align 0 to 0", offset: 0, align: 0, expected: 0}, {name: "align 1 to 0", offset: 1, align: 0, expected: 1}, {name: "align 0 to 1024", offset: 0, align: 1024, expected: 0}, {name: "align 1 to 1024", offset: 1, align: 1024, expected: 1024}, {name: "align 1023 to 1024", offset: 1023, align: 1024, expected: 1024}, {name: "align 1024 to 1024", offset: 1024, align: 1024, expected: 1024}, {name: "align 1025 to 1024", offset: 1025, align: 1024, expected: 2048}, } for _, tc := range cases { actual := nextAligned(tc.offset, tc.align) if actual != tc.expected { t.Errorf("nextAligned case: %q, offset: %d, align: %d, expecting: %d, actual: %d\n", tc.name, tc.offset, tc.align, tc.expected, actual) } } } func TestCreateContainer(t *testing.T) { tests := []struct { name string opts []CreateOpt wantErr error }{ { name: "ErrInsufficientCapacity", opts: []CreateOpt{ OptCreateDeterministic(), OptCreateWithDescriptorCapacity(0), OptCreateWithDescriptors( getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), ), }, wantErr: errInsufficientCapacity, }, { name: "Empty", opts: []CreateOpt{ OptCreateDeterministic(), }, }, { name: "EmptyLaunchScript", opts: []CreateOpt{ OptCreateDeterministic(), OptCreateWithLaunchScript("#!/usr/bin/env launch-script\n"), }, }, { name: "EmptyWithID", opts: []CreateOpt{ OptCreateDeterministic(), OptCreateWithID("de170c43-36ab-44a8-bca9-1ea1a070a274"), }, }, { name: "EmptyWithTime", opts: []CreateOpt{ OptCreateDeterministic(), OptCreateWithTime(time.Unix(946702800, 0)), }, }, { name: "EmptyCloseOnUnload", opts: []CreateOpt{ OptCreateDeterministic(), OptCreateWithCloseOnUnload(true), }, }, { name: "EmptyDescriptorLimitedCapacity", opts: []CreateOpt{ OptCreateDeterministic(), OptCreateWithDescriptorCapacity(1), }, }, { name: "OneDescriptor", opts: []CreateOpt{ OptCreateDeterministic(), OptCreateWithDescriptors( getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), ), }, }, { name: "TwoDescriptors", opts: []CreateOpt{ OptCreateDeterministic(), OptCreateWithDescriptors( getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), getDescriptorInput(t, DataPartition, []byte{0xfe, 0xed}, OptPartitionMetadata(FsSquash, PartPrimSys, "386"), ), ), }, }, { name: "TwoDescriptorsNotAligned", opts: []CreateOpt{ OptCreateDeterministic(), OptCreateWithDescriptors( getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}, OptObjectAlignment(0), ), getDescriptorInput(t, DataPartition, []byte{0xfe, 0xed}, OptPartitionMetadata(FsSquash, PartPrimSys, "386"), OptObjectAlignment(0), ), ), }, }, { name: "TwoDescriptorsAligned", opts: []CreateOpt{ OptCreateDeterministic(), OptCreateWithDescriptors( getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}, OptObjectAlignment(4), ), getDescriptorInput(t, DataPartition, []byte{0xfe, 0xed}, OptPartitionMetadata(FsSquash, PartPrimSys, "386"), OptObjectAlignment(4), ), ), }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var b Buffer f, err := CreateContainer(&b, tt.opts...) if got, want := err, tt.wantErr; !errors.Is(got, want) { t.Fatalf("got error %v, want %v", got, want) } if err == nil { if err := f.UnloadContainer(); err != nil { t.Error(err) } g := goldie.New(t, goldie.WithTestNameForDir(true)) g.Assert(t, tt.name, b.Bytes()) } }) } } func TestCreateContainerAtPath(t *testing.T) { tests := []struct { name string opts []CreateOpt wantErr error }{ { name: "ErrInsufficientCapacity", opts: []CreateOpt{ OptCreateDeterministic(), OptCreateWithDescriptorCapacity(0), OptCreateWithDescriptors( getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), ), }, wantErr: errInsufficientCapacity, }, { name: "Empty", opts: []CreateOpt{ OptCreateDeterministic(), }, }, { name: "EmptyDescriptorLimitedCapacity", opts: []CreateOpt{ OptCreateDeterministic(), OptCreateWithDescriptorCapacity(1), }, }, { name: "OneDescriptor", opts: []CreateOpt{ OptCreateDeterministic(), OptCreateWithDescriptors( getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), ), }, }, { name: "TwoDescriptors", opts: []CreateOpt{ OptCreateDeterministic(), OptCreateWithDescriptors( getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), getDescriptorInput(t, DataPartition, []byte{0xfe, 0xed}, OptPartitionMetadata(FsSquash, PartPrimSys, "386"), ), ), }, }, { name: "TwoDescriptorsNotAligned", opts: []CreateOpt{ OptCreateDeterministic(), OptCreateWithDescriptors( getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}, OptObjectAlignment(0), ), getDescriptorInput(t, DataPartition, []byte{0xfe, 0xed}, OptPartitionMetadata(FsSquash, PartPrimSys, "386"), OptObjectAlignment(0), ), ), }, }, { name: "TwoDescriptorsAligned", opts: []CreateOpt{ OptCreateDeterministic(), OptCreateWithDescriptors( getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}, OptObjectAlignment(4), ), getDescriptorInput(t, DataPartition, []byte{0xfe, 0xed}, OptPartitionMetadata(FsSquash, PartPrimSys, "386"), OptObjectAlignment(4), ), ), }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tf, err := os.CreateTemp("", "sif-test-*") if err != nil { t.Fatal(err) } defer os.Remove(tf.Name()) tf.Close() f, err := CreateContainerAtPath(tf.Name(), tt.opts...) if got, want := err, tt.wantErr; !errors.Is(got, want) { t.Fatalf("got error %v, want %v", got, want) } if err == nil { if err := f.UnloadContainer(); err != nil { t.Error(err) } b, err := os.ReadFile(tf.Name()) if err != nil { t.Fatal(err) } g := goldie.New(t, goldie.WithTestNameForDir(true)) g.Assert(t, tt.name, b) } }) } } func TestAddObject(t *testing.T) { tests := []struct { name string createOpts []CreateOpt di DescriptorInput opts []AddOpt wantErr error }{ { name: "ErrInsufficientCapacity", createOpts: []CreateOpt{ OptCreateDeterministic(), OptCreateWithDescriptorCapacity(0), }, di: getDescriptorInput(t, DataGeneric, []byte{0xfe, 0xed}), wantErr: errInsufficientCapacity, }, { name: "ErrPrimaryPartition", createOpts: []CreateOpt{ OptCreateDeterministic(), OptCreateWithDescriptors( getDescriptorInput(t, DataPartition, []byte{0xfa, 0xce}, OptPartitionMetadata(FsSquash, PartPrimSys, "386"), ), ), }, di: getDescriptorInput(t, DataPartition, []byte{0xfe, 0xed}, OptPartitionMetadata(FsSquash, PartPrimSys, "amd64"), ), wantErr: errPrimaryPartition, }, { name: "WithTime", createOpts: []CreateOpt{ OptCreateDeterministic(), }, di: getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), opts: []AddOpt{ OptAddDeterministic(), OptAddWithTime(time.Unix(946702800, 0)), }, }, { name: "Empty", createOpts: []CreateOpt{ OptCreateDeterministic(), }, di: getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), opts: []AddOpt{ OptAddDeterministic(), }, }, { name: "EmptyNotAligned", createOpts: []CreateOpt{ OptCreateDeterministic(), }, di: getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}, OptObjectAlignment(0), ), opts: []AddOpt{ OptAddDeterministic(), }, }, { name: "EmptyAligned", createOpts: []CreateOpt{ OptCreateDeterministic(), }, di: getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}, OptObjectAlignment(128), ), opts: []AddOpt{ OptAddDeterministic(), }, }, { name: "NotEmpty", createOpts: []CreateOpt{ OptCreateDeterministic(), OptCreateWithDescriptors( getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), ), }, di: getDescriptorInput(t, DataPartition, []byte{0xfe, 0xed}, OptPartitionMetadata(FsSquash, PartPrimSys, "386"), ), opts: []AddOpt{ OptAddDeterministic(), }, }, { name: "NotEmptyNotAligned", createOpts: []CreateOpt{ OptCreateDeterministic(), OptCreateWithDescriptors( getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), ), }, di: getDescriptorInput(t, DataPartition, []byte{0xfe, 0xed}, OptPartitionMetadata(FsSquash, PartPrimSys, "386"), OptObjectAlignment(0), ), opts: []AddOpt{ OptAddDeterministic(), }, }, { name: "NotEmptyAligned", createOpts: []CreateOpt{ OptCreateDeterministic(), OptCreateWithDescriptors( getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), ), }, di: getDescriptorInput(t, DataPartition, []byte{0xfe, 0xed}, OptPartitionMetadata(FsSquash, PartPrimSys, "386"), OptObjectAlignment(128), ), opts: []AddOpt{ OptAddDeterministic(), }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var b Buffer f, err := CreateContainer(&b, tt.createOpts...) if err != nil { t.Fatal(err) } if got, want := f.AddObject(tt.di, tt.opts...), tt.wantErr; !errors.Is(got, want) { t.Errorf("got error %v, want %v", got, want) } if err := f.UnloadContainer(); err != nil { t.Error(err) } g := goldie.New(t, goldie.WithTestNameForDir(true)) g.Assert(t, tt.name, b.Bytes()) }) } } func TestDeleteObject(t *testing.T) { tests := []struct { name string createOpts []CreateOpt id uint32 opts []DeleteOpt wantErr error }{ { name: "ErrObjectNotFound", createOpts: []CreateOpt{ OptCreateDeterministic(), }, id: 1, wantErr: ErrObjectNotFound, }, { name: "Zero", createOpts: []CreateOpt{ OptCreateDeterministic(), OptCreateWithDescriptors( getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), ), }, id: 1, opts: []DeleteOpt{ OptDeleteDeterministic(), OptDeleteZero(true), }, }, { name: "Compact", createOpts: []CreateOpt{ OptCreateDeterministic(), OptCreateWithDescriptors( getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), ), }, id: 1, opts: []DeleteOpt{ OptDeleteDeterministic(), OptDeleteCompact(true), }, }, { name: "ZeroCompact", createOpts: []CreateOpt{ OptCreateDeterministic(), OptCreateWithDescriptors( getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), ), }, id: 1, opts: []DeleteOpt{ OptDeleteDeterministic(), OptDeleteZero(true), OptDeleteCompact(true), }, }, { name: "WithTime", createOpts: []CreateOpt{ OptCreateDeterministic(), OptCreateWithDescriptors( getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), ), }, id: 1, opts: []DeleteOpt{ OptDeleteDeterministic(), OptDeleteWithTime(time.Unix(946702800, 0)), }, }, { name: "PrimaryPartition", createOpts: []CreateOpt{ OptCreateDeterministic(), OptCreateWithDescriptors( getDescriptorInput(t, DataPartition, []byte{0xfa, 0xce}, OptPartitionMetadata(FsSquash, PartPrimSys, "386"), ), ), }, id: 1, opts: []DeleteOpt{ OptDeleteDeterministic(), }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var b Buffer f, err := CreateContainer(&b, tt.createOpts...) if err != nil { t.Fatal(err) } if got, want := f.DeleteObject(tt.id, tt.opts...), tt.wantErr; !errors.Is(got, want) { t.Errorf("got error %v, want %v", got, want) } if err := f.UnloadContainer(); err != nil { t.Error(err) } g := goldie.New(t, goldie.WithTestNameForDir(true)) g.Assert(t, tt.name, b.Bytes()) }) } } func TestSetPrimPart(t *testing.T) { tests := []struct { name string createOpts []CreateOpt id uint32 opts []SetOpt wantErr error }{ { name: "ErrObjectNotFound", createOpts: []CreateOpt{ OptCreateDeterministic(), }, id: 1, wantErr: ErrObjectNotFound, }, { name: "WithTime", createOpts: []CreateOpt{ OptCreateDeterministic(), OptCreateWithDescriptors( getDescriptorInput(t, DataPartition, []byte{0xfa, 0xce}, OptPartitionMetadata(FsRaw, PartSystem, "386"), ), ), }, id: 1, opts: []SetOpt{ OptSetDeterministic(), OptSetWithTime(time.Unix(946702800, 0)), }, }, { name: "One", createOpts: []CreateOpt{ OptCreateDeterministic(), OptCreateWithDescriptors( getDescriptorInput(t, DataPartition, []byte{0xfa, 0xce}, OptPartitionMetadata(FsRaw, PartSystem, "386"), ), ), }, id: 1, opts: []SetOpt{ OptSetDeterministic(), }, }, { name: "Two", createOpts: []CreateOpt{ OptCreateDeterministic(), OptCreateWithDescriptors( getDescriptorInput(t, DataPartition, []byte{0xfa, 0xce}, OptPartitionMetadata(FsRaw, PartPrimSys, "386"), ), getDescriptorInput(t, DataPartition, []byte{0xfe, 0xed}, OptPartitionMetadata(FsRaw, PartSystem, "amd64"), ), ), }, id: 2, opts: []SetOpt{ OptSetDeterministic(), }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var b Buffer f, err := CreateContainer(&b, tt.createOpts...) if err != nil { t.Fatal(err) } if got, want := f.SetPrimPart(tt.id, tt.opts...), tt.wantErr; !errors.Is(got, want) { t.Errorf("got error %v, want %v", got, want) } if err := f.UnloadContainer(); err != nil { t.Error(err) } g := goldie.New(t, goldie.WithTestNameForDir(true)) g.Assert(t, tt.name, b.Bytes()) }) } } sif-2.8.3/pkg/sif/descriptor.go000066400000000000000000000202011432675271000163560ustar00rootroot00000000000000// Copyright (c) 2018-2022, Sylabs Inc. All rights reserved. // Copyright (c) 2017, SingularityWare, LLC. All rights reserved. // Copyright (c) 2017, Yannick Cote All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package sif import ( "bytes" "crypto" "encoding/binary" "errors" "fmt" "io" "strings" "time" ) // rawDescriptor represents an on-disk object descriptor. type rawDescriptor struct { DataType DataType Used bool ID uint32 GroupID uint32 LinkedID uint32 Offset int64 Size int64 SizeWithPadding int64 CreatedAt int64 ModifiedAt int64 UID int64 // Deprecated: UID exists for historical compatibility and should not be used. GID int64 // Deprecated: GID exists for historical compatibility and should not be used. Name [descrNameLen]byte Extra [descrMaxPrivLen]byte } // partition represents the SIF partition data object descriptor. type partition struct { Fstype FSType Parttype PartType Arch archType } // signature represents the SIF signature data object descriptor. type signature struct { Hashtype hashType Entity [descrEntityLen]byte } // cryptoMessage represents the SIF crypto message object descriptor. type cryptoMessage struct { Formattype FormatType Messagetype MessageType } // sbom represents the SIF SBOM data object descriptor. type sbom struct { Format SBOMFormat } var errNameTooLarge = errors.New("name value too large") // setName encodes name into the name field of d. func (d *rawDescriptor) setName(name string) error { if len(name) > len(d.Name) { return errNameTooLarge } for i := copy(d.Name[:], name); i < len(d.Name); i++ { d.Name[i] = 0 } return nil } var errExtraTooLarge = errors.New("extra value too large") // setExtra encodes v into the extra field of d. func (d *rawDescriptor) setExtra(v interface{}) error { if v == nil { return nil } if binary.Size(v) > len(d.Extra) { return errExtraTooLarge } b := new(bytes.Buffer) if err := binary.Write(b, binary.LittleEndian, v); err != nil { return err } for i := copy(d.Extra[:], b.Bytes()); i < len(d.Extra); i++ { d.Extra[i] = 0 } return nil } // getPartitionMetadata gets metadata for a partition data object. func (d rawDescriptor) getPartitionMetadata() (FSType, PartType, string, error) { if got, want := d.DataType, DataPartition; got != want { return 0, 0, "", &unexpectedDataTypeError{got, []DataType{want}} } var p partition b := bytes.NewReader(d.Extra[:]) if err := binary.Read(b, binary.LittleEndian, &p); err != nil { return 0, 0, "", fmt.Errorf("%w", err) } return p.Fstype, p.Parttype, p.Arch.GoArch(), nil } // isPartitionOfType returns true if d is a partition data object of type pt. func (d rawDescriptor) isPartitionOfType(pt PartType) bool { _, t, _, err := d.getPartitionMetadata() if err != nil { return false } return t == pt } // Descriptor represents the SIF descriptor type. type Descriptor struct { r io.ReaderAt // Backing storage. raw rawDescriptor // Raw descriptor from image. relativeID uint32 // ID relative to minimum ID of object group. } // DataType returns the type of data object. func (d Descriptor) DataType() DataType { return d.raw.DataType } // ID returns the data object ID of d. func (d Descriptor) ID() uint32 { return d.raw.ID } // GroupID returns the data object group ID of d, or zero if d is not part of a data object // group. func (d Descriptor) GroupID() uint32 { return d.raw.GroupID &^ descrGroupMask } // LinkedID returns the object/group ID d is linked to, or zero if d does not contain a linked // ID. If isGroup is true, the returned id is an object group ID. Otherwise, the returned id is a // data object ID. // //nolint:nonamedreturns // Named returns effective as documentation. func (d Descriptor) LinkedID() (id uint32, isGroup bool) { return d.raw.LinkedID &^ descrGroupMask, d.raw.LinkedID&descrGroupMask == descrGroupMask } // Offset returns the offset of the data object. func (d Descriptor) Offset() int64 { return d.raw.Offset } // Size returns the data object size. func (d Descriptor) Size() int64 { return d.raw.Size } // CreatedAt returns the creation time of the data object. func (d Descriptor) CreatedAt() time.Time { return time.Unix(d.raw.CreatedAt, 0) } // ModifiedAt returns the modification time of the data object. func (d Descriptor) ModifiedAt() time.Time { return time.Unix(d.raw.ModifiedAt, 0) } // Name returns the name of the data object. func (d Descriptor) Name() string { return strings.TrimRight(string(d.raw.Name[:]), "\000") } // PartitionMetadata gets metadata for a partition data object. // //nolint:nonamedreturns // Named returns effective as documentation. func (d Descriptor) PartitionMetadata() (fs FSType, pt PartType, arch string, err error) { return d.raw.getPartitionMetadata() } var errHashUnsupported = errors.New("hash algorithm unsupported") // getHashType converts ht into a crypto.Hash. func getHashType(ht hashType) (crypto.Hash, error) { switch ht { case hashSHA256: return crypto.SHA256, nil case hashSHA384: return crypto.SHA384, nil case hashSHA512: return crypto.SHA512, nil case hashBLAKE2S: return crypto.BLAKE2s_256, nil case hashBLAKE2B: return crypto.BLAKE2b_256, nil } return 0, errHashUnsupported } // SignatureMetadata gets metadata for a signature data object. // //nolint:nonamedreturns // Named returns effective as documentation. func (d Descriptor) SignatureMetadata() (ht crypto.Hash, fp []byte, err error) { if got, want := d.raw.DataType, DataSignature; got != want { return ht, fp, &unexpectedDataTypeError{got, []DataType{want}} } var s signature b := bytes.NewReader(d.raw.Extra[:]) if err := binary.Read(b, binary.LittleEndian, &s); err != nil { return ht, fp, fmt.Errorf("%w", err) } if ht, err = getHashType(s.Hashtype); err != nil { return ht, fp, fmt.Errorf("%w", err) } fp = make([]byte, 20) if bytes.Equal(s.Entity[:len(fp)], fp) { return ht, nil, nil // Fingerprint not present. } copy(fp, s.Entity[:]) return ht, fp, nil } // CryptoMessageMetadata gets metadata for a crypto message data object. func (d Descriptor) CryptoMessageMetadata() (FormatType, MessageType, error) { if got, want := d.raw.DataType, DataCryptoMessage; got != want { return 0, 0, &unexpectedDataTypeError{got, []DataType{want}} } var m cryptoMessage b := bytes.NewReader(d.raw.Extra[:]) if err := binary.Read(b, binary.LittleEndian, &m); err != nil { return 0, 0, fmt.Errorf("%w", err) } return m.Formattype, m.Messagetype, nil } // SBOMMetadata gets metadata for a SBOM data object. func (d Descriptor) SBOMMetadata() (SBOMFormat, error) { if got, want := d.raw.DataType, DataSBOM; got != want { return 0, &unexpectedDataTypeError{got, []DataType{want}} } var s sbom b := bytes.NewReader(d.raw.Extra[:]) if err := binary.Read(b, binary.LittleEndian, &s); err != nil { return 0, fmt.Errorf("%w", err) } return s.Format, nil } // GetData returns the data object associated with descriptor d. func (d Descriptor) GetData() ([]byte, error) { b := make([]byte, d.raw.Size) if _, err := io.ReadFull(d.GetReader(), b); err != nil { return nil, err } return b, nil } // GetReader returns a io.Reader that reads the data object associated with descriptor d. func (d Descriptor) GetReader() io.Reader { return io.NewSectionReader(d.r, d.raw.Offset, d.raw.Size) } // GetIntegrityReader returns an io.Reader that reads the integrity-protected fields from d. func (d Descriptor) GetIntegrityReader() io.Reader { fields := []interface{}{ d.raw.DataType, d.raw.Used, d.relativeID, d.raw.LinkedID, d.raw.Size, d.raw.CreatedAt, d.raw.UID, d.raw.GID, } // Encode endian-sensitive fields. data := bytes.Buffer{} for _, f := range fields { if err := binary.Write(&data, binary.LittleEndian, f); err != nil { panic(err) // (*bytes.Buffer).Write() is documented as always returning a nil error. } } return io.MultiReader( &data, bytes.NewReader(d.raw.Name[:]), bytes.NewReader(d.raw.Extra[:]), ) } sif-2.8.3/pkg/sif/descriptor_input.go000066400000000000000000000210641432675271000176050ustar00rootroot00000000000000// Copyright (c) 2021-2022, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package sif import ( "crypto" "errors" "fmt" "io" "time" ) // descriptorOpts accumulates data object options. type descriptorOpts struct { groupID uint32 linkID uint32 alignment int name string extra interface{} t time.Time } // DescriptorInputOpt are used to specify data object options. type DescriptorInputOpt func(DataType, *descriptorOpts) error // OptNoGroup specifies the data object is not contained within a data object group. func OptNoGroup() DescriptorInputOpt { return func(_ DataType, opts *descriptorOpts) error { opts.groupID = 0 return nil } } // OptGroupID specifies groupID as data object group ID. func OptGroupID(groupID uint32) DescriptorInputOpt { return func(_ DataType, opts *descriptorOpts) error { if groupID == 0 { return ErrInvalidGroupID } opts.groupID = groupID return nil } } // OptLinkedID specifies that the data object is linked to the data object with the specified ID. func OptLinkedID(id uint32) DescriptorInputOpt { return func(_ DataType, opts *descriptorOpts) error { if id == 0 { return ErrInvalidObjectID } opts.linkID = id return nil } } // OptLinkedGroupID specifies that the data object is linked to the data object group with the // specified groupID. func OptLinkedGroupID(groupID uint32) DescriptorInputOpt { return func(_ DataType, opts *descriptorOpts) error { if groupID == 0 { return ErrInvalidGroupID } opts.linkID = groupID | descrGroupMask return nil } } // OptObjectAlignment specifies n as the data alignment requirement. func OptObjectAlignment(n int) DescriptorInputOpt { return func(_ DataType, opts *descriptorOpts) error { opts.alignment = n return nil } } // OptObjectName specifies name as the data object name. func OptObjectName(name string) DescriptorInputOpt { return func(_ DataType, opts *descriptorOpts) error { opts.name = name return nil } } // OptObjectTime specifies t as the data object creation time. func OptObjectTime(t time.Time) DescriptorInputOpt { return func(_ DataType, opts *descriptorOpts) error { opts.t = t return nil } } type unexpectedDataTypeError struct { got DataType want []DataType } func (e *unexpectedDataTypeError) Error() string { return fmt.Sprintf("unexpected data type %v, expected one of: %v", e.got, e.want) } func (e *unexpectedDataTypeError) Is(target error) bool { //nolint:errorlint // don't compare wrapped errors in Is() t, ok := target.(*unexpectedDataTypeError) if !ok { return false } if len(t.want) > 0 { // Use a map to check that the "want" errors in e and t contain the same values, ignoring // any ordering differences. acc := make(map[DataType]int, len(t.want)) // Increment counter for each data type in e. for _, dt := range e.want { if _, ok := acc[dt]; !ok { acc[dt] = 0 } acc[dt]++ } // Decrement counter for each data type in e. for _, dt := range t.want { if _, ok := acc[dt]; !ok { return false } acc[dt]-- } // If the "want" errors in e and t are equivalent, all counters should be zero. for _, n := range acc { if n != 0 { return false } } } return (e.got == t.got || t.got == 0) } // OptCryptoMessageMetadata sets metadata for a crypto message data object. The format type is set // to ft, and the message type is set to mt. // // If this option is applied to a data object with an incompatible type, an error is returned. func OptCryptoMessageMetadata(ft FormatType, mt MessageType) DescriptorInputOpt { return func(t DataType, opts *descriptorOpts) error { if got, want := t, DataCryptoMessage; got != want { return &unexpectedDataTypeError{got, []DataType{want}} } m := cryptoMessage{ Formattype: ft, Messagetype: mt, } opts.extra = m return nil } } var errUnknownArchitcture = errors.New("unknown architecture") // OptPartitionMetadata sets metadata for a partition data object. The filesystem type is set to // fs, the partition type is set to pt, and the CPU architecture is set to arch. The value of arch // should be the architecture as represented by the Go runtime. // // If this option is applied to a data object with an incompatible type, an error is returned. func OptPartitionMetadata(fs FSType, pt PartType, arch string) DescriptorInputOpt { return func(t DataType, opts *descriptorOpts) error { if got, want := t, DataPartition; got != want { return &unexpectedDataTypeError{got, []DataType{want}} } sifarch := getSIFArch(arch) if sifarch == hdrArchUnknown { return fmt.Errorf("%w: %v", errUnknownArchitcture, arch) } p := partition{ Fstype: fs, Parttype: pt, Arch: sifarch, } opts.extra = p return nil } } // sifHashType converts h into a HashType. func sifHashType(h crypto.Hash) hashType { switch h { case crypto.SHA256: return hashSHA256 case crypto.SHA384: return hashSHA384 case crypto.SHA512: return hashSHA512 case crypto.BLAKE2s_256: return hashBLAKE2S case crypto.BLAKE2b_256: return hashBLAKE2B } return 0 } // OptSignatureMetadata sets metadata for a signature data object. The hash type is set to ht, and // the signing entity fingerprint is set to fp. // // If this option is applied to a data object with an incompatible type, an error is returned. func OptSignatureMetadata(ht crypto.Hash, fp []byte) DescriptorInputOpt { return func(t DataType, opts *descriptorOpts) error { if got, want := t, DataSignature; got != want { return &unexpectedDataTypeError{got, []DataType{want}} } s := signature{ Hashtype: sifHashType(ht), } copy(s.Entity[:], fp) opts.extra = s return nil } } // OptSBOMMetadata sets metadata for a SBOM data object. The SBOM format is set to f. // // If this option is applied to a data object with an incompatible type, an error is returned. func OptSBOMMetadata(f SBOMFormat) DescriptorInputOpt { return func(t DataType, opts *descriptorOpts) error { if got, want := t, DataSBOM; got != want { return &unexpectedDataTypeError{got, []DataType{want}} } s := sbom{ Format: f, } opts.extra = s return nil } } // DescriptorInput describes a new data object. type DescriptorInput struct { dt DataType r io.Reader opts descriptorOpts } // DefaultObjectGroup is the default group that data objects are placed in. const DefaultObjectGroup = 1 // NewDescriptorInput returns a DescriptorInput representing a data object of type t, with contents // read from r, configured according to opts. // // It is possible (and often necessary) to store additional metadata related to certain types of // data objects. Consider supplying options such as OptCryptoMessageMetadata, OptPartitionMetadata, // OptSignatureMetadata, and OptSBOMMetadata for this purpose. // // By default, the data object will be placed in the default data object group (1). To override // this behavior, use OptNoGroup or OptGroupID. To link this data object, use OptLinkedID or // OptLinkedGroupID. // // By default, the data object will not be aligned unless it is of type DataPartition, in which // case it will be aligned on a 4096 byte boundary. To override this behavior, consider using // OptObjectAlignment. // // By default, no name is set for data object. To set a name, use OptObjectName. // // When creating a new image, data object creation/modification times are set to the image creation // time. When modifying an existing image, the data object creation/modification time is set to the // image modification time. To override this behavior, consider using OptObjectTime. func NewDescriptorInput(t DataType, r io.Reader, opts ...DescriptorInputOpt) (DescriptorInput, error) { dopts := descriptorOpts{ groupID: DefaultObjectGroup, } if t == DataPartition { dopts.alignment = 4096 } for _, opt := range opts { if err := opt(t, &dopts); err != nil { return DescriptorInput{}, fmt.Errorf("%w", err) } } di := DescriptorInput{ dt: t, r: r, opts: dopts, } return di, nil } // fillDescriptor fills d according to di. If di does not explicitly specify a time value, use t. func (di DescriptorInput) fillDescriptor(t time.Time, d *rawDescriptor) error { d.DataType = di.dt d.GroupID = di.opts.groupID | descrGroupMask d.LinkedID = di.opts.linkID if !di.opts.t.IsZero() { t = di.opts.t } d.CreatedAt = t.Unix() d.ModifiedAt = t.Unix() d.UID = 0 d.GID = 0 if err := d.setName(di.opts.name); err != nil { return err } return d.setExtra(di.opts.extra) } sif-2.8.3/pkg/sif/descriptor_input_test.go000066400000000000000000000110231432675271000206360ustar00rootroot00000000000000// Copyright (c) 2021-2022, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package sif import ( "bytes" "crypto" "encoding/binary" "errors" "testing" "time" "github.com/sebdah/goldie/v2" ) // getDescriptorInput returns a new DescriptorInput of type dt with contents b, according to opts. func getDescriptorInput(t *testing.T, dt DataType, b []byte, opts ...DescriptorInputOpt) DescriptorInput { t.Helper() di, err := NewDescriptorInput(dt, bytes.NewReader(b), opts...) if err != nil { t.Fatal(err) } return di } func TestNewDescriptorInput(t *testing.T) { t.Parallel() fp := []byte{ 0x12, 0x04, 0x5c, 0x8c, 0x0b, 0x10, 0x04, 0xd0, 0x58, 0xde, 0x4b, 0xed, 0xa2, 0x0c, 0x27, 0xee, 0x7f, 0xf7, 0xba, 0x84, } tests := []struct { name string t DataType opts []DescriptorInputOpt wantErr error }{ { name: "Empty", t: DataGeneric, }, { name: "OptNoGroup", t: DataGeneric, opts: []DescriptorInputOpt{ OptNoGroup(), }, }, { name: "OptGroupIDInvalid", t: DataGeneric, opts: []DescriptorInputOpt{ OptGroupID(0), }, wantErr: ErrInvalidGroupID, }, { name: "OptGroupID", t: DataGeneric, opts: []DescriptorInputOpt{ OptGroupID(2), }, }, { name: "OptLinkedIDInvalid", t: DataGeneric, opts: []DescriptorInputOpt{ OptLinkedID(0), }, wantErr: ErrInvalidObjectID, }, { name: "OptLinkedID", t: DataGeneric, opts: []DescriptorInputOpt{ OptLinkedID(1), }, }, { name: "OptLinkedGroupIDInvalid", t: DataGeneric, opts: []DescriptorInputOpt{ OptLinkedGroupID(0), }, wantErr: ErrInvalidGroupID, }, { name: "OptLinkedGroupID", t: DataGeneric, opts: []DescriptorInputOpt{ OptLinkedGroupID(1), }, }, { name: "OptObjectAlignment", t: DataGeneric, opts: []DescriptorInputOpt{ OptObjectAlignment(8), }, }, { name: "OptObjectName", t: DataGeneric, opts: []DescriptorInputOpt{ OptObjectName("name"), }, }, { name: "OptObjectTime", t: DataGeneric, opts: []DescriptorInputOpt{ OptObjectTime(time.Unix(946702800, 0)), }, }, { name: "OptCryptoMessageMetadataUnexpectedDataType", t: DataGeneric, opts: []DescriptorInputOpt{ OptCryptoMessageMetadata(FormatOpenPGP, MessageClearSignature), }, wantErr: &unexpectedDataTypeError{DataGeneric, []DataType{DataCryptoMessage}}, }, { name: "OptCryptoMessageMetadata", t: DataCryptoMessage, opts: []DescriptorInputOpt{ OptCryptoMessageMetadata(FormatOpenPGP, MessageClearSignature), }, }, { name: "OptPartitionMetadataUnexpectedDataType", t: DataGeneric, opts: []DescriptorInputOpt{ OptPartitionMetadata(FsSquash, PartPrimSys, "386"), }, wantErr: &unexpectedDataTypeError{DataGeneric, []DataType{DataPartition}}, }, { name: "OptPartitionMetadata", t: DataPartition, opts: []DescriptorInputOpt{ OptPartitionMetadata(FsSquash, PartPrimSys, "386"), }, }, { name: "OptSignatureMetadataUnexpectedDataType", t: DataGeneric, opts: []DescriptorInputOpt{ OptSignatureMetadata(crypto.SHA256, fp), }, wantErr: &unexpectedDataTypeError{DataGeneric, []DataType{DataSignature}}, }, { name: "OptSignatureMetadata", t: DataSignature, opts: []DescriptorInputOpt{ OptSignatureMetadata(crypto.SHA256, fp), }, }, { name: "OptSBOMMetadataUnexpectedDataType", t: DataGeneric, opts: []DescriptorInputOpt{ OptSBOMMetadata(SBOMFormatCycloneDXJSON), }, wantErr: &unexpectedDataTypeError{DataGeneric, []DataType{DataSBOM}}, }, { name: "OptSBOMMetadata", t: DataSBOM, opts: []DescriptorInputOpt{ OptSBOMMetadata(SBOMFormatCycloneDXJSON), }, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() di, err := NewDescriptorInput(tt.t, nil, tt.opts...) if got, want := err, tt.wantErr; !errors.Is(got, want) { t.Fatalf("got error %v, want %v", got, want) } if err == nil { d := rawDescriptor{} if err := di.fillDescriptor(time.Time{}, &d); err != nil { t.Fatal(err) } b := bytes.Buffer{} if err := binary.Write(&b, binary.LittleEndian, d); err != nil { t.Fatal(err) } g := goldie.New(t, goldie.WithTestNameForDir(true)) g.Assert(t, tt.name, b.Bytes()) } }) } } sif-2.8.3/pkg/sif/descriptor_test.go000066400000000000000000000223731432675271000174310ustar00rootroot00000000000000// Copyright (c) 2018-2022, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package sif import ( "bytes" "crypto" "errors" "io" "os" "path/filepath" "testing" "github.com/sebdah/goldie/v2" ) func TestDescriptor_GetData(t *testing.T) { f, err := LoadContainerFromPath( filepath.Join(corpus, "one-group-signed.sif"), OptLoadWithFlag(os.O_RDONLY), ) if err != nil { t.Fatalf("failed to load container: %v", err) } defer func() { if err := f.UnloadContainer(); err != nil { t.Error(err) } }() // Get the signature block descr, err := f.GetDescriptor(WithID(3)) if err != nil { t.Fatalf("failed to get descriptor: %v", err) } b, err := descr.GetData() if err != nil { t.Fatalf("failed to get data: %v", err) } if got, want := string(b[5:10]), "BEGIN"; got != want { t.Errorf("got data %#v, want %#v", got, want) } } func TestDescriptor_GetReader(t *testing.T) { f, err := LoadContainerFromPath( filepath.Join(corpus, "one-group-signed.sif"), OptLoadWithFlag(os.O_RDONLY), ) if err != nil { t.Fatalf("failed to load container: %v", err) } defer func() { if err := f.UnloadContainer(); err != nil { t.Error(err) } }() // Get the signature block descr, err := f.GetDescriptor(WithID(3)) if err != nil { t.Fatalf("failed to get descriptor: %v", err) } // Read data via Reader and validate data. b := make([]byte, descr.Size()) if _, err := io.ReadFull(descr.GetReader(), b); err != nil { t.Fatalf("failed to read: %v", err) } if got, want := string(b[5:10]), "BEGIN"; got != want { t.Errorf("got data %#v, want %#v", got, want) } } func TestDescriptor_Name(t *testing.T) { // load the test container f, err := LoadContainerFromPath( filepath.Join(corpus, "one-object-generic-json.sif"), OptLoadWithFlag(os.O_RDONLY), ) if err != nil { t.Fatalf("failed to load container: %v", err) } part, err := f.GetDescriptor(WithDataType(DataGenericJSON)) if err != nil { t.Fatalf("failed to get descriptor: %v", err) } if got, want := part.Name(), "data.json"; got != want { t.Errorf("got name %v, want %v", got, want) } // unload the test container if err = f.UnloadContainer(); err != nil { t.Error("UnloadContainer(fimg):", err) } } func TestDescriptor_PartitionMetadata(t *testing.T) { p := partition{ Fstype: FsSquash, Parttype: PartPrimSys, Arch: hdrArch386, } rd := rawDescriptor{ DataType: DataPartition, } if err := rd.setExtra(p); err != nil { t.Fatal(err) } tests := []struct { name string rd rawDescriptor p partition wantFS FSType wantPart PartType wantArch string wantErr error }{ { name: "UnexpectedDataType", rd: rawDescriptor{ DataType: DataGeneric, }, wantErr: &unexpectedDataTypeError{DataGeneric, []DataType{DataPartition}}, }, { name: "PartPrimSys", rd: rd, wantFS: FsSquash, wantPart: PartPrimSys, wantArch: "386", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { d := Descriptor{raw: tt.rd} fs, part, arch, err := d.PartitionMetadata() if got, want := err, tt.wantErr; !errors.Is(got, want) { t.Fatalf("got error %v, want %v", got, want) } if err == nil { if got, want := fs, tt.wantFS; got != want { t.Fatalf("got filesystem type %v, want %v", got, want) } if got, want := part, tt.wantPart; got != want { t.Fatalf("got partition type %v, want %v", got, want) } if got, want := arch, tt.wantArch; got != want { t.Fatalf("got architecture %v, want %v", got, want) } } }) } } func TestDescriptor_SignatureMetadata(t *testing.T) { tests := []struct { name string dt DataType ht hashType fp []byte wantErr error wantHT crypto.Hash }{ { name: "UnexpectedDataType", dt: DataGeneric, wantErr: &unexpectedDataTypeError{DataGeneric, []DataType{DataSignature}}, }, { name: "Fingerprint", dt: DataSignature, ht: hashSHA384, fp: []byte{ 0x12, 0x04, 0x5c, 0x8c, 0x0b, 0x10, 0x04, 0xd0, 0x58, 0xde, 0x4b, 0xed, 0xa2, 0x0c, 0x27, 0xee, 0x7f, 0xf7, 0xba, 0x84, }, wantHT: crypto.SHA384, }, { name: "NoFingerprint", dt: DataSignature, ht: hashSHA256, wantHT: crypto.SHA256, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { sig := signature{ Hashtype: tt.ht, } copy(sig.Entity[:], tt.fp) rd := rawDescriptor{ DataType: tt.dt, } if err := rd.setExtra(sig); err != nil { t.Fatal(err) } d := Descriptor{raw: rd} ht, fp, err := d.SignatureMetadata() if got, want := err, tt.wantErr; !errors.Is(got, want) { t.Fatalf("got error %v, want %v", got, want) } if err == nil { if got, want := ht, tt.wantHT; got != want { t.Fatalf("got hash type %v, want %v", got, want) } if got, want := fp, tt.fp; !bytes.Equal(got, want) { t.Fatalf("got entity %v, want %v", got, want) } } }) } } func TestDescriptor_CryptoMessageMetadata(t *testing.T) { m := cryptoMessage{ Formattype: FormatOpenPGP, Messagetype: MessageClearSignature, } rd := rawDescriptor{ DataType: DataCryptoMessage, } if err := rd.setExtra(m); err != nil { t.Fatal(err) } tests := []struct { name string rd rawDescriptor wantFT FormatType wantMT MessageType wantErr error }{ { name: "UnexpectedDataType", rd: rawDescriptor{ DataType: DataGeneric, }, wantErr: &unexpectedDataTypeError{DataGeneric, []DataType{DataCryptoMessage}}, }, { name: "OK", rd: rd, wantFT: FormatOpenPGP, wantMT: MessageClearSignature, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { d := Descriptor{raw: tt.rd} ft, mt, err := d.CryptoMessageMetadata() if got, want := err, tt.wantErr; !errors.Is(got, want) { t.Fatalf("got error %v, want %v", got, want) } if err == nil { if got, want := ft, tt.wantFT; got != want { t.Fatalf("got format type %v, want %v", got, want) } if got, want := mt, tt.wantMT; got != want { t.Fatalf("got message type %v, want %v", got, want) } } }) } } func TestDescriptor_SBOMMetadata(t *testing.T) { m := sbom{ Format: SBOMFormatCycloneDXJSON, } rd := rawDescriptor{ DataType: DataSBOM, } if err := rd.setExtra(m); err != nil { t.Fatal(err) } tests := []struct { name string rd rawDescriptor wantFormat SBOMFormat wantErr error }{ { name: "UnexpectedDataType", rd: rawDescriptor{ DataType: DataGeneric, }, wantErr: &unexpectedDataTypeError{DataGeneric, []DataType{DataSBOM}}, }, { name: "OK", rd: rd, wantFormat: SBOMFormatCycloneDXJSON, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { d := Descriptor{raw: tt.rd} f, err := d.SBOMMetadata() if got, want := err, tt.wantErr; !errors.Is(got, want) { t.Fatalf("got error %v, want %v", got, want) } if err == nil { if got, want := f, tt.wantFormat; got != want { t.Fatalf("got format %v, want %v", got, want) } } }) } } func TestDescriptor_GetIntegrityReader(t *testing.T) { rd := rawDescriptor{ DataType: DataDeffile, Used: true, ID: 1, GroupID: descrGroupMask | 1, CreatedAt: 1504657553, ModifiedAt: 1504657553, } copy(rd.Name[:], "GOOD_NAME") copy(rd.Extra[:], "GOOD_EXTRA") tests := []struct { name string relativeID uint32 modFunc func(*rawDescriptor) }{ { name: "DataType", modFunc: func(od *rawDescriptor) { od.DataType = DataEnvVar }, }, { name: "Used", modFunc: func(od *rawDescriptor) { od.Used = !od.Used }, }, { name: "ID", modFunc: func(od *rawDescriptor) { od.ID++ }, }, { name: "RelativeID", relativeID: 1, }, { name: "GroupID", modFunc: func(od *rawDescriptor) { od.GroupID++ }, }, { name: "LinkedID", modFunc: func(od *rawDescriptor) { od.LinkedID++ }, }, { name: "Offset", modFunc: func(od *rawDescriptor) { od.Offset++ }, }, { name: "Size", modFunc: func(od *rawDescriptor) { od.Size++ }, }, { name: "SizeWithPadding", modFunc: func(od *rawDescriptor) { od.SizeWithPadding++ }, }, { name: "CreatedAt", modFunc: func(od *rawDescriptor) { od.CreatedAt++ }, }, { name: "ModifiedAt", modFunc: func(od *rawDescriptor) { od.ModifiedAt++ }, }, { name: "UID", modFunc: func(od *rawDescriptor) { od.UID++ }, }, { name: "GID", modFunc: func(od *rawDescriptor) { od.GID++ }, }, { name: "Name", modFunc: func(od *rawDescriptor) { copy(od.Name[:], "BAD_NAME") }, }, { name: "Extra", modFunc: func(od *rawDescriptor) { copy(od.Extra[:], "BAD_EXTRA") }, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { d := Descriptor{ raw: rd, relativeID: tt.relativeID, } if tt.modFunc != nil { tt.modFunc(&d.raw) } b := bytes.Buffer{} if _, err := io.Copy(&b, d.GetIntegrityReader()); err != nil { t.Fatal(err) } g := goldie.New(t, goldie.WithTestNameForDir(true)) g.Assert(t, tt.name, b.Bytes()) }) } } sif-2.8.3/pkg/sif/load.go000066400000000000000000000104321432675271000151240ustar00rootroot00000000000000// Copyright (c) 2018-2021, Sylabs Inc. All rights reserved. // Copyright (c) 2017, SingularityWare, LLC. All rights reserved. // Copyright (c) 2017, Yannick Cote All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package sif import ( "encoding/binary" "errors" "fmt" "io" "os" ) var ( errInvalidMagic = errors.New("invalid SIF magic") errIncompatibleVersion = errors.New("incompatible SIF version") ) // isValidSif looks at key fields from the global header to assess SIF validity. func isValidSif(f *FileImage) error { if f.h.Magic != hdrMagic { return errInvalidMagic } if f.h.Version != CurrentVersion.bytes() { return errIncompatibleVersion } return nil } // populateMinIDs populates the minIDs field of f. func (f *FileImage) populateMinIDs() { f.minIDs = make(map[uint32]uint32) f.WithDescriptors(func(d Descriptor) bool { if minID, ok := f.minIDs[d.raw.GroupID]; !ok || d.ID() < minID { f.minIDs[d.raw.GroupID] = d.ID() } return false }) } // loadContainer loads a SIF image from rw. func loadContainer(rw ReadWriter) (*FileImage, error) { f := FileImage{rw: rw} // Read global header. err := binary.Read( io.NewSectionReader(rw, 0, int64(binary.Size(f.h))), binary.LittleEndian, &f.h, ) if err != nil { return nil, fmt.Errorf("reading global header: %w", err) } if err := isValidSif(&f); err != nil { return nil, err } // Read descriptors. f.rds = make([]rawDescriptor, f.h.DescriptorsTotal) err = binary.Read( io.NewSectionReader(rw, f.h.DescriptorsOffset, f.h.DescriptorsSize), binary.LittleEndian, &f.rds, ) if err != nil { return nil, fmt.Errorf("reading descriptors: %w", err) } f.populateMinIDs() return &f, nil } // loadOpts accumulates container loading options. type loadOpts struct { flag int closeOnUnload bool } // LoadOpt are used to specify container loading options. type LoadOpt func(*loadOpts) error // OptLoadWithFlag specifies flag (os.O_RDONLY etc.) to be used when opening the container file. func OptLoadWithFlag(flag int) LoadOpt { return func(lo *loadOpts) error { lo.flag = flag return nil } } // OptLoadWithCloseOnUnload specifies whether the ReadWriter should be closed by UnloadContainer. // By default, the ReadWriter will be closed if it implements the io.Closer interface. func OptLoadWithCloseOnUnload(b bool) LoadOpt { return func(lo *loadOpts) error { lo.closeOnUnload = b return nil } } // LoadContainerFromPath loads a new SIF container from path, according to opts. // // On success, a FileImage is returned. The caller must call UnloadContainer to ensure resources // are released. // // By default, the file is opened for read and write access. To change this behavior, consider // using OptLoadWithFlag. func LoadContainerFromPath(path string, opts ...LoadOpt) (*FileImage, error) { lo := loadOpts{ flag: os.O_RDWR, } for _, opt := range opts { if err := opt(&lo); err != nil { return nil, fmt.Errorf("%w", err) } } fp, err := os.OpenFile(path, lo.flag, 0) if err != nil { return nil, fmt.Errorf("%w", err) } f, err := loadContainer(fp) if err != nil { fp.Close() return nil, fmt.Errorf("%w", err) } f.closeOnUnload = true return f, nil } // LoadContainer loads a new SIF container from rw, according to opts. // // On success, a FileImage is returned. The caller must call UnloadContainer to ensure resources // are released. By default, UnloadContainer will close rw if it implements the io.Closer // interface. To change this behavior, consider using OptLoadWithCloseOnUnload. func LoadContainer(rw ReadWriter, opts ...LoadOpt) (*FileImage, error) { lo := loadOpts{ closeOnUnload: true, } for _, opt := range opts { if err := opt(&lo); err != nil { return nil, fmt.Errorf("%w", err) } } f, err := loadContainer(rw) if err != nil { return nil, fmt.Errorf("%w", err) } f.closeOnUnload = lo.closeOnUnload return f, nil } // UnloadContainer unloads f, releasing associated resources. func (f *FileImage) UnloadContainer() error { if c, ok := f.rw.(io.Closer); ok && f.closeOnUnload { if err := c.Close(); err != nil { return fmt.Errorf("%w", err) } } return nil } sif-2.8.3/pkg/sif/load_test.go000066400000000000000000000067041432675271000161720ustar00rootroot00000000000000// Copyright (c) 2018-2021, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package sif import ( "os" "path/filepath" "testing" ) func TestLoadContainerFromPath(t *testing.T) { tests := []struct { name string path string opts []LoadOpt }{ { name: "NoOpts", path: filepath.Join(corpus, "one-group.sif"), }, { name: "ReadOnly", path: filepath.Join(corpus, "one-group.sif"), opts: []LoadOpt{OptLoadWithFlag(os.O_RDONLY)}, }, { name: "ReadWrite", path: filepath.Join(corpus, "one-group.sif"), opts: []LoadOpt{OptLoadWithFlag(os.O_RDWR)}, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { f, err := LoadContainerFromPath(tt.path, tt.opts...) if err != nil { t.Fatalf("failed to load container: %v", err) } if err := f.UnloadContainer(); err != nil { t.Errorf("failed to unload container: %v", err) } }) } } func TestLoadContainer(t *testing.T) { tests := []struct { name string opts []LoadOpt }{ { name: "NoOpts", }, { name: "CloseOnUnload", opts: []LoadOpt{OptLoadWithCloseOnUnload(true)}, }, { name: "NoCloseOnUnload", opts: []LoadOpt{OptLoadWithCloseOnUnload(false)}, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { rw, err := os.Open(filepath.Join(corpus, "one-group.sif")) if err != nil { t.Fatal(err) } defer rw.Close() f, err := LoadContainer(rw, tt.opts...) if err != nil { t.Fatalf("failed to load container: %v", err) } if err := f.UnloadContainer(); err != nil { t.Errorf("failed to unload container: %v", err) } }) } } func TestLoadContainerFpMock(t *testing.T) { // This test is using mockSifReadWriter to verify that the code // is not making assumptions regading the behavior of the // ReadWriter it's getting, as mockSifReadWriter implements a // very dumb buffer. This specific test could be exteded to test // for more error conditions as it would be possible to report // errors from cases where it would be otherwise hard to do so // (e.g. Seek, ReadAt or Truncate reporting errors). // Load a valid SIF file to test the happy path. content, err := os.ReadFile(filepath.Join(corpus, "one-group.sif")) if err != nil { t.Fatalf("failed to read file: %v", err) } rw := NewBuffer(content) fimg, err := LoadContainer(rw, OptLoadWithFlag(os.O_RDONLY)) if err != nil { t.Error("LoadContainerFp(fp, true):", err) } if err = fimg.UnloadContainer(); err != nil { t.Error("fimg.UnloadContainer():", err) } } func TestLoadContainerInvalidMagic(t *testing.T) { // Load a valid SIF file ... content, err := os.ReadFile(filepath.Join(corpus, "one-group.sif")) if err != nil { t.Fatalf("failed to read file: %v", err) } // ... and edit the magic to make it invalid. Instead of // exploring all kinds of invalid, simply mess with the last // byte, as this would catch off-by-one errors in the code. copy(content[hdrLaunchLen:hdrLaunchLen+hdrMagicLen], "SIF_MAGIX") rw := NewBuffer(content) fimg, err := LoadContainer(rw, OptLoadWithFlag(os.O_RDONLY)) if err == nil { // unload the container in case it's loaded, ignore // any errors _ = fimg.UnloadContainer() t.Errorf(`LoadContainerFp(fp, true) did not report an error for a container with invalid magic.`) } } sif-2.8.3/pkg/sif/select.go000066400000000000000000000146221432675271000154710ustar00rootroot00000000000000// Copyright (c) 2021, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package sif import ( "errors" "fmt" ) // ErrNoObjects is the error returned when an image contains no data objects. var ErrNoObjects = errors.New("no objects in image") // ErrObjectNotFound is the error returned when a data object is not found. var ErrObjectNotFound = errors.New("object not found") // ErrMultipleObjectsFound is the error returned when multiple data objects are found. var ErrMultipleObjectsFound = errors.New("multiple objects found") // ErrInvalidObjectID is the error returned when an invalid object ID is supplied. var ErrInvalidObjectID = errors.New("invalid object ID") // ErrInvalidGroupID is the error returned when an invalid group ID is supplied. var ErrInvalidGroupID = errors.New("invalid group ID") // DescriptorSelectorFunc returns true if d matches, and false otherwise. type DescriptorSelectorFunc func(d Descriptor) (bool, error) // WithDataType selects descriptors that have data type dt. func WithDataType(dt DataType) DescriptorSelectorFunc { return func(d Descriptor) (bool, error) { return d.DataType() == dt, nil } } // WithID selects descriptors with a matching ID. func WithID(id uint32) DescriptorSelectorFunc { return func(d Descriptor) (bool, error) { if id == 0 { return false, ErrInvalidObjectID } return d.ID() == id, nil } } // WithNoGroup selects descriptors that are not contained within an object group. func WithNoGroup() DescriptorSelectorFunc { return func(d Descriptor) (bool, error) { return d.GroupID() == 0, nil } } // WithGroupID returns a selector func that selects descriptors with a matching groupID. func WithGroupID(groupID uint32) DescriptorSelectorFunc { return func(d Descriptor) (bool, error) { if groupID == 0 { return false, ErrInvalidGroupID } return d.GroupID() == groupID, nil } } // WithLinkedID selects descriptors that are linked to the data object with specified ID. func WithLinkedID(id uint32) DescriptorSelectorFunc { return func(d Descriptor) (bool, error) { if id == 0 { return false, ErrInvalidObjectID } linkedID, isGroup := d.LinkedID() return !isGroup && linkedID == id, nil } } // WithLinkedGroupID selects descriptors that are linked to the data object group with specified // ID. func WithLinkedGroupID(groupID uint32) DescriptorSelectorFunc { return func(d Descriptor) (bool, error) { if groupID == 0 { return false, ErrInvalidGroupID } linkedID, isGroup := d.LinkedID() return isGroup && linkedID == groupID, nil } } // WithPartitionType selects descriptors containing a partition of type pt. func WithPartitionType(pt PartType) DescriptorSelectorFunc { return func(d Descriptor) (bool, error) { return d.raw.isPartitionOfType(pt), nil } } // descriptorFromRaw populates a Descriptor from rd. func (f *FileImage) descriptorFromRaw(rd *rawDescriptor) Descriptor { return Descriptor{ raw: *rd, r: f.rw, relativeID: rd.ID - f.minIDs[rd.GroupID], } } // GetDescriptors returns a slice of in-use descriptors for which all selector funcs return true. // If the image contains no data objects, an error wrapping ErrNoObjects is returned. func (f *FileImage) GetDescriptors(fns ...DescriptorSelectorFunc) ([]Descriptor, error) { if f.DescriptorsFree() == f.DescriptorsTotal() { return nil, fmt.Errorf("%w", ErrNoObjects) } var ds []Descriptor err := f.withDescriptors(multiSelectorFunc(fns...), func(d *rawDescriptor) error { ds = append(ds, f.descriptorFromRaw(d)) return nil }) if err != nil { return nil, fmt.Errorf("%w", err) } return ds, nil } // getDescriptor returns a pointer to the in-use descriptor selected by fns. If no descriptor is // selected by fns, ErrObjectNotFound is returned. If multiple descriptors are selected by fns, // ErrMultipleObjectsFound is returned. func (f *FileImage) getDescriptor(fns ...DescriptorSelectorFunc) (*rawDescriptor, error) { var d *rawDescriptor err := f.withDescriptors(multiSelectorFunc(fns...), func(found *rawDescriptor) error { if d != nil { return ErrMultipleObjectsFound } d = found return nil }) if err == nil && d == nil { err = ErrObjectNotFound } return d, err } // GetDescriptor returns the in-use descriptor selected by fns. If the image contains no data // objects, an error wrapping ErrNoObjects is returned. If no descriptor is selected by fns, an // error wrapping ErrObjectNotFound is returned. If multiple descriptors are selected by fns, an // error wrapping ErrMultipleObjectsFound is returned. func (f *FileImage) GetDescriptor(fns ...DescriptorSelectorFunc) (Descriptor, error) { if f.DescriptorsFree() == f.DescriptorsTotal() { return Descriptor{}, fmt.Errorf("%w", ErrNoObjects) } d, err := f.getDescriptor(fns...) if err != nil { return Descriptor{}, fmt.Errorf("%w", err) } return f.descriptorFromRaw(d), nil } // multiSelectorFunc returns a DescriptorSelectorFunc that selects a descriptor iff all of fns // select the descriptor. func multiSelectorFunc(fns ...DescriptorSelectorFunc) DescriptorSelectorFunc { return func(d Descriptor) (bool, error) { for _, fn := range fns { if ok, err := fn(d); !ok || err != nil { return ok, err } } return true, nil } } // withDescriptors calls onMatchFn with each in-use descriptor in f for which selectFn returns // true. If selectFn or onMatchFn return a non-nil error, the iteration halts, and the error is // returned to the caller. func (f *FileImage) withDescriptors(selectFn DescriptorSelectorFunc, onMatchFn func(*rawDescriptor) error) error { for i, d := range f.rds { if !d.Used { continue } if ok, err := selectFn(f.descriptorFromRaw(&f.rds[i])); err != nil { return err } else if !ok { continue } if err := onMatchFn(&f.rds[i]); err != nil { return err } } return nil } var errAbort = errors.New("abort") // abortOnMatch is a semantic convenience function that always returns a non-nil error, which can // be used as a no-op matchFn. func abortOnMatch(*rawDescriptor) error { return errAbort } // WithDescriptors calls fn with each in-use descriptor in f, until fn returns true. func (f *FileImage) WithDescriptors(fn func(d Descriptor) bool) { selectFn := func(d Descriptor) (bool, error) { return fn(d), nil } _ = f.withDescriptors(selectFn, abortOnMatch) } sif-2.8.3/pkg/sif/select_test.go000066400000000000000000000131041432675271000165220ustar00rootroot00000000000000// Copyright (c) 2021, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package sif import ( "errors" "testing" ) func TestFileImage_GetDescriptors(t *testing.T) { ds := []rawDescriptor{ { DataType: DataPartition, Used: true, ID: 1, GroupID: 1 | descrGroupMask, }, { DataType: DataSignature, Used: true, ID: 2, GroupID: 1 | descrGroupMask, LinkedID: 1, }, { DataType: DataSignature, Used: true, ID: 3, GroupID: 0 | descrGroupMask, LinkedID: 1 | descrGroupMask, }, } f := &FileImage{ rds: ds, h: header{ DescriptorsTotal: int64(len(ds)), }, } f.populateMinIDs() tests := []struct { name string fns []DescriptorSelectorFunc wantErr error wantIDs []uint32 }{ { name: "DataPartition", fns: []DescriptorSelectorFunc{ WithDataType(DataPartition), }, wantIDs: []uint32{1}, }, { name: "DataSignature", fns: []DescriptorSelectorFunc{ WithDataType(DataSignature), }, wantIDs: []uint32{2, 3}, }, { name: "DataSignatureGroupID", fns: []DescriptorSelectorFunc{ WithDataType(DataSignature), WithGroupID(1), }, wantIDs: []uint32{2}, }, { name: "NoGroupID", fns: []DescriptorSelectorFunc{ WithNoGroup(), }, wantIDs: []uint32{3}, }, { name: "GroupID", fns: []DescriptorSelectorFunc{ WithGroupID(1), }, wantIDs: []uint32{1, 2}, }, { name: "GroupIDInvalidGroupID", fns: []DescriptorSelectorFunc{ WithGroupID(0), }, wantErr: ErrInvalidGroupID, }, { name: "LinkedID", fns: []DescriptorSelectorFunc{ WithLinkedID(1), }, wantIDs: []uint32{2}, }, { name: "LinkedIDInvalidObjectID", fns: []DescriptorSelectorFunc{ WithLinkedID(0), }, wantErr: ErrInvalidObjectID, }, { name: "LinkedGroupID", fns: []DescriptorSelectorFunc{ WithLinkedGroupID(1), }, wantIDs: []uint32{3}, }, { name: "LinkedGroupIDInvalidGroupID", fns: []DescriptorSelectorFunc{ WithLinkedGroupID(0), }, wantErr: ErrInvalidGroupID, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ds, err := f.GetDescriptors(tt.fns...) if got, want := err, tt.wantErr; !errors.Is(got, want) { t.Fatalf("got error %v, want %v", got, want) } if got, want := len(ds), len(tt.wantIDs); got != want { t.Fatalf("got %v IDs, want %v", got, want) } for i := range ds { if got, want := ds[i].ID(), tt.wantIDs[i]; got != want { t.Errorf("got ID %v, want %v", got, want) } } }) } } func TestFileImage_GetDescriptor(t *testing.T) { primPartDescr := rawDescriptor{ DataType: DataPartition, Used: true, ID: 1, GroupID: 1 | descrGroupMask, } p := partition{ Fstype: FsSquash, Parttype: PartPrimSys, Arch: hdrArch386, } if err := primPartDescr.setExtra(p); err != nil { t.Fatal(err) } ds := []rawDescriptor{ primPartDescr, { DataType: DataSignature, Used: true, ID: 2, GroupID: 1 | descrGroupMask, LinkedID: 1, }, { DataType: DataSignature, Used: true, ID: 3, GroupID: 0 | descrGroupMask, LinkedID: 1 | descrGroupMask, }, } f := &FileImage{ rds: ds, h: header{ DescriptorsTotal: int64(len(ds)), }, } f.populateMinIDs() tests := []struct { name string fns []DescriptorSelectorFunc wantErr error wantID uint32 }{ { name: "ID", fns: []DescriptorSelectorFunc{ WithID(1), }, wantID: 1, }, { name: "InvalidObjectID", fns: []DescriptorSelectorFunc{ WithID(0), }, wantErr: ErrInvalidObjectID, }, { name: "MultipleObjectsFound", fns: []DescriptorSelectorFunc{ WithGroupID(1), }, wantErr: ErrMultipleObjectsFound, }, { name: "ObjectNotFound", fns: []DescriptorSelectorFunc{ WithGroupID(2), }, wantErr: ErrObjectNotFound, }, { name: "PartitionType", fns: []DescriptorSelectorFunc{ WithPartitionType(PartPrimSys), }, wantID: 1, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { d, err := f.GetDescriptor(tt.fns...) if got, want := err, tt.wantErr; !errors.Is(got, want) { t.Fatalf("got error %v, want %v", got, want) } if got, want := d.ID(), tt.wantID; got != want { t.Errorf("got ID %v, want %v", got, want) } }) } } func TestFileImage_WithDescriptors(t *testing.T) { ds := []rawDescriptor{ { DataType: DataPartition, Used: true, ID: 1, GroupID: 1 | descrGroupMask, }, { DataType: DataSignature, Used: true, ID: 2, GroupID: 0 | descrGroupMask, LinkedID: 1 | descrGroupMask, }, { DataType: DataSignature, Used: false, ID: 3, GroupID: 0 | descrGroupMask, }, } tests := []struct { name string fn func(t *testing.T) func(d Descriptor) bool }{ { name: "ReturnTrue", fn: func(t *testing.T) func(d Descriptor) bool { return func(d Descriptor) bool { if id := d.ID(); id > 1 { t.Errorf("unexpected ID: %v", id) } return true } }, }, { name: "ReturnFalse", fn: func(t *testing.T) func(d Descriptor) bool { return func(d Descriptor) bool { if id := d.ID(); id > 2 { t.Errorf("unexpected ID: %v", id) } return false } }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { f := &FileImage{rds: ds} f.WithDescriptors(tt.fn(t)) }) } } sif-2.8.3/pkg/sif/sif.go000066400000000000000000000330431432675271000147710ustar00rootroot00000000000000// Copyright (c) 2018-2022, Sylabs Inc. All rights reserved. // Copyright (c) 2017, SingularityWare, LLC. All rights reserved. // Copyright (c) 2017, Yannick Cote All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. // Package sif implements data structures and routines to create // and access SIF files. // // Layout of a SIF file (example): // // .================================================. // | GLOBAL HEADER: Sifheader | // | - launch: "#!/usr/bin/env..." | // | - magic: "SIF_MAGIC" | // | - version: "1" | // | - arch: "4" | // | - uuid: b2659d4e-bd50-4ea5-bd17-eec5e54f918e | // | - ctime: 1504657553 | // | - mtime: 1504657653 | // | - ndescr: 3 | // | - descroff: 120 | --. // | - descrlen: 432 | | // | - dataoff: 4096 | | // | - datalen: 619362 | | // |------------------------------------------------| <-' // | DESCR[0]: Sifdeffile | // | - Sifcommon | // | - datatype: DATA_DEFFILE | // | - id: 1 | // | - groupid: 1 | // | - link: NONE | // | - fileoff: 4096 | --. // | - filelen: 222 | | // |------------------------------------------------| <-----. // | DESCR[1]: Sifpartition | | | // | - Sifcommon | | | // | - datatype: DATA_PARTITION | | | // | - id: 2 | | | // | - groupid: 1 | | | // | - link: NONE | | | // | - fileoff: 4318 | ----. | // | - filelen: 618496 | | | | // | - fstype: Squashfs | | | | // | - parttype: System | | | | // | - content: Linux | | | | // |------------------------------------------------| | | | // | DESCR[2]: Sifsignature | | | | // | - Sifcommon | | | | // | - datatype: DATA_SIGNATURE | | | | // | - id: 3 | | | | // | - groupid: NONE | | | | // | - link: 2 | ------' // | - fileoff: 622814 | ------. // | - filelen: 644 | | | | // | - hashtype: SHA384 | | | | // | - entity: @ | | | | // |------------------------------------------------| <-' | | // | Definition file data | | | // | . | | | // | . | | | // | . | | | // |------------------------------------------------| <---' | // | File system partition image | | // | . | | // | . | | // | . | | // |------------------------------------------------| <-----' // | Signed verification data | // | . | // | . | // | . | // `================================================' package sif import ( "bytes" "fmt" "io" "time" "github.com/google/uuid" ) // SIF header constants and quantities. const ( hdrLaunchLen = 32 // len("#!/usr/bin/env... ") hdrMagicLen = 10 // len("SIF_MAGIC") hdrVersionLen = 3 // len("99") ) var hdrMagic = [...]byte{'S', 'I', 'F', '_', 'M', 'A', 'G', 'I', 'C', '\x00'} // SpecVersion specifies a SIF specification version. type SpecVersion uint8 func (v SpecVersion) String() string { return fmt.Sprintf("%02d", v) } // bytes returns the value of b, formatted for direct inclusion in a SIF header. func (v SpecVersion) bytes() [hdrVersionLen]byte { var b [3]byte copy(b[:], fmt.Sprintf("%02d", v)) return b } // SIF specification versions. const ( version01 SpecVersion = iota + 1 ) // CurrentVersion specifies the current SIF specification version. const CurrentVersion = version01 const ( descrGroupMask = 0xf0000000 // groups start at that offset descrEntityLen = 256 // len("Joe Bloe ...") descrNameLen = 128 // descriptor name (string identifier) descrMaxPrivLen = 384 // size reserved for descriptor specific data ) // DataType represents the different SIF data object types stored in the image. type DataType int32 // List of supported SIF data types. const ( DataDeffile DataType = iota + 0x4001 // definition file data object DataEnvVar // environment variables data object DataLabels // JSON labels data object DataPartition // file system data object DataSignature // signing/verification data object DataGenericJSON // generic JSON meta-data DataGeneric // generic / raw data DataCryptoMessage // cryptographic message data object DataSBOM // software bill of materials ) // String returns a human-readable representation of t. func (t DataType) String() string { switch t { case DataDeffile: return "Def.FILE" case DataEnvVar: return "Env.Vars" case DataLabels: return "JSON.Labels" case DataPartition: return "FS" case DataSignature: return "Signature" case DataGenericJSON: return "JSON.Generic" case DataGeneric: return "Generic/Raw" case DataCryptoMessage: return "Cryptographic Message" case DataSBOM: return "SBOM" } return "Unknown" } // FSType represents the different SIF file system types found in partition data objects. type FSType int32 // List of supported file systems. const ( FsSquash FSType = iota + 1 // Squashfs file system, RDONLY FsExt3 // EXT3 file system, RDWR (deprecated) FsImmuObj // immutable data object archive FsRaw // raw data FsEncryptedSquashfs // Encrypted Squashfs file system, RDONLY ) // String returns a human-readable representation of t. func (t FSType) String() string { switch t { case FsSquash: return "Squashfs" case FsExt3: return "Ext3" case FsImmuObj: return "Archive" case FsRaw: return "Raw" case FsEncryptedSquashfs: return "Encrypted squashfs" } return "Unknown" } // PartType represents the different SIF container partition types (system and data). type PartType int32 // List of supported partition types. const ( PartSystem PartType = iota + 1 // partition hosts an operating system PartPrimSys // partition hosts the primary operating system PartData // partition hosts data only PartOverlay // partition hosts an overlay ) // String returns a human-readable representation of t. func (t PartType) String() string { switch t { case PartSystem: return "System" case PartPrimSys: return "*System" case PartData: return "Data" case PartOverlay: return "Overlay" } return "Unknown" } // hashType represents the different SIF hashing function types used to fingerprint data objects. type hashType int32 // List of supported hash functions. const ( hashSHA256 hashType = iota + 1 hashSHA384 hashSHA512 hashBLAKE2S hashBLAKE2B ) // FormatType represents the different formats used to store cryptographic message objects. type FormatType int32 // List of supported cryptographic message formats. const ( FormatOpenPGP FormatType = iota + 1 FormatPEM ) // String returns a human-readable representation of t. func (t FormatType) String() string { switch t { case FormatOpenPGP: return "OpenPGP" case FormatPEM: return "PEM" } return "Unknown" } // MessageType represents the different messages stored within cryptographic message objects. type MessageType int32 // List of supported cryptographic message formats. const ( // openPGP formatted messages. MessageClearSignature MessageType = 0x100 // PEM formatted messages. MessageRSAOAEP MessageType = 0x200 ) // String returns a human-readable representation of t. func (t MessageType) String() string { switch t { case MessageClearSignature: return "Clear Signature" case MessageRSAOAEP: return "RSA-OAEP" } return "Unknown" } // SBOMFormat represents the format used to store an SBOM object. type SBOMFormat int32 // List of supported SBOM formats. const ( SBOMFormatCycloneDXJSON SBOMFormat = iota + 1 // CycloneDX (JSON) SBOMFormatCycloneDXXML // CycloneDX (XML) SBOMFormatGitHubJSON // GitHub dependency snapshot (JSON) SBOMFormatSPDXJSON // SPDX (JSON) SBOMFormatSPDXRDF // SPDX (RDF/xml) SBOMFormatSPDXTagValue // SPDX (tag/value) SBOMFormatSPDXYAML // SPDX (YAML) SBOMFormatSyftJSON // Syft (JSON) ) // String returns a human-readable representation of f. func (f SBOMFormat) String() string { switch f { case SBOMFormatCycloneDXJSON: return "cyclonedx-json" case SBOMFormatCycloneDXXML: return "cyclonedx-xml" case SBOMFormatGitHubJSON: return "github-json" case SBOMFormatSPDXJSON: return "spdx-json" case SBOMFormatSPDXRDF: return "spdx-rdf" case SBOMFormatSPDXTagValue: return "spdx-tag-value" case SBOMFormatSPDXYAML: return "spdx-yaml" case SBOMFormatSyftJSON: return "syft-json" } return "unknown" } // header describes a loaded SIF file. type header struct { LaunchScript [hdrLaunchLen]byte Magic [hdrMagicLen]byte Version [hdrVersionLen]byte Arch archType ID uuid.UUID CreatedAt int64 ModifiedAt int64 DescriptorsFree int64 DescriptorsTotal int64 DescriptorsOffset int64 DescriptorsSize int64 DataOffset int64 DataSize int64 } // GetIntegrityReader returns an io.Reader that reads the integrity-protected fields from h. func (h header) GetIntegrityReader() io.Reader { return io.MultiReader( bytes.NewReader(h.LaunchScript[:]), bytes.NewReader(h.Magic[:]), bytes.NewReader(h.Version[:]), bytes.NewReader(h.ID[:]), ) } // ReadWriter describes the interface required to read and write SIF images. type ReadWriter interface { io.ReaderAt io.WriteSeeker Truncate(int64) error } // FileImage describes the representation of a SIF file in memory. type FileImage struct { rw ReadWriter // Backing storage for image. h header // Raw global header from image. rds []rawDescriptor // Raw descriptors from image. closeOnUnload bool // Close rw on Unload. minIDs map[uint32]uint32 // Minimum object IDs for each group ID. } // LaunchScript returns the image launch script. func (f *FileImage) LaunchScript() string { return string(bytes.TrimRight(f.h.LaunchScript[:], "\x00")) } // Version returns the SIF specification version of the image. func (f *FileImage) Version() string { return string(bytes.TrimRight(f.h.Version[:], "\x00")) } // PrimaryArch returns the primary CPU architecture of the image, or "unknown" if the primary CPU // architecture cannot be determined. func (f *FileImage) PrimaryArch() string { return f.h.Arch.GoArch() } // ID returns the ID of the image. func (f *FileImage) ID() string { return f.h.ID.String() } // CreatedAt returns the creation time of the image. func (f *FileImage) CreatedAt() time.Time { return time.Unix(f.h.CreatedAt, 0) } // ModifiedAt returns the last modification time of the image. func (f *FileImage) ModifiedAt() time.Time { return time.Unix(f.h.ModifiedAt, 0) } // DescriptorsFree returns the number of free descriptors in the image. func (f *FileImage) DescriptorsFree() int64 { return f.h.DescriptorsFree } // DescriptorsTotal returns the total number of descriptors in the image. func (f *FileImage) DescriptorsTotal() int64 { return f.h.DescriptorsTotal } // DescriptorsOffset returns the offset (in bytes) of the descriptors section in the image. func (f *FileImage) DescriptorsOffset() int64 { return f.h.DescriptorsOffset } // DescriptorsSize returns the size (in bytes) of the descriptors section in the image. func (f *FileImage) DescriptorsSize() int64 { return f.h.DescriptorsSize } // DataOffset returns the offset (in bytes) of the data section in the image. func (f *FileImage) DataOffset() int64 { return f.h.DataOffset } // DataSize returns the size (in bytes) of the data section in the image. func (f *FileImage) DataSize() int64 { return f.h.DataSize } // GetHeaderIntegrityReader returns an io.Reader that reads the integrity-protected fields from the // header of the image. func (f *FileImage) GetHeaderIntegrityReader() io.Reader { return f.h.GetIntegrityReader() } sif-2.8.3/pkg/sif/sif_test.go000066400000000000000000000044001432675271000160230ustar00rootroot00000000000000// Copyright (c) 2021, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package sif import ( "bytes" "io" "path/filepath" "testing" "github.com/google/uuid" "github.com/sebdah/goldie/v2" ) var corpus = filepath.Join("..", "..", "test", "images") func TestHeader_GetIntegrityReader(t *testing.T) { h := header{ Magic: hdrMagic, Version: CurrentVersion.bytes(), Arch: hdrArchAMD64, ID: uuid.UUID{0xb2, 0x65, 0x9d, 0x4e, 0xbd, 0x50, 0x4e, 0xa5, 0xbd, 0x17, 0xee, 0xc5, 0xe5, 0x4f, 0x91, 0x8e}, CreatedAt: 1504657553, ModifiedAt: 1504657653, } copy(h.LaunchScript[:], "#!/usr/bin/env run-singularity\n") tests := []struct { name string modFunc func(h header) header }{ {"LaunchScript", func(h header) header { copy(h.LaunchScript[:], "#!/usr/bin/env rm\n") return h }}, {"Magic", func(h header) header { copy(h.Magic[:], "BAD_MAGIC") return h }}, {"Version", func(h header) header { copy(h.Version[:], "02") return h }}, {"Arch", func(h header) header { h.Arch = hdrArchS390x return h }}, {"ID", func(h header) header { h.ID[0]++ return h }}, {"CreatedAt", func(h header) header { h.CreatedAt++ return h }}, {"ModifiedAt", func(h header) header { h.ModifiedAt++ return h }}, {"DescriptorsFree", func(h header) header { h.DescriptorsFree++ return h }}, {"DescriptorsTotal", func(h header) header { h.DescriptorsTotal++ return h }}, {"DescriptorsOffset", func(h header) header { h.DescriptorsOffset++ return h }}, {"DescriptorsSize", func(h header) header { h.DescriptorsSize++ return h }}, {"DataOffset", func(h header) header { h.DataOffset++ return h }}, {"DataSize", func(h header) header { h.DataSize++ return h }}, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { b := bytes.Buffer{} h := tt.modFunc(h) if _, err := io.Copy(&b, h.GetIntegrityReader()); err != nil { t.Fatal(err) } g := goldie.New(t, goldie.WithTestNameForDir(true)) g.Assert(t, tt.name, b.Bytes()) }) } } sif-2.8.3/pkg/sif/testdata/000077500000000000000000000000001432675271000154675ustar00rootroot00000000000000sif-2.8.3/pkg/sif/testdata/TestAddObject/000077500000000000000000000000001432675271000201465ustar00rootroot00000000000000sif-2.8.3/pkg/sif/testdata/TestAddObject/Empty.golden000066400000000000000000000766621432675271000224570ustar00rootroot00000000000000SIF_MAGIC0100 n n/0m}@} n nsif-2.8.3/pkg/sif/testdata/TestAddObject/EmptyAligned.golden000066400000000000000000000770021432675271000237300ustar00rootroot00000000000000SIF_MAGIC0100 n n/0m}R@~R n nsif-2.8.3/pkg/sif/testdata/TestAddObject/EmptyNotAligned.golden000066400000000000000000000766621432675271000244240ustar00rootroot00000000000000SIF_MAGIC0100 n n/0m}@} n nsif-2.8.3/pkg/sif/testdata/TestAddObject/ErrInsufficientCapacity.golden000066400000000000000000000100001432675271000261040ustar00rootroot00000000000000SIF_MAGIC0100 n nsif-2.8.3/pkg/sif/testdata/TestAddObject/ErrPrimaryPartition.golden000066400000000000000000001000021432675271000253170ustar00rootroot00000000000000SIF_MAGIC0101 n n/0m}R@R n n01sif-2.8.3/pkg/sif/testdata/TestAddObject/NotEmpty.golden000066400000000000000000001000021432675271000231100ustar00rootroot00000000000000SIF_MAGIC0101 n n.0m}R@} n n@P n n01sif-2.8.3/pkg/sif/testdata/TestAddObject/NotEmptyAligned.golden000066400000000000000000000770021432675271000244110ustar00rootroot00000000000000SIF_MAGIC0101 n n.0m}R@} n n@~P n n01sif-2.8.3/pkg/sif/testdata/TestAddObject/NotEmptyNotAligned.golden000066400000000000000000000766641432675271000251070ustar00rootroot00000000000000SIF_MAGIC0101 n n.0m}@} n n@} n n01sif-2.8.3/pkg/sif/testdata/TestAddObject/WithTime.golden000066400000000000000000000766621432675271000231130ustar00rootroot00000000000000SIF_MAGIC0100 nЉm8/0m}@}Љm8Љm8sif-2.8.3/pkg/sif/testdata/TestCreateContainer/000077500000000000000000000000001432675271000213755ustar00rootroot00000000000000sif-2.8.3/pkg/sif/testdata/TestCreateContainer/Empty.golden000066400000000000000000000766601432675271000237040ustar00rootroot00000000000000SIF_MAGIC0100 n n00m}sif-2.8.3/pkg/sif/testdata/TestCreateContainer/EmptyCloseOnUnload.golden000066400000000000000000000766601432675271000263320ustar00rootroot00000000000000SIF_MAGIC0100 n n00m}sif-2.8.3/pkg/sif/testdata/TestCreateContainer/EmptyDescriptorLimitedCapacity.golden000066400000000000000000000111111432675271000307050ustar00rootroot00000000000000SIF_MAGIC0100 n nIIsif-2.8.3/pkg/sif/testdata/TestCreateContainer/EmptyLaunchScript.golden000066400000000000000000000766601432675271000262240ustar00rootroot00000000000000#!/usr/bin/env launch-script SIF_MAGIC0100 n n00m}sif-2.8.3/pkg/sif/testdata/TestCreateContainer/EmptyWithID.golden000066400000000000000000000766601432675271000247550ustar00rootroot00000000000000SIF_MAGIC0100 C6Dpt n n00m}sif-2.8.3/pkg/sif/testdata/TestCreateContainer/EmptyWithTime.golden000066400000000000000000000766601432675271000253570ustar00rootroot00000000000000SIF_MAGIC0100Љm8Љm800m}sif-2.8.3/pkg/sif/testdata/TestCreateContainer/OneDescriptor.golden000066400000000000000000000766621432675271000253700ustar00rootroot00000000000000SIF_MAGIC0100 n n/0m}@} n nsif-2.8.3/pkg/sif/testdata/TestCreateContainer/TwoDescriptors.golden000066400000000000000000001000021432675271000255530ustar00rootroot00000000000000SIF_MAGIC0101 n n.0m}R@} n n@P n n01sif-2.8.3/pkg/sif/testdata/TestCreateContainer/TwoDescriptorsAligned.golden000066400000000000000000000766661432675271000270730ustar00rootroot00000000000000SIF_MAGIC0101 n n.0m}@} n n@} n n01sif-2.8.3/pkg/sif/testdata/TestCreateContainer/TwoDescriptorsNotAligned.golden000066400000000000000000000766641432675271000275520ustar00rootroot00000000000000SIF_MAGIC0101 n n.0m}@} n n@} n n01sif-2.8.3/pkg/sif/testdata/TestCreateContainerAtPath/000077500000000000000000000000001432675271000224775ustar00rootroot00000000000000sif-2.8.3/pkg/sif/testdata/TestCreateContainerAtPath/Empty.golden000066400000000000000000000766601432675271000250060ustar00rootroot00000000000000SIF_MAGIC0100 n n00m}sif-2.8.3/pkg/sif/testdata/TestCreateContainerAtPath/EmptyDescriptorLimitedCapacity.golden000066400000000000000000000111111432675271000320070ustar00rootroot00000000000000SIF_MAGIC0100 n nIIsif-2.8.3/pkg/sif/testdata/TestCreateContainerAtPath/OneDescriptor.golden000066400000000000000000000766621432675271000264720ustar00rootroot00000000000000SIF_MAGIC0100 n n/0m}@} n nsif-2.8.3/pkg/sif/testdata/TestCreateContainerAtPath/TwoDescriptors.golden000066400000000000000000001000021432675271000266550ustar00rootroot00000000000000SIF_MAGIC0101 n n.0m}R@} n n@P n n01sif-2.8.3/pkg/sif/testdata/TestCreateContainerAtPath/TwoDescriptorsAligned.golden000066400000000000000000000766661432675271000301750ustar00rootroot00000000000000SIF_MAGIC0101 n n.0m}@} n n@} n n01sif-2.8.3/pkg/sif/testdata/TestCreateContainerAtPath/TwoDescriptorsNotAligned.golden000066400000000000000000000766641432675271000306540ustar00rootroot00000000000000SIF_MAGIC0101 n n.0m}@} n n@} n n01sif-2.8.3/pkg/sif/testdata/TestDeleteObject/000077500000000000000000000000001432675271000206605ustar00rootroot00000000000000sif-2.8.3/pkg/sif/testdata/TestDeleteObject/Compact.golden000066400000000000000000000766601432675271000234570ustar00rootroot00000000000000SIF_MAGIC0100 n n00m}sif-2.8.3/pkg/sif/testdata/TestDeleteObject/ErrObjectNotFound.golden000066400000000000000000000766601432675271000254250ustar00rootroot00000000000000SIF_MAGIC0100 n n00m}sif-2.8.3/pkg/sif/testdata/TestDeleteObject/PrimaryPartition.golden000066400000000000000000001000021432675271000253600ustar00rootroot00000000000000SIF_MAGIC0100 n n00m}Rsif-2.8.3/pkg/sif/testdata/TestDeleteObject/WithTime.golden000066400000000000000000000766621432675271000236250ustar00rootroot00000000000000SIF_MAGIC0100 nЉm800m}sif-2.8.3/pkg/sif/testdata/TestDeleteObject/Zero.golden000066400000000000000000000766621432675271000230120ustar00rootroot00000000000000SIF_MAGIC0100 n n00m}sif-2.8.3/pkg/sif/testdata/TestDeleteObject/ZeroCompact.golden000066400000000000000000000766601432675271000243170ustar00rootroot00000000000000SIF_MAGIC0100 n n00m}sif-2.8.3/pkg/sif/testdata/TestDescriptor_GetIntegrityReader/000077500000000000000000000000001432675271000242665ustar00rootroot00000000000000sif-2.8.3/pkg/sif/testdata/TestDescriptor_GetIntegrityReader/CreatedAt.golden000066400000000000000000000010551432675271000273150ustar00rootroot00000000000000@@YGOOD_NAMEGOOD_EXTRAsif-2.8.3/pkg/sif/testdata/TestDescriptor_GetIntegrityReader/DataType.golden000066400000000000000000000010551432675271000271740ustar00rootroot00000000000000@@YGOOD_NAMEGOOD_EXTRAsif-2.8.3/pkg/sif/testdata/TestDescriptor_GetIntegrityReader/Extra.golden000066400000000000000000000010551432675271000265440ustar00rootroot00000000000000@@YGOOD_NAMEBAD_EXTRAAsif-2.8.3/pkg/sif/testdata/TestDescriptor_GetIntegrityReader/GID.golden000066400000000000000000000010551432675271000260640ustar00rootroot00000000000000@@YGOOD_NAMEGOOD_EXTRAsif-2.8.3/pkg/sif/testdata/TestDescriptor_GetIntegrityReader/GroupID.golden000066400000000000000000000010551432675271000267720ustar00rootroot00000000000000@@YGOOD_NAMEGOOD_EXTRAsif-2.8.3/pkg/sif/testdata/TestDescriptor_GetIntegrityReader/ID.golden000066400000000000000000000010551432675271000257550ustar00rootroot00000000000000@@YGOOD_NAMEGOOD_EXTRAsif-2.8.3/pkg/sif/testdata/TestDescriptor_GetIntegrityReader/LinkedID.golden000066400000000000000000000010551432675271000271040ustar00rootroot00000000000000@@YGOOD_NAMEGOOD_EXTRAsif-2.8.3/pkg/sif/testdata/TestDescriptor_GetIntegrityReader/ModifiedAt.golden000066400000000000000000000010551432675271000274660ustar00rootroot00000000000000@@YGOOD_NAMEGOOD_EXTRAsif-2.8.3/pkg/sif/testdata/TestDescriptor_GetIntegrityReader/Name.golden000066400000000000000000000010551432675271000263410ustar00rootroot00000000000000@@YBAD_NAMEEGOOD_EXTRAsif-2.8.3/pkg/sif/testdata/TestDescriptor_GetIntegrityReader/Offset.golden000066400000000000000000000010551432675271000267070ustar00rootroot00000000000000@@YGOOD_NAMEGOOD_EXTRAsif-2.8.3/pkg/sif/testdata/TestDescriptor_GetIntegrityReader/RelativeID.golden000066400000000000000000000010551432675271000274510ustar00rootroot00000000000000@@YGOOD_NAMEGOOD_EXTRAsif-2.8.3/pkg/sif/testdata/TestDescriptor_GetIntegrityReader/Size.golden000066400000000000000000000010551432675271000263730ustar00rootroot00000000000000@@YGOOD_NAMEGOOD_EXTRAsif-2.8.3/pkg/sif/testdata/TestDescriptor_GetIntegrityReader/SizeWithPadding.golden000066400000000000000000000010551432675271000305160ustar00rootroot00000000000000@@YGOOD_NAMEGOOD_EXTRAsif-2.8.3/pkg/sif/testdata/TestDescriptor_GetIntegrityReader/UID.golden000066400000000000000000000010551432675271000261020ustar00rootroot00000000000000@@YGOOD_NAMEGOOD_EXTRAsif-2.8.3/pkg/sif/testdata/TestDescriptor_GetIntegrityReader/Used.golden000066400000000000000000000010551432675271000263610ustar00rootroot00000000000000@@YGOOD_NAMEGOOD_EXTRAsif-2.8.3/pkg/sif/testdata/TestHeader_GetIntegrityReader/000077500000000000000000000000001432675271000233405ustar00rootroot00000000000000sif-2.8.3/pkg/sif/testdata/TestHeader_GetIntegrityReader/Arch.golden000066400000000000000000000000751432675271000254110ustar00rootroot00000000000000#!/usr/bin/env run-singularity SIF_MAGIC01eNPNOsif-2.8.3/pkg/sif/testdata/TestHeader_GetIntegrityReader/CreatedAt.golden000066400000000000000000000000751432675271000263700ustar00rootroot00000000000000#!/usr/bin/env run-singularity SIF_MAGIC01eNPNOsif-2.8.3/pkg/sif/testdata/TestHeader_GetIntegrityReader/DataOffset.golden000066400000000000000000000000751432675271000265540ustar00rootroot00000000000000#!/usr/bin/env run-singularity SIF_MAGIC01eNPNOsif-2.8.3/pkg/sif/testdata/TestHeader_GetIntegrityReader/DataSize.golden000066400000000000000000000000751432675271000262400ustar00rootroot00000000000000#!/usr/bin/env run-singularity SIF_MAGIC01eNPNOsif-2.8.3/pkg/sif/testdata/TestHeader_GetIntegrityReader/DescriptorsFree.golden000066400000000000000000000000751432675271000276370ustar00rootroot00000000000000#!/usr/bin/env run-singularity SIF_MAGIC01eNPNOsif-2.8.3/pkg/sif/testdata/TestHeader_GetIntegrityReader/DescriptorsOffset.golden000066400000000000000000000000751432675271000302040ustar00rootroot00000000000000#!/usr/bin/env run-singularity SIF_MAGIC01eNPNOsif-2.8.3/pkg/sif/testdata/TestHeader_GetIntegrityReader/DescriptorsSize.golden000066400000000000000000000000751432675271000276700ustar00rootroot00000000000000#!/usr/bin/env run-singularity SIF_MAGIC01eNPNOsif-2.8.3/pkg/sif/testdata/TestHeader_GetIntegrityReader/DescriptorsTotal.golden000066400000000000000000000000751432675271000300410ustar00rootroot00000000000000#!/usr/bin/env run-singularity SIF_MAGIC01eNPNOsif-2.8.3/pkg/sif/testdata/TestHeader_GetIntegrityReader/ID.golden000066400000000000000000000000751432675271000250300ustar00rootroot00000000000000#!/usr/bin/env run-singularity SIF_MAGIC01eNPNOsif-2.8.3/pkg/sif/testdata/TestHeader_GetIntegrityReader/LaunchScript.golden000066400000000000000000000000751432675271000271330ustar00rootroot00000000000000#!/usr/bin/env rm -singularity SIF_MAGIC01eNPNOsif-2.8.3/pkg/sif/testdata/TestHeader_GetIntegrityReader/Magic.golden000066400000000000000000000000751432675271000255540ustar00rootroot00000000000000#!/usr/bin/env run-singularity BAD_MAGIC01eNPNOsif-2.8.3/pkg/sif/testdata/TestHeader_GetIntegrityReader/ModifiedAt.golden000066400000000000000000000000751432675271000265410ustar00rootroot00000000000000#!/usr/bin/env run-singularity SIF_MAGIC01eNPNOsif-2.8.3/pkg/sif/testdata/TestHeader_GetIntegrityReader/Version.golden000066400000000000000000000000751432675271000261610ustar00rootroot00000000000000#!/usr/bin/env run-singularity SIF_MAGIC02eNPNOsif-2.8.3/pkg/sif/testdata/TestNewDescriptorInput/000077500000000000000000000000001432675271000221375ustar00rootroot00000000000000sif-2.8.3/pkg/sif/testdata/TestNewDescriptorInput/Empty.golden000066400000000000000000000011111432675271000244210ustar00rootroot00000000000000@ n nsif-2.8.3/pkg/sif/testdata/TestNewDescriptorInput/OptCryptoMessageMetadata.golden000066400000000000000000000011111432675271000302340ustar00rootroot00000000000000@ n nsif-2.8.3/pkg/sif/testdata/TestNewDescriptorInput/OptGroupID.golden000066400000000000000000000011111432675271000253170ustar00rootroot00000000000000@ n nsif-2.8.3/pkg/sif/testdata/TestNewDescriptorInput/OptLinkedGroupID.golden000066400000000000000000000011111432675271000264460ustar00rootroot00000000000000@ n nsif-2.8.3/pkg/sif/testdata/TestNewDescriptorInput/OptLinkedID.golden000066400000000000000000000011111432675271000254310ustar00rootroot00000000000000@ n nsif-2.8.3/pkg/sif/testdata/TestNewDescriptorInput/OptNoGroup.golden000066400000000000000000000011111432675271000253770ustar00rootroot00000000000000@ n nsif-2.8.3/pkg/sif/testdata/TestNewDescriptorInput/OptObjectAlignment.golden000066400000000000000000000011111432675271000270530ustar00rootroot00000000000000@ n nsif-2.8.3/pkg/sif/testdata/TestNewDescriptorInput/OptObjectName.golden000066400000000000000000000011111432675271000260150ustar00rootroot00000000000000@ n nnamesif-2.8.3/pkg/sif/testdata/TestNewDescriptorInput/OptObjectTime.golden000066400000000000000000000011111432675271000260330ustar00rootroot00000000000000@Љm8Љm8sif-2.8.3/pkg/sif/testdata/TestNewDescriptorInput/OptPartitionMetadata.golden000066400000000000000000000011111432675271000274200ustar00rootroot00000000000000@ n n01sif-2.8.3/pkg/sif/testdata/TestNewDescriptorInput/OptSBOMMetadata.golden000066400000000000000000000011111432675271000262070ustar00rootroot00000000000000 @ n nsif-2.8.3/pkg/sif/testdata/TestNewDescriptorInput/OptSignatureMetadata.golden000066400000000000000000000011111432675271000274100ustar00rootroot00000000000000@ n n\ XK 'sif-2.8.3/pkg/sif/testdata/TestSetPrimPart/000077500000000000000000000000001432675271000205415ustar00rootroot00000000000000sif-2.8.3/pkg/sif/testdata/TestSetPrimPart/ErrObjectNotFound.golden000066400000000000000000000766601432675271000253060ustar00rootroot00000000000000SIF_MAGIC0100 n n00m}sif-2.8.3/pkg/sif/testdata/TestSetPrimPart/One.golden000066400000000000000000001000021432675271000224450ustar00rootroot00000000000000SIF_MAGIC0101 n n/0m}R@R n n386sif-2.8.3/pkg/sif/testdata/TestSetPrimPart/Two.golden000066400000000000000000001100021432675271000224760ustar00rootroot00000000000000SIF_MAGIC0102 n n.0m}R@R n n01@ n namdsif-2.8.3/pkg/sif/testdata/TestSetPrimPart/WithTime.golden000066400000000000000000001000021432675271000234560ustar00rootroot00000000000000SIF_MAGIC0101 nЉm8/0m}R@R n n386sif-2.8.3/pkg/siftool/000077500000000000000000000000001432675271000145545ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/add.go000066400000000000000000000167441432675271000156470ustar00rootroot00000000000000// Copyright (c) 2019-2022, Sylabs Inc. All rights reserved. // Copyright (c) 2017, SingularityWare, LLC. All rights reserved. // Copyright (c) 2017, Yannick Cote All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package siftool import ( "crypto" "encoding/hex" "errors" "fmt" "os" "strings" "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/sylabs/sif/v2/pkg/sif" ) var ( dataType *int partType *int32 partFS *int32 partArch *int32 signHash *int32 signEntity *string sbomFormat *string groupID *uint32 linkID *uint32 alignment *int name *string ) // getAddExamples returns add command examples based on rootCmd. func getAddExamples(rootPath string) string { examples := []string{ rootPath + " add image.sif recipe.def -datatype 1", rootPath + " add image.sif rootfs.squashfs --datatype 4 --parttype 1 --partfs 1 ----partarch 2", rootPath + " add image.sif signature.bin -datatype 5 --signentity 433FE984155206BD962725E20E8713472A879943 --signhash 1", } return strings.Join(examples, "\n") } // addFlags declares the command line flags for the add command. func addFlags(fs *pflag.FlagSet) { dataType = fs.Int("datatype", 0, `the type of data to add [NEEDED, no default]: 1-Deffile, 2-EnvVar, 3-Labels, 4-Partition, 5-Signature, 6-GenericJSON, 7-Generic, 8-CryptoMessage, 9-SBOM`) partType = fs.Int32("parttype", 0, `the type of partition (with -datatype 4-Partition) [NEEDED, no default]: 1-System, 2-PrimSys, 3-Data, 4-Overlay`) partFS = fs.Int32("partfs", 0, `the filesystem used (with -datatype 4-Partition) [NEEDED, no default]: 1-Squash, 2-Ext3, 3-ImmuObj, 4-Raw`) partArch = fs.Int32("partarch", 0, `the main architecture used (with -datatype 4-Partition) [NEEDED, no default]: 1-386, 2-amd64, 3-arm, 4-arm64, 5-ppc64, 6-ppc64le, 7-mips, 8-mipsle, 9-mips64, 10-mips64le, 11-s390x, 12-riscv64`) signHash = fs.Int32("signhash", 0, `the signature hash used (with -datatype 5-Signature) [NEEDED, no default]: 1-SHA256, 2-SHA384, 3-SHA512, 4-BLAKE2s_256, 5-BLAKE2b_256`) signEntity = fs.String("signentity", "", `the entity that signs (with -datatype 5-Signature) [NEEDED, no default]: example: 433FE984155206BD962725E20E8713472A879943`) sbomFormat = fs.String("sbomformat", "", `the SBOM format (with -datatype 9-sbom): cyclonedx-json, cyclonedx-xml, github-json, spdx-json, spdx-rdf, spdx-tag-value, spdx-yaml, syft-json`) groupID = fs.Uint32("groupid", 0, "set groupid [default: 0]") linkID = fs.Uint32("link", 0, "set link pointer [default: 0]") alignment = fs.Int("alignment", 0, "set alignment [default: 4096 with -datatype 4-Partition, 0 otherwise]") name = fs.String("filename", "", "set logical filename/handle [default: input filename]") } var errDataTypeRequired = errors.New("-datatype flag is required with a valid range") // getDataType returns the data type corresponding to input. func getDataType() (sif.DataType, error) { switch *dataType { case 1: return sif.DataDeffile, nil case 2: return sif.DataEnvVar, nil case 3: return sif.DataLabels, nil case 4: return sif.DataPartition, nil case 5: return sif.DataSignature, nil case 6: return sif.DataGenericJSON, nil case 7: return sif.DataGeneric, nil case 8: return sif.DataCryptoMessage, nil case 9: return sif.DataSBOM, nil default: return 0, errDataTypeRequired } } func getArch() string { switch *partArch { case 1: return "386" case 2: return "amd64" case 3: return "arm" case 4: return "arm64" case 5: return "ppc64" case 6: return "ppc64le" case 7: return "mips" case 8: return "mipsle" case 9: return "mips64" case 10: return "mips64le" case 11: return "s390x" case 12: return "riscv64" default: return "unknown" } } var errInvalidHashType = errors.New("invalid hash type") func getHashType() (crypto.Hash, error) { switch *signHash { case 1: return crypto.SHA256, nil case 2: return crypto.SHA384, nil case 3: return crypto.SHA512, nil case 4: return crypto.BLAKE2s_256, nil case 5: return crypto.BLAKE2b_256, nil default: return 0, fmt.Errorf("%w: %v", errInvalidHashType, *signHash) } } var errInvalidSBOMFormat = errors.New("invalid SBOM format") func getSBOMFormat() (sif.SBOMFormat, error) { switch *sbomFormat { case "cyclonedx-json": return sif.SBOMFormatCycloneDXJSON, nil case "cyclonedx-xml": return sif.SBOMFormatCycloneDXXML, nil case "github", "github-json": return sif.SBOMFormatGitHubJSON, nil case "spdx-json": return sif.SBOMFormatSPDXJSON, nil case "spdx-rdf": return sif.SBOMFormatSPDXRDF, nil case "spdx-tag-value": return sif.SBOMFormatSPDXTagValue, nil case "spdx-yaml": return sif.SBOMFormatSPDXYAML, nil case "syft-json": return sif.SBOMFormatSyftJSON, nil default: return 0, fmt.Errorf("%w: %v", errInvalidSBOMFormat, *sbomFormat) } } var ( errPartitionArgs = errors.New("with partition datatype, -partfs, -parttype and -partarch must be passed") errInvalidFingerprintLength = errors.New("invalid signing entity fingerprint length") errSBOMArgs = errors.New("with SBOM datatype, -sbomformat must be passed") ) func getOptions(dt sif.DataType, fs *pflag.FlagSet) ([]sif.DescriptorInputOpt, error) { var opts []sif.DescriptorInputOpt if *groupID == 0 { opts = append(opts, sif.OptNoGroup()) } else { opts = append(opts, sif.OptGroupID(*groupID)) } if fs.Changed("link") { opts = append(opts, sif.OptLinkedID(*linkID)) } if fs.Changed("alignment") { opts = append(opts, sif.OptObjectAlignment(*alignment)) } if fs.Changed("filename") { opts = append(opts, sif.OptObjectName(*name)) } switch dt { case sif.DataPartition: if *partType == 0 || *partFS == 0 || *partArch == 0 { return nil, errPartitionArgs } opts = append(opts, sif.OptPartitionMetadata(sif.FSType(*partFS), sif.PartType(*partType), getArch()), ) case sif.DataSignature: b, err := hex.DecodeString(*signEntity) if err != nil { return nil, fmt.Errorf("failed to decode signing entity fingerprint: %w", err) } ht, err := getHashType() if err != nil { return nil, err } fp := make([]byte, 20) if got, want := len(b), len(fp); got != want { return nil, fmt.Errorf("%w: got %v, want %v", errInvalidFingerprintLength, got, want) } copy(fp, b) opts = append(opts, sif.OptSignatureMetadata(ht, fp)) case sif.DataSBOM: if *sbomFormat == "" { return nil, errSBOMArgs } f, err := getSBOMFormat() if err != nil { return nil, err } opts = append(opts, sif.OptSBOMMetadata(f)) } return opts, nil } // getAdd returns a command that adds a data object to a SIF. func (c *command) getAdd() *cobra.Command { cmd := &cobra.Command{ Use: "add ", Short: "Add data object", Long: "Add a data object to a SIF image.", Example: getAddExamples(c.opts.rootPath), Args: cobra.ExactArgs(2), } addFlags(cmd.Flags()) cmd.PreRunE = c.initApp cmd.RunE = func(cmd *cobra.Command, args []string) error { dt, err := getDataType() if err != nil { return err } f, err := os.Open(args[1]) if err != nil { return err } defer f.Close() opts, err := getOptions(dt, cmd.Flags()) if err != nil { return err } return c.app.Add(args[0], dt, f, opts...) } return cmd } sif-2.8.3/pkg/siftool/add_test.go000066400000000000000000000020551432675271000166740ustar00rootroot00000000000000// Copyright (c) 2021-2022, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package siftool import ( "path/filepath" "testing" ) func Test_command_getAdd(t *testing.T) { tests := []struct { name string opts commandOpts flags []string }{ { name: "DataPartition", flags: []string{ "--datatype", "4", "--parttype", "2", "--partfs", "1", "--partarch", "1", }, }, { name: "DataSignature", flags: []string{ "--datatype", "5", "--signhash", "1", "--signentity", "433FE984155206BD962725E20E8713472A879943", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &command{opts: tt.opts} cmd := c.getAdd() args := []string{ makeTestSIF(t, false), filepath.Join("testdata", "input", "input.bin"), } args = append(args, tt.flags...) runCommand(t, cmd, args, nil) }) } } sif-2.8.3/pkg/siftool/del.go000066400000000000000000000021151432675271000156460ustar00rootroot00000000000000// Copyright (c) 2018-2021, Sylabs Inc. All rights reserved. // Copyright (c) 2017, SingularityWare, LLC. All rights reserved. // Copyright (c) 2017, Yannick Cote All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package siftool import ( "fmt" "strconv" "github.com/spf13/cobra" ) // getDel returns a command that deletes a data object from a SIF. func (c *command) getDel() *cobra.Command { return &cobra.Command{ Use: "del ", Short: "Delete data object", Long: "Delete a data object from a SIF image.", Example: c.opts.rootPath + " del 1 image.sif", Args: cobra.ExactArgs(2), PreRunE: c.initApp, RunE: func(cmd *cobra.Command, args []string) error { id, err := strconv.ParseUint(args[0], 10, 32) if err != nil { return fmt.Errorf("while converting id: %w", err) } return c.app.Del(args[1], uint32(id)) }, DisableFlagsInUseLine: true, } } sif-2.8.3/pkg/siftool/del_test.go000066400000000000000000000011571432675271000167120ustar00rootroot00000000000000// Copyright (c) 2021-2022, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package siftool import ( "testing" ) func Test_command_getDel(t *testing.T) { tests := []struct { name string opts commandOpts }{ { name: "OK", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &command{opts: tt.opts} cmd := c.getDel() runCommand(t, cmd, []string{"1", makeTestSIF(t, true)}, nil) }) } } sif-2.8.3/pkg/siftool/dump.go000066400000000000000000000022371432675271000160540ustar00rootroot00000000000000// Copyright (c) 2018-2021, Sylabs Inc. All rights reserved. // Copyright (c) 2018, Divya Cote All rights reserved. // Copyright (c) 2017, SingularityWare, LLC. All rights reserved. // Copyright (c) 2017, Yannick Cote All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package siftool import ( "fmt" "strconv" "github.com/spf13/cobra" ) // getDump returns a command that dumps a data object from a SIF file. func (c *command) getDump() *cobra.Command { return &cobra.Command{ Use: "dump ", Short: "Dump data object", Long: "Dump a data object from a SIF image.", Example: c.opts.rootPath + " dump 1 image.sif", Args: cobra.ExactArgs(2), PreRunE: c.initApp, RunE: func(cmd *cobra.Command, args []string) error { id, err := strconv.ParseUint(args[0], 10, 32) if err != nil { return fmt.Errorf("while converting id: %w", err) } return c.app.Dump(args[1], uint32(id)) }, DisableFlagsInUseLine: true, } } sif-2.8.3/pkg/siftool/dump_test.go000066400000000000000000000016321432675271000171110ustar00rootroot00000000000000// Copyright (c) 2021-2022, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package siftool import ( "path/filepath" "testing" ) func Test_command_getDump(t *testing.T) { tests := []struct { name string opts commandOpts id string path string }{ { name: "One", id: "1", path: filepath.Join(corpus, "one-group-signed.sif"), }, { name: "Two", path: filepath.Join(corpus, "one-group-signed.sif"), id: "2", }, { name: "Three", path: filepath.Join(corpus, "one-group-signed.sif"), id: "3", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &command{opts: tt.opts} cmd := c.getDump() runCommand(t, cmd, []string{tt.id, tt.path}, nil) }) } } sif-2.8.3/pkg/siftool/header.go000066400000000000000000000020061432675271000163310ustar00rootroot00000000000000// Copyright (c) 2018-2021, Sylabs Inc. All rights reserved. // Copyright (c) 2018, Divya Cote All rights reserved. // Copyright (c) 2017, SingularityWare, LLC. All rights reserved. // Copyright (c) 2017, Yannick Cote All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package siftool import ( "github.com/spf13/cobra" ) // getHeader returns a command that displays the global SIF header. func (c *command) getHeader() *cobra.Command { return &cobra.Command{ Use: "header ", Short: "Display global header", Long: "Display global header from a SIF image.", Example: c.opts.rootPath + " header image.sif", Args: cobra.ExactArgs(1), PreRunE: c.initApp, RunE: func(cmd *cobra.Command, args []string) error { return c.app.Header(args[0]) }, DisableFlagsInUseLine: true, } } sif-2.8.3/pkg/siftool/header_test.go000066400000000000000000000032721432675271000173760ustar00rootroot00000000000000// Copyright (c) 2021-2022, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. //nolint:dupl package siftool import ( "path/filepath" "testing" ) func Test_command_getHeader(t *testing.T) { tests := []struct { name string opts commandOpts path string }{ { name: "Empty", path: filepath.Join(corpus, "empty.sif"), }, { name: "OneGroup", path: filepath.Join(corpus, "one-group.sif"), }, { name: "OneGroupSigned", path: filepath.Join(corpus, "one-group-signed.sif"), }, { name: "OneGroupSignedLegacy", path: filepath.Join(corpus, "one-group-signed-legacy.sif"), }, { name: "OneGroupSignedLegacyAll", path: filepath.Join(corpus, "one-group-signed-legacy-all.sif"), }, { name: "OneGroupSignedLegacyGroup", path: filepath.Join(corpus, "one-group-signed-legacy-group.sif"), }, { name: "TwoGroups", path: filepath.Join(corpus, "two-groups.sif"), }, { name: "TwoGroupsSigned", path: filepath.Join(corpus, "two-groups-signed.sif"), }, { name: "TwoGroupsSignedLegacy", path: filepath.Join(corpus, "two-groups-signed-legacy.sif"), }, { name: "TwoGroupsSignedLegacyAll", path: filepath.Join(corpus, "two-groups-signed-legacy-all.sif"), }, { name: "TwoGroupsSignedLegacyGroup", path: filepath.Join(corpus, "two-groups-signed-legacy-group.sif"), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &command{opts: tt.opts} cmd := c.getHeader() runCommand(t, cmd, []string{tt.path}, nil) }) } } sif-2.8.3/pkg/siftool/info.go000066400000000000000000000023331432675271000160370ustar00rootroot00000000000000// Copyright (c) 2018-2021, Sylabs Inc. All rights reserved. // Copyright (c) 2018, Divya Cote All rights reserved. // Copyright (c) 2017, SingularityWare, LLC. All rights reserved. // Copyright (c) 2017, Yannick Cote All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package siftool import ( "fmt" "strconv" "github.com/spf13/cobra" ) // getInfo returns a command that displays detailed information of an object descriptor from a SIF // image. func (c *command) getInfo() *cobra.Command { return &cobra.Command{ Use: "info ", Short: "Display data object info", Long: "Display info about a data object from a SIF image.", Example: c.opts.rootPath + " info 1 image.sif", Args: cobra.ExactArgs(2), PreRunE: c.initApp, RunE: func(cmd *cobra.Command, args []string) error { id, err := strconv.ParseUint(args[0], 10, 32) if err != nil { return fmt.Errorf("while converting id: %w", err) } return c.app.Info(args[1], uint32(id)) }, DisableFlagsInUseLine: true, } } sif-2.8.3/pkg/siftool/info_test.go000066400000000000000000000016321432675271000170770ustar00rootroot00000000000000// Copyright (c) 2021-2022, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package siftool import ( "path/filepath" "testing" ) func Test_command_getInfo(t *testing.T) { tests := []struct { name string opts commandOpts id string path string }{ { name: "One", id: "1", path: filepath.Join(corpus, "one-group-signed.sif"), }, { name: "Two", id: "2", path: filepath.Join(corpus, "one-group-signed.sif"), }, { name: "Three", id: "3", path: filepath.Join(corpus, "one-group-signed.sif"), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &command{opts: tt.opts} cmd := c.getInfo() runCommand(t, cmd, []string{tt.id, tt.path}, nil) }) } } sif-2.8.3/pkg/siftool/list.go000066400000000000000000000017771432675271000160720ustar00rootroot00000000000000// Copyright (c) 2018-2021, Sylabs Inc. All rights reserved. // Copyright (c) 2018, Divya Cote All rights reserved. // Copyright (c) 2017, SingularityWare, LLC. All rights reserved. // Copyright (c) 2017, Yannick Cote All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package siftool import ( "github.com/spf13/cobra" ) // getList returns a command that lists object descriptors from a SIF image. func (c *command) getList() *cobra.Command { return &cobra.Command{ Use: "list ", Short: "List data objects", Long: "List data objects from a SIF image.", Example: c.opts.rootPath + " list image.sif", Args: cobra.ExactArgs(1), PreRunE: c.initApp, RunE: func(cmd *cobra.Command, args []string) error { return c.app.List(args[0]) }, DisableFlagsInUseLine: true, } } sif-2.8.3/pkg/siftool/list_test.go000066400000000000000000000032661432675271000171240ustar00rootroot00000000000000// Copyright (c) 2021-2022, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. //nolint:dupl package siftool import ( "path/filepath" "testing" ) func Test_command_getList(t *testing.T) { tests := []struct { name string opts commandOpts path string }{ { name: "Empty", path: filepath.Join(corpus, "empty.sif"), }, { name: "OneGroup", path: filepath.Join(corpus, "one-group.sif"), }, { name: "OneGroupSigned", path: filepath.Join(corpus, "one-group-signed.sif"), }, { name: "OneGroupSignedLegacy", path: filepath.Join(corpus, "one-group-signed-legacy.sif"), }, { name: "OneGroupSignedLegacyAll", path: filepath.Join(corpus, "one-group-signed-legacy-all.sif"), }, { name: "OneGroupSignedLegacyGroup", path: filepath.Join(corpus, "one-group-signed-legacy-group.sif"), }, { name: "TwoGroups", path: filepath.Join(corpus, "two-groups.sif"), }, { name: "TwoGroupsSigned", path: filepath.Join(corpus, "two-groups-signed.sif"), }, { name: "TwoGroupsSignedLegacy", path: filepath.Join(corpus, "two-groups-signed-legacy.sif"), }, { name: "TwoGroupsSignedLegacyAll", path: filepath.Join(corpus, "two-groups-signed-legacy-all.sif"), }, { name: "TwoGroupsSignedLegacyGroup", path: filepath.Join(corpus, "two-groups-signed-legacy-group.sif"), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &command{opts: tt.opts} cmd := c.getList() runCommand(t, cmd, []string{tt.path}, nil) }) } } sif-2.8.3/pkg/siftool/mount.go000066400000000000000000000016671432675271000162570ustar00rootroot00000000000000// Copyright (c) 2022, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package siftool import ( "github.com/spf13/cobra" ) // getMount returns a command that mounts the primary system partition of a SIF image. func (c *command) getMount() *cobra.Command { return &cobra.Command{ Use: "mount ", Short: "Mount primary system partition", Long: "Mount the primary system partition of a SIF image", Example: c.opts.rootPath + " mount image.sif path/", Args: cobra.ExactArgs(2), PreRunE: c.initApp, RunE: func(cmd *cobra.Command, args []string) error { return c.app.Mount(cmd.Context(), args[0], args[1]) }, DisableFlagsInUseLine: true, Hidden: true, // hide while command is experimental } } sif-2.8.3/pkg/siftool/mount_test.go000066400000000000000000000023561432675271000173120ustar00rootroot00000000000000// Copyright (c) 2022, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package siftool import ( "os" "os/exec" "path/filepath" "testing" "github.com/sylabs/sif/v2/pkg/sif" ) func Test_command_getMount(t *testing.T) { if _, err := exec.LookPath("squashfuse"); err != nil { t.Skip("squashfuse not found, skipping mount tests") } tests := []struct { name string opts commandOpts path string wantErr error }{ { name: "Empty", path: filepath.Join(corpus, "empty.sif"), wantErr: sif.ErrNoObjects, }, { name: "OneGroup", path: filepath.Join(corpus, "one-group.sif"), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { path, err := os.MkdirTemp("", "siftool-mount-*") if err != nil { t.Fatal(err) } t.Cleanup(func() { cmd := exec.Command("fusermount", "-u", path) if err := cmd.Run(); err != nil { t.Log(err) } os.RemoveAll(path) }) c := &command{opts: tt.opts} cmd := c.getMount() runCommand(t, cmd, []string{tt.path, path}, tt.wantErr) }) } } sif-2.8.3/pkg/siftool/new.go000066400000000000000000000016331432675271000156770ustar00rootroot00000000000000// Copyright (c) 2018-2021, Sylabs Inc. All rights reserved. // Copyright (c) 2017, SingularityWare, LLC. All rights reserved. // Copyright (c) 2017, Yannick Cote All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package siftool import ( "github.com/spf13/cobra" ) // getNew returns a command that creates a new, empty SIF image. func (c *command) getNew() *cobra.Command { return &cobra.Command{ Use: "new ", Short: "Create SIF image", Long: "Create a new, empty SIF image.", Example: c.opts.rootPath + " new image.sif", Args: cobra.ExactArgs(1), PreRunE: c.initApp, RunE: func(cmd *cobra.Command, args []string) error { return c.app.New(args[0]) }, DisableFlagsInUseLine: true, } } sif-2.8.3/pkg/siftool/new_test.go000066400000000000000000000013511432675271000167330ustar00rootroot00000000000000// Copyright (c) 2021-2022, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package siftool import ( "os" "testing" ) func Test_command_getNew(t *testing.T) { tests := []struct { name string opts commandOpts }{ { name: "OK", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tf, err := os.CreateTemp("", "sif-test-*") if err != nil { t.Fatal(err) } defer os.Remove(tf.Name()) tf.Close() c := &command{opts: tt.opts} cmd := c.getNew() runCommand(t, cmd, []string{tf.Name()}, nil) }) } } sif-2.8.3/pkg/siftool/setprim.go000066400000000000000000000021661432675271000165730ustar00rootroot00000000000000// Copyright (c) 2018-2021, Sylabs Inc. All rights reserved. // Copyright (c) 2017, SingularityWare, LLC. All rights reserved. // Copyright (c) 2017, Yannick Cote All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package siftool import ( "fmt" "strconv" "github.com/spf13/cobra" ) // getSetPrim returns a command that sets the primary system partition. func (c *command) getSetPrim() *cobra.Command { return &cobra.Command{ Use: "setprim ", Short: "Set primary system partition", Long: "Set the primary system partition in a SIF image.", Example: c.opts.rootPath + " setprim 1 image.sif", Args: cobra.ExactArgs(2), PreRunE: c.initApp, RunE: func(cmd *cobra.Command, args []string) error { id, err := strconv.ParseUint(args[0], 10, 32) if err != nil { return fmt.Errorf("while converting id: %w", err) } return c.app.Setprim(args[1], uint32(id)) }, DisableFlagsInUseLine: true, } } sif-2.8.3/pkg/siftool/setprim_test.go000066400000000000000000000011671432675271000176320ustar00rootroot00000000000000// Copyright (c) 2021-2022, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package siftool import ( "testing" ) func Test_command_getSetPrim(t *testing.T) { tests := []struct { name string opts commandOpts }{ { name: "OK", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &command{opts: tt.opts} cmd := c.getSetPrim() runCommand(t, cmd, []string{"1", makeTestSIF(t, true)}, nil) }) } } sif-2.8.3/pkg/siftool/siftool.go000066400000000000000000000041051432675271000165620ustar00rootroot00000000000000// Copyright (c) 2018-2022, Sylabs Inc. All rights reserved. // Copyright (c) 2017, SingularityWare, LLC. All rights reserved. // Copyright (c) 2017, Yannick Cote All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. // Package siftool adds siftool commands to a parent cobra.Command. package siftool import ( "github.com/spf13/cobra" "github.com/sylabs/sif/v2/internal/app/siftool" ) // command contains options and command state. type command struct { opts commandOpts app *siftool.App } // initApp initializes the siftool app. func (c *command) initApp(cmd *cobra.Command, args []string) error { app, err := siftool.New( siftool.OptAppOutput(cmd.OutOrStdout()), siftool.OptAppError(cmd.ErrOrStderr()), ) c.app = app return err } // commandOpts contains configured options. type commandOpts struct { rootPath string experimental bool } // CommandOpt are used to configure optional command behavior. type CommandOpt func(*commandOpts) error // OptWithExperimental enables/disables experimental commands. func OptWithExperimental(b bool) CommandOpt { return func(co *commandOpts) error { co.experimental = b return nil } } // AddCommands adds siftool commands to cmd according to opts. // // A set of commands are provided to display elements such as the SIF global // header, the data object descriptors and to dump data objects. It is also // possible to modify a SIF file via this tool via the add/del commands. func AddCommands(cmd *cobra.Command, opts ...CommandOpt) error { c := command{ opts: commandOpts{ rootPath: cmd.CommandPath(), }, } for _, opt := range opts { if err := opt(&c.opts); err != nil { return err } } cmd.AddCommand( c.getHeader(), c.getList(), c.getInfo(), c.getDump(), c.getNew(), c.getAdd(), c.getDel(), c.getSetPrim(), ) if c.opts.experimental { cmd.AddCommand(c.getMount()) cmd.AddCommand(c.getUnmount()) } return nil } sif-2.8.3/pkg/siftool/siftool_test.go000066400000000000000000000052321432675271000176230ustar00rootroot00000000000000// Copyright (c) 2021-2022, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package siftool import ( "bytes" "errors" "os" "path/filepath" "testing" "github.com/sebdah/goldie/v2" "github.com/spf13/cobra" "github.com/sylabs/sif/v2/internal/app/siftool" "github.com/sylabs/sif/v2/pkg/sif" ) var corpus = filepath.Join("..", "..", "test", "images") func makeTestSIF(t *testing.T, withDataObject bool) string { tf, err := os.CreateTemp("", "sif-test-*") if err != nil { t.Fatal(err) } t.Cleanup(func() { os.Remove(tf.Name()) }) tf.Close() app, err := siftool.New() if err != nil { t.Fatal(err) } if err := app.New(tf.Name()); err != nil { t.Fatal(err) } if withDataObject { err := app.Add(tf.Name(), sif.DataPartition, bytes.NewReader([]byte{0xde, 0xad, 0xbe, 0xef}), sif.OptPartitionMetadata(sif.FsSquash, sif.PartSystem, "386"), ) if err != nil { t.Fatal(err) } } return tf.Name() } func runCommand(t *testing.T, cmd *cobra.Command, args []string, wantErr error) { t.Helper() var out, err bytes.Buffer cmd.SetOut(&out) cmd.SetErr(&err) cmd.SetArgs(args) if got, want := cmd.Execute(), wantErr; !errors.Is(got, want) { t.Fatalf("got error %v, want %v", got, want) } g := goldie.New(t, goldie.WithTestNameForDir(true), goldie.WithSubTestNameForDir(true), ) g.Assert(t, "out", out.Bytes()) g.Assert(t, "err", err.Bytes()) } func TestAddCommands(t *testing.T) { tests := []struct { name string opts []CommandOpt args []string }{ { name: "SifTool", args: []string{"help"}, }, { name: "SifToolExperimental", opts: []CommandOpt{OptWithExperimental(true)}, args: []string{"help"}, }, { name: "Add", args: []string{"help", "add"}, }, { name: "Del", args: []string{"help", "del"}, }, { name: "Dump", args: []string{"help", "dump"}, }, { name: "Header", args: []string{"help", "header"}, }, { name: "Info", args: []string{"help", "info"}, }, { name: "List", args: []string{"help", "list"}, }, { name: "New", args: []string{"help", "new"}, }, { name: "SetPrim", args: []string{"help", "setprim"}, }, { name: "Mount", opts: []CommandOpt{OptWithExperimental(true)}, args: []string{"help", "mount"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { cmd := &cobra.Command{ Use: "siftool", } if err := AddCommands(cmd, tt.opts...); err != nil { t.Fatal(err) } runCommand(t, cmd, tt.args, nil) }) } } sif-2.8.3/pkg/siftool/testdata/000077500000000000000000000000001432675271000163655ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/TestAddCommands/000077500000000000000000000000001432675271000213775ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/TestAddCommands/Add/000077500000000000000000000000001432675271000220675ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/TestAddCommands/Add/err.golden000066400000000000000000000000001432675271000240370ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/TestAddCommands/Add/out.golden000066400000000000000000000050771432675271000241010ustar00rootroot00000000000000Add a data object to a SIF image. Usage: siftool add [flags] Examples: siftool add image.sif recipe.def -datatype 1 siftool add image.sif rootfs.squashfs --datatype 4 --parttype 1 --partfs 1 ----partarch 2 siftool add image.sif signature.bin -datatype 5 --signentity 433FE984155206BD962725E20E8713472A879943 --signhash 1 Flags: --alignment int set alignment [default: 4096 with -datatype 4-Partition, 0 otherwise] --datatype int the type of data to add [NEEDED, no default]: 1-Deffile, 2-EnvVar, 3-Labels, 4-Partition, 5-Signature, 6-GenericJSON, 7-Generic, 8-CryptoMessage, 9-SBOM --filename string set logical filename/handle [default: input filename] --groupid uint32 set groupid [default: 0] -h, --help help for add --link uint32 set link pointer [default: 0] --partarch int32 the main architecture used (with -datatype 4-Partition) [NEEDED, no default]: 1-386, 2-amd64, 3-arm, 4-arm64, 5-ppc64, 6-ppc64le, 7-mips, 8-mipsle, 9-mips64, 10-mips64le, 11-s390x, 12-riscv64 --partfs int32 the filesystem used (with -datatype 4-Partition) [NEEDED, no default]: 1-Squash, 2-Ext3, 3-ImmuObj, 4-Raw --parttype int32 the type of partition (with -datatype 4-Partition) [NEEDED, no default]: 1-System, 2-PrimSys, 3-Data, 4-Overlay --sbomformat string the SBOM format (with -datatype 9-sbom): cyclonedx-json, cyclonedx-xml, github-json, spdx-json, spdx-rdf, spdx-tag-value, spdx-yaml, syft-json --signentity string the entity that signs (with -datatype 5-Signature) [NEEDED, no default]: example: 433FE984155206BD962725E20E8713472A879943 --signhash int32 the signature hash used (with -datatype 5-Signature) [NEEDED, no default]: 1-SHA256, 2-SHA384, 3-SHA512, 4-BLAKE2s_256, 5-BLAKE2b_256 sif-2.8.3/pkg/siftool/testdata/TestAddCommands/Del/000077500000000000000000000000001432675271000221035ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/TestAddCommands/Del/err.golden000066400000000000000000000000001432675271000240530ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/TestAddCommands/Del/out.golden000066400000000000000000000002241432675271000241020ustar00rootroot00000000000000Delete a data object from a SIF image. Usage: siftool del Examples: siftool del 1 image.sif Flags: -h, --help help for del sif-2.8.3/pkg/siftool/testdata/TestAddCommands/Dump/000077500000000000000000000000001432675271000223045ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/TestAddCommands/Dump/err.golden000066400000000000000000000000001432675271000242540ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/TestAddCommands/Dump/out.golden000066400000000000000000000002251432675271000243040ustar00rootroot00000000000000Dump a data object from a SIF image. Usage: siftool dump Examples: siftool dump 1 image.sif Flags: -h, --help help for dump sif-2.8.3/pkg/siftool/testdata/TestAddCommands/Header/000077500000000000000000000000001432675271000225675ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/TestAddCommands/Header/err.golden000066400000000000000000000000001432675271000245370ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/TestAddCommands/Header/out.golden000066400000000000000000000002271432675271000245710ustar00rootroot00000000000000Display global header from a SIF image. Usage: siftool header Examples: siftool header image.sif Flags: -h, --help help for header sif-2.8.3/pkg/siftool/testdata/TestAddCommands/Info/000077500000000000000000000000001432675271000222725ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/TestAddCommands/Info/err.golden000066400000000000000000000000001432675271000242420ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/TestAddCommands/Info/out.golden000066400000000000000000000002431432675271000242720ustar00rootroot00000000000000Display info about a data object from a SIF image. Usage: siftool info Examples: siftool info 1 image.sif Flags: -h, --help help for info sif-2.8.3/pkg/siftool/testdata/TestAddCommands/List/000077500000000000000000000000001432675271000223125ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/TestAddCommands/List/err.golden000066400000000000000000000000001432675271000242620ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/TestAddCommands/List/out.golden000066400000000000000000000002151432675271000243110ustar00rootroot00000000000000List data objects from a SIF image. Usage: siftool list Examples: siftool list image.sif Flags: -h, --help help for list sif-2.8.3/pkg/siftool/testdata/TestAddCommands/Mount/000077500000000000000000000000001432675271000225015ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/TestAddCommands/Mount/err.golden000066400000000000000000000000001432675271000244510ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/TestAddCommands/Mount/out.golden000066400000000000000000000002611432675271000245010ustar00rootroot00000000000000Mount the primary system partition of a SIF image Usage: siftool mount Examples: siftool mount image.sif path/ Flags: -h, --help help for mount sif-2.8.3/pkg/siftool/testdata/TestAddCommands/New/000077500000000000000000000000001432675271000221305ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/TestAddCommands/New/err.golden000066400000000000000000000000001432675271000241000ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/TestAddCommands/New/out.golden000066400000000000000000000002051432675271000241260ustar00rootroot00000000000000Create a new, empty SIF image. Usage: siftool new Examples: siftool new image.sif Flags: -h, --help help for new sif-2.8.3/pkg/siftool/testdata/TestAddCommands/SetPrim/000077500000000000000000000000001432675271000227625ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/TestAddCommands/SetPrim/err.golden000066400000000000000000000000001432675271000247320ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/TestAddCommands/SetPrim/out.golden000066400000000000000000000002521432675271000247620ustar00rootroot00000000000000Set the primary system partition in a SIF image. Usage: siftool setprim Examples: siftool setprim 1 image.sif Flags: -h, --help help for setprim sif-2.8.3/pkg/siftool/testdata/TestAddCommands/SifTool/000077500000000000000000000000001432675271000227565ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/TestAddCommands/SifTool/err.golden000066400000000000000000000000001432675271000247260ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/TestAddCommands/SifTool/out.golden000066400000000000000000000010371432675271000247600ustar00rootroot00000000000000Usage: siftool [command] Available Commands: add Add data object completion Generate the autocompletion script for the specified shell del Delete data object dump Dump data object header Display global header help Help about any command info Display data object info list List data objects new Create SIF image setprim Set primary system partition Flags: -h, --help help for siftool Use "siftool [command] --help" for more information about a command. sif-2.8.3/pkg/siftool/testdata/TestAddCommands/SifToolExperimental/000077500000000000000000000000001432675271000253345ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/TestAddCommands/SifToolExperimental/err.golden000066400000000000000000000000001432675271000273040ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/TestAddCommands/SifToolExperimental/out.golden000066400000000000000000000010371432675271000273360ustar00rootroot00000000000000Usage: siftool [command] Available Commands: add Add data object completion Generate the autocompletion script for the specified shell del Delete data object dump Dump data object header Display global header help Help about any command info Display data object info list List data objects new Create SIF image setprim Set primary system partition Flags: -h, --help help for siftool Use "siftool [command] --help" for more information about a command. sif-2.8.3/pkg/siftool/testdata/Test_command_getAdd/000077500000000000000000000000001432675271000222525ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getAdd/DataPartition/000077500000000000000000000000001432675271000250155ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getAdd/DataPartition/err.golden000066400000000000000000000000001432675271000267650ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getAdd/DataPartition/out.golden000066400000000000000000000000001432675271000270040ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getAdd/DataSignature/000077500000000000000000000000001432675271000250055ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getAdd/DataSignature/err.golden000066400000000000000000000000001432675271000267550ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getAdd/DataSignature/out.golden000066400000000000000000000000001432675271000267740ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getDel/000077500000000000000000000000001432675271000222665ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getDel/OK/000077500000000000000000000000001432675271000225775ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getDel/OK/err.golden000066400000000000000000000000001432675271000245470ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getDel/OK/out.golden000066400000000000000000000000001432675271000245660ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getDump/000077500000000000000000000000001432675271000224675ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getDump/One/000077500000000000000000000000001432675271000232105ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getDump/One/err.golden000066400000000000000000000000001432675271000251600ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getDump/One/out.golden000066400000000000000000000000041432675271000252030ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getDump/Three/000077500000000000000000000000001432675271000235365ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getDump/Three/err.golden000066400000000000000000000000001432675271000255060ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getDump/Three/out.golden000066400000000000000000000020361432675271000255400ustar00rootroot00000000000000-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 {"version":1,"header":{"digest":"sha256:635fa0a14a8ef0c0351ed3e985799ed1d4f75ce973dea3cc76c99710795cc3f1"},"objects":[{"relativeId":0,"descriptorDigest":"sha256:3634ad01db0dd5482ecf685267b53d6201690438ca27c3d7ea91c971a1f41f92","objectDigest":"sha256:004dfc8da678c309de28b5386a1e9efd57f536b150c40d29b31506aa0fb17ec2"},{"relativeId":1,"descriptorDigest":"sha256:04b5f87c9692a54f80d10fb6af00c779763aeca29d610348854bd97cd8bf66fd","objectDigest":"sha256:9f9c4e5e131934969b4ac8f495691c70b8c6c8e3f489c2c9ab5f1af82bce0604"}]} -----BEGIN PGP SIGNATURE----- wsBzBAEBCAAnBQJe+oD0CZCiDCfuf/e6hBYhBBIEXIwLEATQWN5L7aIMJ+5/97qE AAC46gf/VXyzZ649nttrX13JkM5kRVPlAIblBQxfoUxA1xwIXdRoM5ceDY0Em+YD 8b6Xl1w2sDTqo0R15cJSh8sf0ClFOvYpDQRNCwKx17k1Wd0gHcW4QVu6gJnlbNvN o/EJdEN2TkbCM2aFvj34DAIfErRBIEsCeDDvJ/6WUSySWbnydfNU2pCsnK4A7l2H KOXFzSaPijG9L/pU3O3vNZ+fXPffqHL9JVhs5Mt/Yo3oeoEnoVaKvJLGx/fyl+Gj 7qsfWFyHWzRCww9VFg/TCBeUku0CYRfXhxOgo4OuHNr8oo82rKDZU6+l3UZ2Sw8T +kLe/zUkaILocGOvhvKdi630OGGb/Q== =3Jq2 -----END PGP SIGNATURE-----sif-2.8.3/pkg/siftool/testdata/Test_command_getDump/Two/000077500000000000000000000000001432675271000232405ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getDump/Two/err.golden000066400000000000000000000000001432675271000252100ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getDump/Two/out.golden000066400000000000000000000100001432675271000252300ustar00rootroot00000000000000hsqsgCb sHello from Sylabs! 'xcb]I`bFpy&8Vbf ^xc`F(WRQ`xc`( !sif-2.8.3/pkg/siftool/testdata/Test_command_getHeader/000077500000000000000000000000001432675271000227525ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getHeader/Empty/000077500000000000000000000000001432675271000240505ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getHeader/Empty/err.golden000066400000000000000000000000001432675271000260200ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getHeader/Empty/out.golden000066400000000000000000000002531432675271000260510ustar00rootroot00000000000000Version: 01 Descriptors Free: 48 Descriptors Total: 48 Descriptors Offset: 4096 Descriptors Size: 27 KiB Data Offset: 32176 Data Size: 0 B sif-2.8.3/pkg/siftool/testdata/Test_command_getHeader/OneGroup/000077500000000000000000000000001432675271000245105ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getHeader/OneGroup/err.golden000066400000000000000000000000001432675271000264600ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getHeader/OneGroup/out.golden000066400000000000000000000003251432675271000265110ustar00rootroot00000000000000Version: 01 Primary Architecture: 386 Descriptors Free: 46 Descriptors Total: 48 Descriptors Offset: 4096 Descriptors Size: 27 KiB Data Offset: 32176 Data Size: 9 KiB sif-2.8.3/pkg/siftool/testdata/Test_command_getHeader/OneGroupSigned/000077500000000000000000000000001432675271000256425ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getHeader/OneGroupSigned/err.golden000066400000000000000000000000001432675271000276120ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getHeader/OneGroupSigned/out.golden000066400000000000000000000003261432675271000276440ustar00rootroot00000000000000Version: 01 Primary Architecture: 386 Descriptors Free: 45 Descriptors Total: 48 Descriptors Offset: 4096 Descriptors Size: 27 KiB Data Offset: 32176 Data Size: 10 KiB sif-2.8.3/pkg/siftool/testdata/Test_command_getHeader/OneGroupSignedLegacy/000077500000000000000000000000001432675271000267675ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getHeader/OneGroupSignedLegacy/err.golden000066400000000000000000000000001432675271000307370ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getHeader/OneGroupSignedLegacy/out.golden000066400000000000000000000006551432675271000307760ustar00rootroot00000000000000Launch Script: #!/usr/bin/env run-singularity Version: 01 Primary Architecture: 386 ID: 6ecc76b7-a497-4f7f-9ebd-8da2a04c6be1 Created At: 2020-05-22 19:30:59 +0000 UTC Modified At: 2020-06-20 20:16:39 +0000 UTC Descriptors Free: 45 Descriptors Total: 48 Descriptors Offset: 4096 Descriptors Size: 27 KiB Data Offset: 32768 Data Size: 9 KiB sif-2.8.3/pkg/siftool/testdata/Test_command_getHeader/OneGroupSignedLegacyAll/000077500000000000000000000000001432675271000274205ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getHeader/OneGroupSignedLegacyAll/err.golden000066400000000000000000000000001432675271000313700ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getHeader/OneGroupSignedLegacyAll/out.golden000066400000000000000000000006561432675271000314300ustar00rootroot00000000000000Launch Script: #!/usr/bin/env run-singularity Version: 01 Primary Architecture: 386 ID: 6ecc76b7-a497-4f7f-9ebd-8da2a04c6be1 Created At: 2020-05-22 19:30:59 +0000 UTC Modified At: 2020-06-20 20:17:15 +0000 UTC Descriptors Free: 44 Descriptors Total: 48 Descriptors Offset: 4096 Descriptors Size: 27 KiB Data Offset: 32768 Data Size: 13 KiB sif-2.8.3/pkg/siftool/testdata/Test_command_getHeader/OneGroupSignedLegacyGroup/000077500000000000000000000000001432675271000300045ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getHeader/OneGroupSignedLegacyGroup/err.golden000066400000000000000000000000001432675271000317540ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getHeader/OneGroupSignedLegacyGroup/out.golden000066400000000000000000000006551432675271000320130ustar00rootroot00000000000000Launch Script: #!/usr/bin/env run-singularity Version: 01 Primary Architecture: 386 ID: 6ecc76b7-a497-4f7f-9ebd-8da2a04c6be1 Created At: 2020-05-22 19:30:59 +0000 UTC Modified At: 2020-06-20 20:16:55 +0000 UTC Descriptors Free: 45 Descriptors Total: 48 Descriptors Offset: 4096 Descriptors Size: 27 KiB Data Offset: 32768 Data Size: 9 KiB sif-2.8.3/pkg/siftool/testdata/Test_command_getHeader/TwoGroups/000077500000000000000000000000001432675271000247235ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getHeader/TwoGroups/err.golden000066400000000000000000000000001432675271000266730ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getHeader/TwoGroups/out.golden000066400000000000000000000003271432675271000267260ustar00rootroot00000000000000Version: 01 Primary Architecture: 386 Descriptors Free: 45 Descriptors Total: 48 Descriptors Offset: 4096 Descriptors Size: 27 KiB Data Offset: 32176 Data Size: 265 KiB sif-2.8.3/pkg/siftool/testdata/Test_command_getHeader/TwoGroupsSigned/000077500000000000000000000000001432675271000260555ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getHeader/TwoGroupsSigned/err.golden000066400000000000000000000000001432675271000300250ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getHeader/TwoGroupsSigned/out.golden000066400000000000000000000003271432675271000300600ustar00rootroot00000000000000Version: 01 Primary Architecture: 386 Descriptors Free: 43 Descriptors Total: 48 Descriptors Offset: 4096 Descriptors Size: 27 KiB Data Offset: 32176 Data Size: 266 KiB sif-2.8.3/pkg/siftool/testdata/Test_command_getHeader/TwoGroupsSignedLegacy/000077500000000000000000000000001432675271000272025ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getHeader/TwoGroupsSignedLegacy/err.golden000066400000000000000000000000001432675271000311520ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getHeader/TwoGroupsSignedLegacy/out.golden000066400000000000000000000006561432675271000312120ustar00rootroot00000000000000Launch Script: #!/usr/bin/env run-singularity Version: 01 Primary Architecture: 386 ID: 0b19ec2c-0b08-46c9-95ae-fa88cd9e48a1 Created At: 2020-05-22 19:30:59 +0000 UTC Modified At: 2020-06-20 20:16:44 +0000 UTC Descriptors Free: 44 Descriptors Total: 48 Descriptors Offset: 4096 Descriptors Size: 27 KiB Data Offset: 32768 Data Size: 13 KiB sif-2.8.3/pkg/siftool/testdata/Test_command_getHeader/TwoGroupsSignedLegacyAll/000077500000000000000000000000001432675271000276335ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getHeader/TwoGroupsSignedLegacyAll/err.golden000066400000000000000000000000001432675271000316030ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getHeader/TwoGroupsSignedLegacyAll/out.golden000066400000000000000000000006561432675271000316430ustar00rootroot00000000000000Launch Script: #!/usr/bin/env run-singularity Version: 01 Primary Architecture: 386 ID: 0b19ec2c-0b08-46c9-95ae-fa88cd9e48a1 Created At: 2020-05-22 19:30:59 +0000 UTC Modified At: 2020-06-20 20:17:19 +0000 UTC Descriptors Free: 43 Descriptors Total: 48 Descriptors Offset: 4096 Descriptors Size: 27 KiB Data Offset: 32768 Data Size: 17 KiB sif-2.8.3/pkg/siftool/testdata/Test_command_getHeader/TwoGroupsSignedLegacyGroup/000077500000000000000000000000001432675271000302175ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getHeader/TwoGroupsSignedLegacyGroup/err.golden000066400000000000000000000000001432675271000321670ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getHeader/TwoGroupsSignedLegacyGroup/out.golden000066400000000000000000000006561432675271000322270ustar00rootroot00000000000000Launch Script: #!/usr/bin/env run-singularity Version: 01 Primary Architecture: 386 ID: 0b19ec2c-0b08-46c9-95ae-fa88cd9e48a1 Created At: 2020-05-22 19:30:59 +0000 UTC Modified At: 2020-06-20 20:16:58 +0000 UTC Descriptors Free: 44 Descriptors Total: 48 Descriptors Offset: 4096 Descriptors Size: 27 KiB Data Offset: 32768 Data Size: 13 KiB sif-2.8.3/pkg/siftool/testdata/Test_command_getInfo/000077500000000000000000000000001432675271000224555ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getInfo/One/000077500000000000000000000000001432675271000231765ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getInfo/One/err.golden000066400000000000000000000000001432675271000251460ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getInfo/One/out.golden000066400000000000000000000003271432675271000252010ustar00rootroot00000000000000 Data Type: FS ID: 1 Group ID: 1 Linked ID: NONE Offset: 32768 Size: 4 Filesystem Type: Raw Partition Type: System Architecture: 386 sif-2.8.3/pkg/siftool/testdata/Test_command_getInfo/Three/000077500000000000000000000000001432675271000235245ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getInfo/Three/err.golden000066400000000000000000000000001432675271000254740ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getInfo/Three/out.golden000066400000000000000000000003031432675271000255210ustar00rootroot00000000000000 Data Type: Signature ID: 3 Group ID: NONE Linked ID: 1 (G) Offset: 40960 Size: 1054 Hash Type: SHA-256 Entity: 12045C8C0B1004D058DE4BEDA20C27EE7FF7BA84 sif-2.8.3/pkg/siftool/testdata/Test_command_getInfo/Two/000077500000000000000000000000001432675271000232265ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getInfo/Two/err.golden000066400000000000000000000000001432675271000251760ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getInfo/Two/out.golden000066400000000000000000000003401432675271000252240ustar00rootroot00000000000000 Data Type: FS ID: 2 Group ID: 1 Linked ID: NONE Offset: 36864 Size: 4096 Filesystem Type: Squashfs Partition Type: *System Architecture: 386 sif-2.8.3/pkg/siftool/testdata/Test_command_getList/000077500000000000000000000000001432675271000224755ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getList/Empty/000077500000000000000000000000001432675271000235735ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getList/Empty/err.golden000066400000000000000000000000001432675271000255430ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getList/Empty/out.golden000066400000000000000000000003261432675271000255750ustar00rootroot00000000000000------------------------------------------------------------------------------ ID |GROUP |LINK |SIF POSITION (start-end) |TYPE ------------------------------------------------------------------------------ sif-2.8.3/pkg/siftool/testdata/Test_command_getList/OneGroup/000077500000000000000000000000001432675271000242335ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getList/OneGroup/err.golden000066400000000000000000000000001432675271000262030ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getList/OneGroup/out.golden000066400000000000000000000005521432675271000262360ustar00rootroot00000000000000------------------------------------------------------------------------------ ID |GROUP |LINK |SIF POSITION (start-end) |TYPE ------------------------------------------------------------------------------ 1 |1 |NONE |32768-32772 |FS (Raw/System/386) 2 |1 |NONE |36864-40960 |FS (Squashfs/*System/386) sif-2.8.3/pkg/siftool/testdata/Test_command_getList/OneGroupSigned/000077500000000000000000000000001432675271000253655ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getList/OneGroupSigned/err.golden000066400000000000000000000000001432675271000273350ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getList/OneGroupSigned/out.golden000066400000000000000000000006611432675271000273710ustar00rootroot00000000000000------------------------------------------------------------------------------ ID |GROUP |LINK |SIF POSITION (start-end) |TYPE ------------------------------------------------------------------------------ 1 |1 |NONE |32768-32772 |FS (Raw/System/386) 2 |1 |NONE |36864-40960 |FS (Squashfs/*System/386) 3 |NONE |1 (G) |40960-42014 |Signature (SHA-256) sif-2.8.3/pkg/siftool/testdata/Test_command_getList/OneGroupSignedLegacy/000077500000000000000000000000001432675271000265125ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getList/OneGroupSignedLegacy/err.golden000066400000000000000000000000001432675271000304620ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getList/OneGroupSignedLegacy/out.golden000066400000000000000000000006611432675271000305160ustar00rootroot00000000000000------------------------------------------------------------------------------ ID |GROUP |LINK |SIF POSITION (start-end) |TYPE ------------------------------------------------------------------------------ 1 |1 |NONE |32768-32772 |FS (Raw/System/386) 2 |1 |NONE |36864-36868 |FS (Squashfs/*System/386) 3 |1 |2 |40960-41569 |Signature (SHA-384) sif-2.8.3/pkg/siftool/testdata/Test_command_getList/OneGroupSignedLegacyAll/000077500000000000000000000000001432675271000271435ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getList/OneGroupSignedLegacyAll/err.golden000066400000000000000000000000001432675271000311130ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getList/OneGroupSignedLegacyAll/out.golden000066400000000000000000000007701432675271000311500ustar00rootroot00000000000000------------------------------------------------------------------------------ ID |GROUP |LINK |SIF POSITION (start-end) |TYPE ------------------------------------------------------------------------------ 1 |1 |NONE |32768-32772 |FS (Raw/System/386) 2 |1 |NONE |36864-36868 |FS (Squashfs/*System/386) 3 |1 |1 |40960-41569 |Signature (SHA-384) 4 |1 |2 |45056-45665 |Signature (SHA-384) sif-2.8.3/pkg/siftool/testdata/Test_command_getList/OneGroupSignedLegacyGroup/000077500000000000000000000000001432675271000275275ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getList/OneGroupSignedLegacyGroup/err.golden000066400000000000000000000000001432675271000314770ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getList/OneGroupSignedLegacyGroup/out.golden000066400000000000000000000006611432675271000315330ustar00rootroot00000000000000------------------------------------------------------------------------------ ID |GROUP |LINK |SIF POSITION (start-end) |TYPE ------------------------------------------------------------------------------ 1 |1 |NONE |32768-32772 |FS (Raw/System/386) 2 |1 |NONE |36864-36868 |FS (Squashfs/*System/386) 3 |NONE |1 (G) |40960-41569 |Signature (SHA-384) sif-2.8.3/pkg/siftool/testdata/Test_command_getList/TwoGroups/000077500000000000000000000000001432675271000244465ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getList/TwoGroups/err.golden000066400000000000000000000000001432675271000264160ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getList/TwoGroups/out.golden000066400000000000000000000006641432675271000264550ustar00rootroot00000000000000------------------------------------------------------------------------------ ID |GROUP |LINK |SIF POSITION (start-end) |TYPE ------------------------------------------------------------------------------ 1 |1 |NONE |32768-32772 |FS (Raw/System/386) 2 |1 |NONE |36864-40960 |FS (Squashfs/*System/386) 3 |2 |NONE |40960-303104 |FS (Ext3/System/amd64) sif-2.8.3/pkg/siftool/testdata/Test_command_getList/TwoGroupsSigned/000077500000000000000000000000001432675271000256005ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getList/TwoGroupsSigned/err.golden000066400000000000000000000000001432675271000275500ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getList/TwoGroupsSigned/out.golden000066400000000000000000000011021432675271000275730ustar00rootroot00000000000000------------------------------------------------------------------------------ ID |GROUP |LINK |SIF POSITION (start-end) |TYPE ------------------------------------------------------------------------------ 1 |1 |NONE |32768-32772 |FS (Raw/System/386) 2 |1 |NONE |36864-40960 |FS (Squashfs/*System/386) 3 |2 |NONE |40960-303104 |FS (Ext3/System/amd64) 4 |NONE |1 (G) |303104-304158 |Signature (SHA-256) 5 |NONE |2 (G) |304158-305013 |Signature (SHA-256) sif-2.8.3/pkg/siftool/testdata/Test_command_getList/TwoGroupsSignedLegacy/000077500000000000000000000000001432675271000267255ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getList/TwoGroupsSignedLegacy/err.golden000066400000000000000000000000001432675271000306750ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getList/TwoGroupsSignedLegacy/out.golden000066400000000000000000000007731432675271000307350ustar00rootroot00000000000000------------------------------------------------------------------------------ ID |GROUP |LINK |SIF POSITION (start-end) |TYPE ------------------------------------------------------------------------------ 1 |1 |NONE |32768-32772 |FS (Raw/System/386) 2 |1 |NONE |36864-36868 |FS (Squashfs/*System/386) 3 |2 |NONE |40960-40964 |FS (Ext3/System/amd64) 4 |1 |2 |45056-45665 |Signature (SHA-384) sif-2.8.3/pkg/siftool/testdata/Test_command_getList/TwoGroupsSignedLegacyAll/000077500000000000000000000000001432675271000273565ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getList/TwoGroupsSignedLegacyAll/err.golden000066400000000000000000000000001432675271000313260ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getList/TwoGroupsSignedLegacyAll/out.golden000066400000000000000000000011021432675271000313510ustar00rootroot00000000000000------------------------------------------------------------------------------ ID |GROUP |LINK |SIF POSITION (start-end) |TYPE ------------------------------------------------------------------------------ 1 |1 |NONE |32768-32772 |FS (Raw/System/386) 2 |1 |NONE |36864-36868 |FS (Squashfs/*System/386) 3 |2 |NONE |40960-40964 |FS (Ext3/System/amd64) 4 |1 |1 |45056-45665 |Signature (SHA-384) 5 |1 |2 |49152-49761 |Signature (SHA-384) sif-2.8.3/pkg/siftool/testdata/Test_command_getList/TwoGroupsSignedLegacyGroup/000077500000000000000000000000001432675271000277425ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getList/TwoGroupsSignedLegacyGroup/err.golden000066400000000000000000000000001432675271000317120ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getList/TwoGroupsSignedLegacyGroup/out.golden000066400000000000000000000007731432675271000317520ustar00rootroot00000000000000------------------------------------------------------------------------------ ID |GROUP |LINK |SIF POSITION (start-end) |TYPE ------------------------------------------------------------------------------ 1 |1 |NONE |32768-32772 |FS (Raw/System/386) 2 |1 |NONE |36864-36868 |FS (Squashfs/*System/386) 3 |2 |NONE |40960-40964 |FS (Ext3/System/amd64) 4 |NONE |1 (G) |45056-45665 |Signature (SHA-384) sif-2.8.3/pkg/siftool/testdata/Test_command_getMount/000077500000000000000000000000001432675271000226645ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getMount/Empty/000077500000000000000000000000001432675271000237625ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getMount/Empty/err.golden000066400000000000000000000000771432675271000257500ustar00rootroot00000000000000Error: failed to get partition descriptor: no objects in image sif-2.8.3/pkg/siftool/testdata/Test_command_getMount/Empty/out.golden000066400000000000000000000001601432675271000257600ustar00rootroot00000000000000Usage: mount Examples: mount image.sif path/ Flags: -h, --help help for mount sif-2.8.3/pkg/siftool/testdata/Test_command_getMount/OneGroup/000077500000000000000000000000001432675271000244225ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getMount/OneGroup/err.golden000066400000000000000000000000001432675271000263720ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getMount/OneGroup/out.golden000066400000000000000000000000001432675271000264110ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getNew/000077500000000000000000000000001432675271000223135ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getNew/OK/000077500000000000000000000000001432675271000226245ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getNew/OK/err.golden000066400000000000000000000000001432675271000245740ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getNew/OK/out.golden000066400000000000000000000000001432675271000246130ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getSetPrim/000077500000000000000000000000001432675271000231455ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getSetPrim/OK/000077500000000000000000000000001432675271000234565ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getSetPrim/OK/err.golden000066400000000000000000000000001432675271000254260ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getSetPrim/OK/out.golden000066400000000000000000000000001432675271000254450ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getUnmount/000077500000000000000000000000001432675271000232275ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getUnmount/err.golden000066400000000000000000000000001432675271000251770ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/Test_command_getUnmount/out.golden000066400000000000000000000000001432675271000252160ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/input/000077500000000000000000000000001432675271000175245ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/testdata/input/input.bin000066400000000000000000000000041432675271000213470ustar00rootroot00000000000000sif-2.8.3/pkg/siftool/unmount.go000066400000000000000000000016471432675271000166200ustar00rootroot00000000000000// Copyright (c) 2022, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package siftool import ( "github.com/spf13/cobra" ) // getUnmount returns a command that unmounts the primary system partition of a SIF image. func (c *command) getUnmount() *cobra.Command { return &cobra.Command{ Use: "unmount ", Short: "Unmount primary system partition", Long: "Unmount a primary system partition of a SIF image", Example: c.opts.rootPath + " unmount path/", Args: cobra.ExactArgs(1), PreRunE: c.initApp, RunE: func(cmd *cobra.Command, args []string) error { return c.app.Unmount(cmd.Context(), args[0]) }, DisableFlagsInUseLine: true, Hidden: true, // hide while command is experimental } } sif-2.8.3/pkg/siftool/unmount_test.go000066400000000000000000000017451432675271000176560ustar00rootroot00000000000000// Copyright (c) 2022, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package siftool import ( "context" "os" "os/exec" "path/filepath" "testing" "github.com/sylabs/sif/v2/pkg/user" ) func Test_command_getUnmount(t *testing.T) { if _, err := exec.LookPath("squashfuse"); err != nil { t.Skip(" not found, skipping unmount tests") } if _, err := exec.LookPath("fusermount"); err != nil { t.Skip(" not found, skipping unmount tests") } path, err := os.MkdirTemp("", "siftool-unmount-*") if err != nil { t.Fatal(err) } t.Cleanup(func() { os.RemoveAll(path) }) testSIF := filepath.Join(corpus, "one-group.sif") if err := user.Mount(context.Background(), testSIF, path); err != nil { t.Fatal(err) } c := &command{} cmd := c.getUnmount() runCommand(t, cmd, []string{path}, nil) } sif-2.8.3/pkg/user/000077500000000000000000000000001432675271000140535ustar00rootroot00000000000000sif-2.8.3/pkg/user/mount.go000066400000000000000000000064711432675271000155540ustar00rootroot00000000000000// Copyright (c) 2022, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package user import ( "context" "errors" "fmt" "io" "os" "os/exec" "path/filepath" "github.com/sylabs/sif/v2/pkg/sif" ) // mountSquashFS mounts the SquashFS filesystem from path at offset into mountPath. func mountSquashFS(ctx context.Context, offset int64, path, mountPath string, mo mountOpts) error { args := []string{ "-o", fmt.Sprintf("ro,offset=%d", offset), filepath.Clean(path), filepath.Clean(mountPath), } //nolint:gosec // note (gosec exclusion) - we require callers to be able to specify squashfuse not on PATH cmd := exec.CommandContext(ctx, mo.squashfusePath, args...) cmd.Stdout = mo.stdout cmd.Stderr = mo.stderr if err := cmd.Run(); err != nil { return fmt.Errorf("failed to mount: %w", err) } return nil } // mountOpts accumulates mount options. type mountOpts struct { stdout io.Writer stderr io.Writer squashfusePath string } // MountOpt are used to specify mount options. type MountOpt func(*mountOpts) error // OptMountStdout writes standard output to w. func OptMountStdout(w io.Writer) MountOpt { return func(mo *mountOpts) error { mo.stdout = w return nil } } // OptMountStderr writes standard error to w. func OptMountStderr(w io.Writer) MountOpt { return func(mo *mountOpts) error { mo.stderr = w return nil } } var errSquashfusePathInvalid = errors.New("squashfuse path must be relative or absolute") // OptMountSquashfusePath sets an explicit path to the squashfuse binary. The path must be an // absolute or relative path. func OptMountSquashfusePath(path string) MountOpt { return func(mo *mountOpts) error { if filepath.Base(path) == path { return errSquashfusePathInvalid } mo.squashfusePath = path return nil } } var errUnsupportedFSType = errors.New("unrecognized filesystem type") // Mount mounts the primary system partition of the SIF file at path into mountPath. // // Mount may start one or more underlying processes. By default, stdout and stderr of these // processes is discarded. To modify this behavior, consider using OptMountStdout and/or // OptMountStderr. // // By default, Mount searches for a squashfuse binary in the directories named by the PATH // environment variable. To override this behavior, consider using OptMountSquashfusePath(). func Mount(ctx context.Context, path, mountPath string, opts ...MountOpt) error { mo := mountOpts{ squashfusePath: "squashfuse", } for _, opt := range opts { if err := opt(&mo); err != nil { return fmt.Errorf("%w", err) } } f, err := sif.LoadContainerFromPath(path, sif.OptLoadWithFlag(os.O_RDONLY)) if err != nil { return fmt.Errorf("failed to load image: %w", err) } defer func() { _ = f.UnloadContainer() }() d, err := f.GetDescriptor(sif.WithPartitionType(sif.PartPrimSys)) if err != nil { return fmt.Errorf("failed to get partition descriptor: %w", err) } fs, _, _, err := d.PartitionMetadata() if err != nil { return fmt.Errorf("failed to get partition metadata: %w", err) } switch fs { case sif.FsSquash: return mountSquashFS(ctx, d.Offset(), path, mountPath, mo) default: return errUnsupportedFSType } } sif-2.8.3/pkg/user/unmount.go000066400000000000000000000046351432675271000161170ustar00rootroot00000000000000// Copyright (c) 2022, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package user import ( "context" "errors" "fmt" "io" "os/exec" "path/filepath" ) // unmountSquashFS unmounts the filesystem at mountPath. func unmountSquashFS(ctx context.Context, mountPath string, uo unmountOpts) error { args := []string{ "-u", filepath.Clean(mountPath), } cmd := exec.CommandContext(ctx, uo.fusermountPath, args...) //nolint:gosec cmd.Stdout = uo.stdout cmd.Stderr = uo.stderr if err := cmd.Run(); err != nil { return fmt.Errorf("failed to unmount: %w", err) } return nil } // unmountOpts accumulates unmount options. type unmountOpts struct { stdout io.Writer stderr io.Writer fusermountPath string } // UnmountOpt are used to specify unmount options. type UnmountOpt func(*unmountOpts) error // OptUnmountStdout writes standard output to w. func OptUnmountStdout(w io.Writer) UnmountOpt { return func(mo *unmountOpts) error { mo.stdout = w return nil } } // OptUnmountStderr writes standard error to w. func OptUnmountStderr(w io.Writer) UnmountOpt { return func(mo *unmountOpts) error { mo.stderr = w return nil } } var errFusermountPathInvalid = errors.New("fusermount path must be relative or absolute") // OptUnmountFusermountPath sets the path to the fusermount binary. func OptUnmountFusermountPath(path string) UnmountOpt { return func(mo *unmountOpts) error { if filepath.Base(path) == path { return errFusermountPathInvalid } mo.fusermountPath = path return nil } } // Unmount unmounts the filesystem at mountPath. // // Unmount may start one or more underlying processes. By default, stdout and stderr of these // processes is discarded. To modify this behavior, consider using OptUnmountStdout and/or // OptUnmountStderr. // // By default, Unmount searches for a fusermount binary in the directories named by the PATH // environment variable. To override this behavior, consider using OptUnmountFusermountPath(). func Unmount(ctx context.Context, mountPath string, opts ...UnmountOpt) error { uo := unmountOpts{ fusermountPath: "fusermount", } for _, opt := range opts { if err := opt(&uo); err != nil { return fmt.Errorf("%w", err) } } return unmountSquashFS(ctx, mountPath, uo) } sif-2.8.3/pkg/user/unmount_test.go000066400000000000000000000062221432675271000171500ustar00rootroot00000000000000// Copyright (c) 2022, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. package user import ( "bufio" "context" "errors" "fmt" "os" "os/exec" "path/filepath" "strings" "testing" ) var corpus = filepath.Join("..", "..", "test", "images") func Test_Unmount(t *testing.T) { if _, err := exec.LookPath("squashfuse"); err != nil { t.Skip(" not found, skipping mount tests") } fusermountPath, err := exec.LookPath("fusermount") if err != nil { t.Skip(" not found, skipping mount tests") } path, err := os.MkdirTemp("", "siftool-mount-*") if err != nil { t.Fatal(err) } t.Cleanup(func() { os.RemoveAll(path) }) tests := []struct { name string mountSIF string mountPath string opts []UnmountOpt wantErr bool wantUnmounted bool }{ { name: "Mounted", mountSIF: filepath.Join(corpus, "one-group.sif"), mountPath: path, wantErr: false, wantUnmounted: true, }, { name: "NotMounted", mountSIF: "", mountPath: path, wantErr: true, }, { name: "NotSquashfuse", mountSIF: "", mountPath: "/dev", wantErr: true, }, { name: "FusermountBare", mountSIF: "", mountPath: path, opts: []UnmountOpt{OptUnmountFusermountPath("fusermount")}, wantErr: true, }, { name: "FusermountValid", mountSIF: filepath.Join(corpus, "one-group.sif"), mountPath: path, opts: []UnmountOpt{OptUnmountFusermountPath(fusermountPath)}, wantErr: false, wantUnmounted: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.mountSIF != "" { err := Mount(context.Background(), tt.mountSIF, path) if err != nil { t.Fatal(err) } } err := Unmount(context.Background(), tt.mountPath, tt.opts...) if err != nil && !tt.wantErr { t.Errorf("Unexpected error: %s", err) } if err == nil && tt.wantErr { t.Error("Unexpected success") } mounted, err := isMounted(tt.mountPath) if err != nil { t.Fatal(err) } if tt.wantUnmounted && mounted { t.Errorf("Expected %s to be unmounted, but it is mounted", tt.mountPath) } }) } } var errBadMountInfo = errors.New("bad mount info") func isMounted(mountPath string) (bool, error) { mountPath, err := filepath.Abs(mountPath) if err != nil { return false, err } mi, err := os.Open("/proc/self/mountinfo") if err != nil { return false, fmt.Errorf("failed to open /proc/self/mountinfo: %w", err) } defer mi.Close() scanner := bufio.NewScanner(mi) for scanner.Scan() { fields := strings.Split(scanner.Text(), " ") if len(fields) < 5 { return false, fmt.Errorf("not enough mountinfo fields: %w", errBadMountInfo) } //nolint:lll // 1348 63 0:77 / /tmp/siftool-mount-956028386 ro,nosuid,nodev,relatime shared:646 - fuse.squashfuse squashfuse ro,user_id=1000,group_id=100 mntTarget := fields[4] if mntTarget == mountPath { return true, nil } } return false, nil } sif-2.8.3/test/000077500000000000000000000000001432675271000132735ustar00rootroot00000000000000sif-2.8.3/test/gen_sifs.go000077500000000000000000000124731432675271000154310ustar00rootroot00000000000000// Copyright (c) 2020-2022, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the LICENSE.md file // distributed with the sources of this project regarding your rights to use or distribute this // software. package main import ( "bytes" "errors" "log" "os" "path/filepath" "time" "github.com/ProtonMail/go-crypto/openpgp" "github.com/sylabs/sif/v2/pkg/integrity" "github.com/sylabs/sif/v2/pkg/sif" ) var errUnexpectedNumEntities = errors.New("unexpected number of entities") func getEntity() (*openpgp.Entity, error) { f, err := os.Open(filepath.Join("keys", "private.asc")) if err != nil { return nil, err } defer f.Close() el, err := openpgp.ReadArmoredKeyRing(f) if err != nil { return nil, err } if len(el) != 1 { return nil, errUnexpectedNumEntities } return el[0], nil } func generateImages() error { e, err := getEntity() if err != nil { return err } objectGenericJSON := func() (sif.DescriptorInput, error) { return sif.NewDescriptorInput(sif.DataGenericJSON, bytes.NewReader([]byte{0x7b, 0x7d}), sif.OptObjectName("data.json"), ) } objectCryptoMessage := func() (sif.DescriptorInput, error) { return sif.NewDescriptorInput(sif.DataCryptoMessage, bytes.NewReader([]byte{0xfe, 0xfe, 0xf0, 0xf0}), sif.OptCryptoMessageMetadata(sif.FormatOpenPGP, sif.MessageClearSignature), ) } objectSBOM := func() (sif.DescriptorInput, error) { b, err := os.ReadFile(filepath.Join("input", "sbom.cdx.json")) if err != nil { return sif.DescriptorInput{}, err } return sif.NewDescriptorInput(sif.DataSBOM, bytes.NewReader(b), sif.OptSBOMMetadata(sif.SBOMFormatCycloneDXJSON), ) } partSystem := func() (sif.DescriptorInput, error) { return sif.NewDescriptorInput(sif.DataPartition, bytes.NewReader([]byte{0xfa, 0xce, 0xfe, 0xed}), sif.OptPartitionMetadata(sif.FsRaw, sif.PartSystem, "386"), ) } partPrimSys := func() (sif.DescriptorInput, error) { b, err := os.ReadFile(filepath.Join("input", "root.squashfs")) if err != nil { return sif.DescriptorInput{}, err } return sif.NewDescriptorInput(sif.DataPartition, bytes.NewReader(b), sif.OptPartitionMetadata(sif.FsSquash, sif.PartPrimSys, "386"), ) } partSystemGroup2 := func() (sif.DescriptorInput, error) { b, err := os.ReadFile(filepath.Join("input", "root.ext3")) if err != nil { return sif.DescriptorInput{}, err } return sif.NewDescriptorInput(sif.DataPartition, bytes.NewReader(b), sif.OptPartitionMetadata(sif.FsExt3, sif.PartSystem, "amd64"), sif.OptGroupID(2), ) } images := []struct { path string diFns []func() (sif.DescriptorInput, error) opts []sif.CreateOpt sign bool }{ // Images with no objects. { path: "empty.sif", }, { path: "empty-id.sif", opts: []sif.CreateOpt{ sif.OptCreateWithID("3fa802cc-358b-45e3-bcc0-69dc7a45f9f8"), }, }, { path: "empty-launch-script.sif", opts: []sif.CreateOpt{ sif.OptCreateWithLaunchScript("#!/usr/bin/env run-script\n"), }, }, // Images with one data object in one group. { path: "one-object-time.sif", opts: []sif.CreateOpt{ sif.OptCreateWithTime(time.Date(2020, 6, 30, 0, 1, 56, 0, time.UTC)), }, diFns: []func() (sif.DescriptorInput, error){ objectGenericJSON, }, }, { path: "one-object-generic-json.sif", diFns: []func() (sif.DescriptorInput, error){ objectGenericJSON, }, }, { path: "one-object-crypt-message.sif", diFns: []func() (sif.DescriptorInput, error){ objectCryptoMessage, }, }, { path: "one-object-sbom.sif", diFns: []func() (sif.DescriptorInput, error){ objectSBOM, }, }, // Images with two partitions in one group. { path: "one-group.sif", diFns: []func() (sif.DescriptorInput, error){ partSystem, partPrimSys, }, }, { path: "one-group-signed.sif", diFns: []func() (sif.DescriptorInput, error){ partSystem, partPrimSys, }, sign: true, }, // Images with three partitions in two groups. { path: "two-groups.sif", diFns: []func() (sif.DescriptorInput, error){ partSystem, partPrimSys, partSystemGroup2, }, }, { path: "two-groups-signed.sif", diFns: []func() (sif.DescriptorInput, error){ partSystem, partPrimSys, partSystemGroup2, }, sign: true, }, } for _, image := range images { dis := make([]sif.DescriptorInput, 0, len(image.diFns)) for _, fn := range image.diFns { di, err := fn() if err != nil { return err } dis = append(dis, di) } opts := []sif.CreateOpt{ sif.OptCreateDeterministic(), sif.OptCreateWithDescriptors(dis...), } opts = append(opts, image.opts...) f, err := sif.CreateContainerAtPath(filepath.Join("images", image.path), opts...) if err != nil { return err } defer func() { if err := f.UnloadContainer(); err != nil { log.Printf("failed to unload container: %v", err) } }() if image.sign { s, err := integrity.NewSigner(f, integrity.OptSignWithEntity(e), integrity.OptSignWithTime(func() time.Time { return time.Date(2020, 6, 30, 0, 1, 56, 0, time.UTC) }), integrity.OptSignDeterministic(), ) if err != nil { return err } if err := s.Sign(); err != nil { return err } } } return nil } func main() { if err := generateImages(); err != nil { log.Fatal(err) } } sif-2.8.3/test/images/000077500000000000000000000000001432675271000145405ustar00rootroot00000000000000sif-2.8.3/test/images/empty-id.sif000077500000000000000000000766601432675271000170150ustar00rootroot00000000000000SIF_MAGIC0100?5EizE n n00m}sif-2.8.3/test/images/empty-launch-script.sif000077500000000000000000000766601432675271000211750ustar00rootroot00000000000000#!/usr/bin/env run-script SIF_MAGIC0100 n n00m}sif-2.8.3/test/images/empty.sif000077500000000000000000000766601432675271000164230ustar00rootroot00000000000000SIF_MAGIC0100 n n00m}sif-2.8.3/test/images/one-group-signed-legacy-all.sif000077500000000000000000001311411432675271000224410ustar00rootroot00000000000000#!/usr/bin/env run-singularity SIF_MAGIC0101nvOLks(^n^,0ma2@s(^s(^.01@s(^s(^.01@a]n^n^part-signature\ XK '@an^n^part-signature\ XK 'ޭ-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 SIFHASH: f8722c6694c4997334525090678b2148f6263502c3eb144a44e8be0d2bfd039f4067a3f8152f94ab3af7c63acfe78ce6 -----BEGIN PGP SIGNATURE----- wsBcBAEBCAAQBQJe7m7DCRCiDCfuf/e6hAAAa1YIAHbNQ1P+XEhxfLSDSRHOqFm5 Deyc7IxhGhi+9jzFuXWpAiIJy5SMyYUG6ZoLGqvPLnT1gIm7l/DEt7tJ1MR8NKi0 GFshtbzLeKErlH6uZvE9kPqqW1CMqxHrBPt2C/x9t5Jntul39ohzeLRpIwpIyyXd UV3j11RMSqx77RcPy1K54gD2JJC0qvWIg2UBUkhtUV9EvCQ/Hc6K5CGTQcC7p3ns 1sSs+fhJsfKShf1UTgOv2rdDJmE0VBerus+hUV0UyWfN5Fa9rfrMde9OR67KK7q9 5BZy/taIMJYM+92nVPXK1DlWDGRmgVG02JQVMNkfGcFpI6aFnHT3neXED4aq6wE= =5vdJ -----END PGP SIGNATURE----------BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 SIFHASH: 0b7e0522460767c74abb4245bc0d3a27209a5aed111059faead54ffc74a93759160ac9642d7a7df3038ece62f2fa9815 -----BEGIN PGP SIGNATURE----- wsBcBAEBCAAQBQJe7m7LCRCiDCfuf/e6hAAAZ4oIAIoLG+zjs0Me162Ap98Q/vrk LTGHp3ze4u83r2PNedigs+UVkk9oi6+Gye+/PhGsllQzmDiH/8EunzZ/Uv3ykPkj iyRSNdxxjABmI6TQ5nSz8zxeRwPRjBvEToB3yA2Tj0k41ycbYPI/BmYte+ICluFC aeTyVj8KQ0HE7z2dgAYC18WvQFxFYpgnyuLKDHHkL4fPh+otk+5oRvVVjfUOfmAs znNY3gqxFThI2qMD9a5tQHwvfmFPTTuXWtwEfMqeJRIS3Pb5oe+1WeooB/mzYLoe aePGXqCs15bQXO4XFUJiWsjee+E9MgeWbcD6BeszurRi29opuf4P4rQQc4nQpSg= =I6Ex -----END PGP SIGNATURE-----sif-2.8.3/test/images/one-group-signed-legacy-group.sif000077500000000000000000001211411432675271000230240ustar00rootroot00000000000000#!/usr/bin/env run-singularity SIF_MAGIC0101nvOLks(^n^-0ma"@s(^s(^.01@s(^s(^.01@a]n^n^part-signature\ XK 'ޭ-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 SIFHASH: 81266c932e08340e3876c5e7ebcd3cd5e69c8af76c1a883561ac8077bd78ffc9d6ac86ec79dde7601f347800cb8ce7b8 -----BEGIN PGP SIGNATURE----- wsBcBAEBCAAQBQJe7m63CRCiDCfuf/e6hAAA3xoIAHRmwcEE0hsKGHBJfb9iwPEd jnnbyfYv39F2g25f9Yq4YmZEj+Bt1wil+s5/rHJC/R40wBqBfc3cOSwt8JUrfyK9 j3BckroC99sGxGDxMu6FKwQ5GxzGFN3v6i/x2cyItVVrpahB3YImHnS08/qlEAJ+ r5vG7Ten5nDm0mDGdu5csrKX1KSrFyjtt0V96Cawq7PWWStanm2pcpbBkqkNrM66 z92bldnOtzRDOHv06xAMXK8JpZu5iLAmI5yVdiEHprP5pDpfN1WC31f52o5m0fyS S1r8n4pbuQWC+OQfasXHLXLdNAIQ8VrzjK+TGcc42O6SBUO7kHSEKUmmkOFu080= =HWEo -----END PGP SIGNATURE-----sif-2.8.3/test/images/one-group-signed-legacy.sif000077500000000000000000001211411432675271000216720ustar00rootroot00000000000000#!/usr/bin/env run-singularity SIF_MAGIC0101nvOLks(^n^-0ma"@s(^s(^.01@s(^s(^.01@a]n^n^part-signature\ XK 'ޭ-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 SIFHASH: 0b7e0522460767c74abb4245bc0d3a27209a5aed111059faead54ffc74a93759160ac9642d7a7df3038ece62f2fa9815 -----BEGIN PGP SIGNATURE----- wsBcBAEBCAAQBQJe7m6nCRCiDCfuf/e6hAAA+j0IAIk4ffcu1LzcXX3KvD4uaY7H tPYw8i3Xq+6iC4WVdfkf96/t6HIiXI8wA8RAYu9skwu4hWSSVtC77Lqv4MC2nrQ7 UD8msTMlfDXw2kflYGjbxN/+fR2ZrhyWQ1XetzboqZVdCEeLvmaPPa78G8s79LYf GphHYew34UrFGXZCU8DwP5/YHwnZWD12fKFPFpL5YxY/nX3K3qXjPxpap4lDGdbG 00olMKNbiiZPpHGOAKvdl5ovGQWD0U9BkL9TrDZcR1ag0sRn9f2qrdVOTHwICjyQ Xvmkmw/VED27TBZAsCWuUQ0uquy9GmrohIh4IYxm4jHL3saw0JreQPyuDrNaNn8= =aIKA -----END PGP SIGNATURE-----sif-2.8.3/test/images/one-group-signed.sif000077500000000000000000001220361432675271000204340ustar00rootroot00000000000000SIF_MAGIC0101 n n-0m}n&@T n n01@ n n01@ n n\ XK 'hsqsgCb sHello from Sylabs! 'xcb]I`bFpy&8Vbf ^xc`F(WRQ`xc`( !-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 {"version":1,"header":{"digest":"sha256:635fa0a14a8ef0c0351ed3e985799ed1d4f75ce973dea3cc76c99710795cc3f1"},"objects":[{"relativeId":0,"descriptorDigest":"sha256:3634ad01db0dd5482ecf685267b53d6201690438ca27c3d7ea91c971a1f41f92","objectDigest":"sha256:004dfc8da678c309de28b5386a1e9efd57f536b150c40d29b31506aa0fb17ec2"},{"relativeId":1,"descriptorDigest":"sha256:04b5f87c9692a54f80d10fb6af00c779763aeca29d610348854bd97cd8bf66fd","objectDigest":"sha256:9f9c4e5e131934969b4ac8f495691c70b8c6c8e3f489c2c9ab5f1af82bce0604"}]} -----BEGIN PGP SIGNATURE----- wsBzBAEBCAAnBQJe+oD0CZCiDCfuf/e6hBYhBBIEXIwLEATQWN5L7aIMJ+5/97qE AAC46gf/VXyzZ649nttrX13JkM5kRVPlAIblBQxfoUxA1xwIXdRoM5ceDY0Em+YD 8b6Xl1w2sDTqo0R15cJSh8sf0ClFOvYpDQRNCwKx17k1Wd0gHcW4QVu6gJnlbNvN o/EJdEN2TkbCM2aFvj34DAIfErRBIEsCeDDvJ/6WUSySWbnydfNU2pCsnK4A7l2H KOXFzSaPijG9L/pU3O3vNZ+fXPffqHL9JVhs5Mt/Yo3oeoEnoVaKvJLGx/fyl+Gj 7qsfWFyHWzRCww9VFg/TCBeUku0CYRfXhxOgo4OuHNr8oo82rKDZU6+l3UZ2Sw8T +kLe/zUkaILocGOvhvKdi630OGGb/Q== =3Jq2 -----END PGP SIGNATURE-----sif-2.8.3/test/images/one-group.sif000077500000000000000000001200001432675271000171520ustar00rootroot00000000000000SIF_MAGIC0101 n n.0m}P"@T n n01@ n n01hsqsgCb sHello from Sylabs! 'xcb]I`bFpy&8Vbf ^xc`F(WRQ`xc`( !sif-2.8.3/test/images/one-object-crypt-message.sif000077500000000000000000000766641432675271000220770ustar00rootroot00000000000000SIF_MAGIC0100 n n/0m}@} n nsif-2.8.3/test/images/one-object-generic-json.sif000077500000000000000000000766621432675271000216750ustar00rootroot00000000000000SIF_MAGIC0100 n n/0m}@} n ndata.json{}sif-2.8.3/test/images/one-object-sbom.sif000077500000000000000000000775661432675271000202560ustar00rootroot00000000000000SIF_MAGIC0100 n n/0m} @} n n{ "bomFormat": "CycloneDX", "specVersion": "1.4", "serialNumber": "urn:uuid:c0e8da98-7afc-4807-89a1-928a14dcd910", "version": 1, "metadata": { "timestamp": "2022-10-21T13:27:49Z", "tools": [ { "vendor": "anchore", "name": "syft", "version": "0.59.0" } ], "component": { "bom-ref": "a0f23d5d8e46dd55", "type": "container", "name": "image.sif" } }, "components": [] } sif-2.8.3/test/images/one-object-time.sif000077500000000000000000000766621432675271000202500ustar00rootroot00000000000000SIF_MAGIC0100^^/0m}@}^^data.json{}sif-2.8.3/test/images/two-groups-signed-legacy-all.sif000077500000000000000000001411411432675271000226550ustar00rootroot00000000000000#!/usr/bin/env run-singularity SIF_MAGIC0101 , Fɕ͞Hs(^n^+0maB@s(^s(^.01@s(^s(^.01@s(^s(^.02@a]n^n^part-signature\ XK '@an^n^part-signature\ XK 'ޭ-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 SIFHASH: f8722c6694c4997334525090678b2148f6263502c3eb144a44e8be0d2bfd039f4067a3f8152f94ab3af7c63acfe78ce6 -----BEGIN PGP SIGNATURE----- wsBcBAEBCAAQBQJe7m7GCRCiDCfuf/e6hAAAgKkIAAyVYQcWifSBnj0c4yKegLSd l6YceQ5I8944dsB53dC4PvaIZYjL9Gcfq1yIKuX8xMQzjj7xZ+7q8J1O57Ee/KCV TAsUFgItzOl8tt6OX2ve2+XHVQW8EIP19Iyj07niidwZq330vFqiMTMXasCAjfIB Mn1BjuAQdOPa/F8IJVsuUtjYhNkP8AD/4d+6eYRGRsqA0Wh/Gg+1ETGH3Ljn/1Eo mN9qEJ5mbLkhZkrClJ7bkILPqDuuIsZSjdLiYouKfda2gcLD+FiJl2XJXnqeZ6nQ ACMKDPWeTFKC1EQVImKCV5S4t4DHEm6CBt5aSlLzY7Eyb5rClAeDkWiRoH+2a54= =ze/S -----END PGP SIGNATURE----------BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 SIFHASH: 0b7e0522460767c74abb4245bc0d3a27209a5aed111059faead54ffc74a93759160ac9642d7a7df3038ece62f2fa9815 -----BEGIN PGP SIGNATURE----- wsBcBAEBCAAQBQJe7m7PCRCiDCfuf/e6hAAADz0IAB7Ii+6fMarco1099A/dmFNN /st7uyw4hzbnG2kHLsgH26mOiI1tcwX+Adr9jf8T4ykOPRj/UtKEBjGspt00bLs/ m+cRayN6CRI9OJ6a0hsLSK/Atv53xqnKaFridVy9uBB6rybv6xPJy/xnwPEDjMHE kaV2Ws8xSMlkwP9J9hJM3EymI2NimpklVPl5HhMYtSusG5iSftmRdPlhphQp5wry zpiC4M1AULwZyEP4iN1vWhRNRHJRHDbmQFE///zBGisKbUt6aAl63AJ3vBGPzLYm fyowEDZSL1Aqb/oSvtZLls38mzJbriOsyh1244rgMoDiF+jdAB13E+g9tXcXxXk= =kl1t -----END PGP SIGNATURE-----sif-2.8.3/test/images/two-groups-signed-legacy-group.sif000077500000000000000000001311411432675271000232400ustar00rootroot00000000000000#!/usr/bin/env run-singularity SIF_MAGIC0101 , Fɕ͞Hs(^n^,0ma2@s(^s(^.01@s(^s(^.01@s(^s(^.02@a]n^n^part-signature\ XK 'ޭ-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 SIFHASH: 81266c932e08340e3876c5e7ebcd3cd5e69c8af76c1a883561ac8077bd78ffc9d6ac86ec79dde7601f347800cb8ce7b8 -----BEGIN PGP SIGNATURE----- wsBcBAEBCAAQBQJe7m66CRCiDCfuf/e6hAAABi4IAJq2eVUaPRBNoUdANwd1DuV2 gMSvQGbKojojcVr8QARY7IgRjpweTxTs1+Cak5xkNXs1/AiXcaclGEoA+bjr1pYz UbrUcncsIJwcKHI6NnDZ6CG6pl5A7frUUt2kCvm513PZmgyjgrCSvcdUL0GUyl4k ohxB87amIaKEvJUkF3+t8oC2S2ZJP70pzhoUdH6GFP6M1dCjuJ8OMkEW1x2dhHvp 9MeKxtFyn8RriZPac2QrdYF3HuqhSWC2eHvG88likmEi67ueU7uNvxmaAOfsv5o5 KG6hRI5L32eEWp9kVxwC1OCNmkv3fOfXKCp4lFN8SEpFctBhXnsHqcVnWZ0AKFQ= =ai4E -----END PGP SIGNATURE-----sif-2.8.3/test/images/two-groups-signed-legacy.sif000077500000000000000000001311411432675271000221060ustar00rootroot00000000000000#!/usr/bin/env run-singularity SIF_MAGIC0101 , Fɕ͞Hs(^n^,0ma2@s(^s(^.01@s(^s(^.01@s(^s(^.02@a]n^n^part-signature\ XK 'ޭ-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 SIFHASH: 0b7e0522460767c74abb4245bc0d3a27209a5aed111059faead54ffc74a93759160ac9642d7a7df3038ece62f2fa9815 -----BEGIN PGP SIGNATURE----- wsBcBAEBCAAQBQJe7m6sCRCiDCfuf/e6hAAAP5wIACa6P13o9XwWBp0trOM0f77E YkPTEob8B1draZPZEGDUbCQqV2S4sLI8gNgX06rUPEBipep4qm5u4ivPAfV9Vh/2 9zK+ugaHCEKGgu1FXjxT2WwQQ1t/2MyWDz1idPwKgFPy9/bA0C/Wv++tnnYbAXJi 0dZXGzsu2wsEFiTAjAoXLRquB7OVydB/uetSV7RIc6/rTDgc60Ew8h5QsYtTnZBg oRDLgy9M55oQM+nEoC0IxqQeC48K64Wcxz2kGB0D9kuNfRks/O/Kk6ZVxMu2KMrQ PEoQJqKc6fCaxhNk2X3noneUpxdC3U9ozZFhXu9UgXxXtpO05lwiWaMEJ2ICOUM= =yVbl -----END PGP SIGNATURE-----sif-2.8.3/test/images/two-groups-signed.sif000077500000000000000000011235651432675271000206600ustar00rootroot00000000000000SIF_MAGIC0101 n n+0m})@T n n01@ n n01@ n n02@ n n\ XK '@WW n n\ XK 'hsqsgCb sHello from Sylabs! 'xcb]I`bFpy&8Vbf ^xc`F(WRQ`xc`( ! @8 aDbsDbSнDb 8jE5/home/adam/src/sif/loopfs%=Kt 0|j нDb@8? нDbнDbнDbA:Db8Db8Db@нDbнDbнDb AнDb8Db8Db8DbmDb1DbDb dV . ..  lost+found  hello.txt ...Hello from Sylabs! -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 {"version":1,"header":{"digest":"sha256:635fa0a14a8ef0c0351ed3e985799ed1d4f75ce973dea3cc76c99710795cc3f1"},"objects":[{"relativeId":0,"descriptorDigest":"sha256:3634ad01db0dd5482ecf685267b53d6201690438ca27c3d7ea91c971a1f41f92","objectDigest":"sha256:004dfc8da678c309de28b5386a1e9efd57f536b150c40d29b31506aa0fb17ec2"},{"relativeId":1,"descriptorDigest":"sha256:04b5f87c9692a54f80d10fb6af00c779763aeca29d610348854bd97cd8bf66fd","objectDigest":"sha256:9f9c4e5e131934969b4ac8f495691c70b8c6c8e3f489c2c9ab5f1af82bce0604"}]} -----BEGIN PGP SIGNATURE----- wsBzBAEBCAAnBQJe+oD0CZCiDCfuf/e6hBYhBBIEXIwLEATQWN5L7aIMJ+5/97qE AAC46gf/VXyzZ649nttrX13JkM5kRVPlAIblBQxfoUxA1xwIXdRoM5ceDY0Em+YD 8b6Xl1w2sDTqo0R15cJSh8sf0ClFOvYpDQRNCwKx17k1Wd0gHcW4QVu6gJnlbNvN o/EJdEN2TkbCM2aFvj34DAIfErRBIEsCeDDvJ/6WUSySWbnydfNU2pCsnK4A7l2H KOXFzSaPijG9L/pU3O3vNZ+fXPffqHL9JVhs5Mt/Yo3oeoEnoVaKvJLGx/fyl+Gj 7qsfWFyHWzRCww9VFg/TCBeUku0CYRfXhxOgo4OuHNr8oo82rKDZU6+l3UZ2Sw8T +kLe/zUkaILocGOvhvKdi630OGGb/Q== =3Jq2 -----END PGP SIGNATURE----------BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 {"version":1,"header":{"digest":"sha256:635fa0a14a8ef0c0351ed3e985799ed1d4f75ce973dea3cc76c99710795cc3f1"},"objects":[{"relativeId":0,"descriptorDigest":"sha256:b356c9810f880c619f95c34909387de498895fc2054ef590ebaeab5c9b50c995","objectDigest":"sha256:d2dd40e7ff6b6753d84c1a85061189e61d4de9688d5531537ff96ff09b1f12dc"}]} -----BEGIN PGP SIGNATURE----- wsBzBAEBCAAnBQJe+oD0CZCiDCfuf/e6hBYhBBIEXIwLEATQWN5L7aIMJ+5/97qE AABlUwf9G3oiLqRsILlItwYbpixFDnxOwlfboKUwdvyXtg9MOvHYCP/KyFyh/D18 Gvhde+uy6XkYLmxsXv2nhrkNhUY+2EEZS4suRwlrpKaBrNcI5wt/ig6eHd2G6v/0 GyRfDgA/Lkj1MyeKe60DKD+fSnCeaKIkPOnDt5o0zD6EJtH2RiqkRUfUpT5+i9sc O+pUKbqUPDl1jAN+Bo5bpBy9SysRhLhUflGtGcyD+qPRcMQYX31v0EkFYM/rmCVh KY185uRjwZGQRb6iGXgbzqiQaPb+DCmNGbnPPHAQciU8wT6HNA3tcd4/Jr8Bzcft lKhyMIll8i/ZJHW0ENSyU1KTTpaVOA== =Cbho -----END PGP SIGNATURE-----sif-2.8.3/test/images/two-groups.sif000077500000000000000000011200001432675271000173660ustar00rootroot00000000000000SIF_MAGIC0101 n n-0m}P"@T n n01@ n n01@ n n02hsqsgCb sHello from Sylabs! 'xcb]I`bFpy&8Vbf ^xc`F(WRQ`xc`( ! @8 aDbsDbSнDb 8jE5/home/adam/src/sif/loopfs%=Kt 0|j нDb@8? нDbнDbнDbA:Db8Db8Db@нDbнDbнDb AнDb8Db8Db8DbmDb1DbDb dV . ..  lost+found  hello.txt ...Hello from Sylabs! sif-2.8.3/test/input/000077500000000000000000000000001432675271000144325ustar00rootroot00000000000000sif-2.8.3/test/input/root.ext3000066400000000000000000010000001432675271000162110ustar00rootroot00000000000000 @8 aDbsDbSнDb 8jE5/home/adam/src/sif/loopfs%=Kt 0|j нDb@8? нDbнDbнDbA:Db8Db8Db@нDbнDbнDb AнDb8Db8Db8DbmDb1DbDb dV . ..  lost+found  hello.txt ...Hello from Sylabs! sif-2.8.3/test/input/root.squashfs000066400000000000000000000100001432675271000171630ustar00rootroot00000000000000hsqsgCb sHello from Sylabs! 'xcb]I`bFpy&8Vbf ^xc`F(WRQ`xc`( !sif-2.8.3/test/input/sbom.cdx.json000066400000000000000000000007061432675271000170450ustar00rootroot00000000000000{ "bomFormat": "CycloneDX", "specVersion": "1.4", "serialNumber": "urn:uuid:c0e8da98-7afc-4807-89a1-928a14dcd910", "version": 1, "metadata": { "timestamp": "2022-10-21T13:27:49Z", "tools": [ { "vendor": "anchore", "name": "syft", "version": "0.59.0" } ], "component": { "bom-ref": "a0f23d5d8e46dd55", "type": "container", "name": "image.sif" } }, "components": [] } sif-2.8.3/test/keys/000077500000000000000000000000001432675271000142465ustar00rootroot00000000000000sif-2.8.3/test/keys/private.asc000066400000000000000000000066521432675271000164210ustar00rootroot00000000000000-----BEGIN PGP PRIVATE KEY BLOCK----- lQOYBF6nUPABCACmd6vggtFfkZvYHJRv/u2UfazFL78oLhD05UpqEaS90ripzPN9 G30IF6WqxQHxia0nV/IqJ9Tjozs0nIaK761y69gCYbac27e1r6Pf4uCoTfOWeGVZ TYsbseu6pf8BSDLQMu1S7/P5y5BHthAep9n6zpWr6drPdt20w2HOmTWDhbGw9Sue n12BVoiyNChuT01tDcBlffXn3gN9qIWS6aJLxKbvh88LIsKWkTcFv9bEhHdh0tU8 ckt1xDT6PkkZToHdOl8OqNz4Psy6oELJR1lopdto/xBuWWTsx4hBM7mnIrNdvN/W qNXzIP1UAHNG24lLaGpL410HDG6E+P/knhjdABEBAAEAB/4piJ8+L3WM2jgfhhX1 EY52Z5rVQhH4NPIvIAxehZNsdyhy2TuBIfwiqj+/6VKQULD+qX82zGRd/YqWmF0r ShylGTn2cinNXjaqYq3I/QLfiz908goba3EDUnOFyMfkqn5fGu3CrTLaxtLzSXYh J/EHlkaas4jQDZDIep0rHmfPLgO1h6zWzCJEEwciK/sBWiUHm4lHIC9BHWp3oMwF HYiaj9//9+/5ofP2WfMRbf4hM83HHwOj2YFSiSOlzhFKJhhgluOzlhlNqPtqgxpP qI8a4E5hYkgMIThoIUKbW0869jSOVEY/P7h9D2rI0zH+M9T6OmaikrX83HKB699M zS5xBADIOoDWQcPDZSDf06+JIVGBJ5Rw2v6Q/DZqrOEOb8kncpyMJFbuc7GhIx3g 4ZZMzp6+c+hKdw/Mm+431nqcEUJKRvoaFRUvhAea5sRrRJtOx9CbzqyvSWvon16a 99l2Yd3mljSKfsz5sNlwZOMXj84ia8drhVdhmhynF5OLatXglQQA1NXNNwWPYSZz r4kaytnGsMJ+iKbZ8WHI52aCcX6cuRBeOget2EbwicU1NOnFpP7YiE+G8SXq73LZ 2F1QCe5boc90QDVAx8yO6hFxCf1By58YuNcgnCU+k1W0eLOul78U6mRusAPPTu9j 1es1dK4jk2oBwHUBMLOz+ZVkPvnrXSkD/2xo+U441tM1+w+9njSsS4huaobqKgvx OKy4PpWh25IjOd3ODdrKpLeR5DHdtvl0b2ph1tOTgnOrDF3lCQ9y50f46JDY+Usm /0hV9Vg7bXS6hpW36M+StuxbxNFoIcJOaStkWnOaysKQQfQ7qKu1v3yXu5woGlmM q8fAGOQ9zkOhStS0GVVuaXQgVGVzdCA8dW5pdEB0ZXN0LmNvbT6JAU4EEwEIADgC GwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AWIQQSBFyMCxAE0FjeS+2iDCfuf/e6 hAUCYnwW1gAKCRCiDCfuf/e6hNu/B/9ypP+dMhn4zqCXPNmxT/3GZ+NeTPiUF1lF BmF+cFc8LTVrJ5WFGTAfTCXFKeGsHMMu7F0CdR6pN8+gGagqouMbqsLUXbZnYyyr R3FRHDyDf3Ei9BTeygXzWKnyGMhmPmL1V+BEN4AegQeRFnfcJXHfjw01QBBkudwj EGPOmpiBcUBo9q3SSha9qUDolgwqVVTQsofn+SWQ5peKOrvMqMsVlg6dK9DVf4i0 aII4G2SQ7r60YCuTu7hixTtwkKd7CJEX+MsPWU7WQp4V/+1fCMmZ3AJZaHrNE2a+ Jqme3Y+jPcFkyUWfqEc6dtzYOnzXKWCBj9uiF+4t+SknFL9xfz9snQOYBF6nUPAB CAC/yLh6jYYFrWwQp0NQJtBXsw2iK2TJ42mZdtCUeRmr82eBui+JoiCJVleQNr5O e+JFbIeI6VwxR+n8ct5jDHOP5skjVAhzPNZ7jwrrVlZbeW/BVnILEUuo6CiqJY3F CIuOncX5IAH/0jyDRkz50rFqPAAODyV5TTFCViBdtAYZZ3r4pqg5z7a4CRZmn/+A o3/27opAgt96VUkIqIQLIukiquS7ZSLcJrJxxS6QjDcy0gswdLbenG9FXtwEcUK2 Jdc8IAq5WVkzE4xOcgE9JeV9L2/449MStZm/nkzFteutPWc9PpTXSDWu+H4U9+Wo ZW5OwINRe9VpNVv7UlxW80VpABEBAAEAB/wJ5Hwjki5CF7Z1y3Ls7PudMna3ET7z LQBS8q6CohaBaJ5DsktmcY71FpeQsEozuS8sPpNlLAhd4GRA6dnvyQIi/5gLcve2 ngJAQFojVoJA2Kw7kE50pLE+5q7GTAaajbzJH/lIxu5jeEA300YAMu6E2NB16TEZ JzKtxcyImNMhtz434EXvPp01T8NxukBKYjfJ6cZ1hFIahFoZGZ9GemFGx2FRfM0C U/yXIYTwXUBkLaE8U6bx1cU7ujaBuu/uMN0FA+37UHBSQWh+9yiDQ0+Iq7D3WkjT AgGdQ/GAhTDVtt9Y7h4cozNvlSS/VrEyH0nJbPMCbEDoB65yim0/+N+JBADD7u0+ ajZtyW5JpE1eyucn2VDg1m34QZU0/h5bBnMy6P1zhRO/8fqIKta00y9iY7PAvYtF ivY18zrmNYmaQFAe6Cawm9/Qb2db7sbex5tMXfqKB0pItl05RKUK3BRM69iLYg76 jtPSZerfSJJVGhpQmFJxa3EdcumzEyiUnm4bPQQA+pQoESGvSZ6GJPu/fGz/duOt w4TBNtMWlt2dtSVtWru6r/aVWlkAXMdvNWdSGKrEXqvxo39xIhKIW742BlqWlZ2f sJQb/9UmBFjhDg5poj+jpATQ2MFhOlSc4un+0KE7R3sz6n98S8AN76PPlPZOwgbz BCAub/+kMCQcnog75Z0EAJdYFYs/gUnvOmfoKZisIn8OZ6aa6LWqWhFCSgNU03I6 +wWyEjJsbcBRBXEpuGfeVfAUDJSVSv5lJg2fOU0p3ephoiZSORaCZovXTPQgTgO0 afcSUP5g8o/Tjp1QPETBd/Rd/Br5WBdfoF91ZUhblvs04qb+lswvJXYZ6caeot1E PByJATYEGAEIACACGwwWIQQSBFyMCxAE0FjeS+2iDCfuf/e6hAUCYnwXAgAKCRCi DCfuf/e6hHFVB/0b1W2AvcRRXUH4HCmapgGsmLU1k/PEVHz1FmOX+a7UDEh8moVg fVeaV9pR6gKGHs6LMH3eDxF2LNAPNzzs7V3+RjeIDvxnFkld3BSlNltR1v7uz8tl SSUX7zASAhUyT4Qq3Lvo1TdQdgK9HnZJuKzNHofUq4v71xWWIfyYSwGmu3OAtxpH /xb0bAYnrPG9hruy/ZgL2KP4Irb3a1zFBIKIjUN4iTbHpkLeNIOygbOK3GDnUTGe Q4v8IP2ZtLVMlVjYJNMOV9zNnNzP7dj+ua7rOiqbBWKZaRvG7o1YZUx7Km6xhG4y EZ/ZSGnSuqYT1FabxNxb1kR4eRMWbeDjk7uX =+DIa -----END PGP PRIVATE KEY BLOCK-----