summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorReinhard Tartler <siretart@tauware.de>2022-11-26 11:30:56 -0500
committerReinhard Tartler <siretart@tauware.de>2022-11-26 11:30:56 -0500
commitd6caca7a938130982d713b6be0ca0823f46b0b4c (patch)
treedc35be0da27e89eb9208a44aabbd24b00df629db
parentdc1634f506b369be461ff65c77b2a804e06df87f (diff)
parentdab73e7c0f7576075e7f073f71cc3f32a656b161 (diff)
Update upstream source from tag 'upstream/2.8.3'
Update to upstream version '2.8.3' with Debian dir 7536ac56d7f18b29fd849f99100170df028e78fe
-rw-r--r--.circleci/config.yml30
-rw-r--r--.github/dependabot.yml2
-rw-r--r--.golangci.yml17
-rw-r--r--.goreleaser.yml91
-rw-r--r--CONTRIBUTING.md4
-rw-r--r--LICENSE.md2
-rw-r--r--README.md20
-rw-r--r--cmd/siftool/siftool.go21
-rw-r--r--go.mod27
-rw-r--r--go.sum832
-rw-r--r--internal/app/siftool/app.go15
-rw-r--r--internal/app/siftool/file.go17
-rw-r--r--internal/app/siftool/info.go24
-rw-r--r--internal/app/siftool/info_test.go15
-rw-r--r--internal/app/siftool/modif_test.go10
-rw-r--r--internal/app/siftool/mount.go20
-rw-r--r--internal/app/siftool/testdata/TestApp_Dump/Three.golden18
-rw-r--r--internal/app/siftool/testdata/TestApp_Dump/Two.goldenbin4 -> 4096 bytes
-rw-r--r--internal/app/siftool/testdata/TestApp_Header/OneGroup.golden2
-rw-r--r--internal/app/siftool/testdata/TestApp_Header/OneObjectCryptMessage.golden2
-rw-r--r--internal/app/siftool/testdata/TestApp_Header/OneObjectGenericJSON.golden2
-rw-r--r--internal/app/siftool/testdata/TestApp_Header/OneObjectSBOM.golden7
-rw-r--r--internal/app/siftool/testdata/TestApp_Header/OneObjectTime.golden2
-rw-r--r--internal/app/siftool/testdata/TestApp_Header/TwoGroups.golden2
-rw-r--r--internal/app/siftool/testdata/TestApp_Header/TwoGroupsSigned.golden2
-rw-r--r--internal/app/siftool/testdata/TestApp_Info/CryptMessage.golden2
-rw-r--r--internal/app/siftool/testdata/TestApp_Info/DataPartitionEXT3.golden2
-rw-r--r--internal/app/siftool/testdata/TestApp_Info/DataPartitionSquashFS.golden2
-rw-r--r--internal/app/siftool/testdata/TestApp_Info/DataSignature.golden2
-rw-r--r--internal/app/siftool/testdata/TestApp_Info/GenericJSON.golden2
-rw-r--r--internal/app/siftool/testdata/TestApp_Info/SBOM.golden7
-rw-r--r--internal/app/siftool/testdata/TestApp_Info/Time.golden2
-rw-r--r--internal/app/siftool/testdata/TestApp_List/OneGroup.golden2
-rw-r--r--internal/app/siftool/testdata/TestApp_List/OneGroupSigned.golden2
-rw-r--r--internal/app/siftool/testdata/TestApp_List/OneObjectCryptMessage.golden2
-rw-r--r--internal/app/siftool/testdata/TestApp_List/OneObjectGenericJSON.golden2
-rw-r--r--internal/app/siftool/testdata/TestApp_List/OneObjectSBOM.golden4
-rw-r--r--internal/app/siftool/testdata/TestApp_List/OneObjectTime.golden2
-rw-r--r--internal/app/siftool/testdata/TestApp_List/TwoGroups.golden4
-rw-r--r--internal/app/siftool/testdata/TestApp_List/TwoGroupsSigned.golden8
-rw-r--r--internal/app/siftool/unmount.go20
-rw-r--r--mage.go17
-rw-r--r--magefile.go112
-rw-r--r--pkg/integrity/clearsign.go105
-rw-r--r--pkg/integrity/clearsign_test.go158
-rw-r--r--pkg/integrity/digest.go26
-rw-r--r--pkg/integrity/digest_test.go76
-rw-r--r--pkg/integrity/doc.go6
-rw-r--r--pkg/integrity/metadata_test.go26
-rw-r--r--pkg/integrity/select.go31
-rw-r--r--pkg/integrity/sign.go94
-rw-r--r--pkg/integrity/sign_test.go260
-rw-r--r--pkg/integrity/testdata/TestDigest_MarshalJSON/SHA1.golden1
-rw-r--r--pkg/integrity/testdata/TestDigest_MarshalJSON/SHA512_224.golden1
-rw-r--r--pkg/integrity/testdata/TestDigest_MarshalJSON/SHA512_256.golden1
-rw-r--r--pkg/integrity/testdata/TestGetHeaderMetadata/SHA1.golden1
-rw-r--r--pkg/integrity/testdata/TestGetHeaderMetadata/SHA512_224.golden1
-rw-r--r--pkg/integrity/testdata/TestGetHeaderMetadata/SHA512_256.golden1
-rw-r--r--pkg/integrity/testdata/TestGetImageMetadata/Object1.golden2
-rw-r--r--pkg/integrity/testdata/TestGetImageMetadata/Object2.golden2
-rw-r--r--pkg/integrity/testdata/TestGetImageMetadata/SHA1.golden1
-rw-r--r--pkg/integrity/testdata/TestGetImageMetadata/SHA224.golden2
-rw-r--r--pkg/integrity/testdata/TestGetImageMetadata/SHA256.golden2
-rw-r--r--pkg/integrity/testdata/TestGetImageMetadata/SHA384.golden2
-rw-r--r--pkg/integrity/testdata/TestGetImageMetadata/SHA512.golden2
-rw-r--r--pkg/integrity/testdata/TestGetObjectMetadata/RelativeID.golden2
-rw-r--r--pkg/integrity/testdata/TestGetObjectMetadata/SHA1.golden1
-rw-r--r--pkg/integrity/testdata/TestGetObjectMetadata/SHA512_224.golden1
-rw-r--r--pkg/integrity/testdata/TestGetObjectMetadata/SHA512_256.golden1
-rw-r--r--pkg/integrity/testdata/TestGroupSigner_Sign/Group1.golden (renamed from pkg/integrity/testdata/TestGroupSigner_SignWithEntity/Group1.golden)bin33692 -> 33230 bytes
-rw-r--r--pkg/integrity/testdata/TestGroupSigner_Sign/Group2.golden (renamed from pkg/integrity/testdata/TestGroupSigner_SignWithEntity/Group2.golden)bin33545 -> 33031 bytes
-rw-r--r--pkg/integrity/testdata/TestGroupSigner_Sign/Object1.golden (renamed from pkg/integrity/testdata/TestGroupSigner_SignWithEntity/Object1.golden)bin33545 -> 33031 bytes
-rw-r--r--pkg/integrity/testdata/TestGroupSigner_Sign/Object2.golden (renamed from pkg/integrity/testdata/TestGroupSigner_SignWithEntity/Object2.golden)bin33545 -> 33031 bytes
-rw-r--r--pkg/integrity/testdata/TestGroupSigner_SignWithEntity/SignatureConfigSHA256.goldenbin33692 -> 0 bytes
-rw-r--r--pkg/integrity/testdata/TestGroupSigner_SignWithEntity/SignatureConfigSHA384.goldenbin33692 -> 0 bytes
-rw-r--r--pkg/integrity/testdata/TestGroupSigner_SignWithEntity/SignatureConfigSHA512.goldenbin33692 -> 0 bytes
-rw-r--r--pkg/integrity/testdata/TestSignAndEncodeJSON/DefaultHash.golden15
-rw-r--r--pkg/integrity/testdata/TestSignAndEncodeJSON/SHA1.golden15
-rw-r--r--pkg/integrity/testdata/TestSignAndEncodeJSON/SHA224.golden15
-rw-r--r--pkg/integrity/testdata/TestSignAndEncodeJSON/SHA256.golden15
-rw-r--r--pkg/integrity/testdata/TestSignAndEncodeJSON/SHA384.golden15
-rw-r--r--pkg/integrity/testdata/TestSignAndEncodeJSON/SHA512.golden15
-rw-r--r--pkg/integrity/testdata/TestSigner_Sign/EncryptedKey.goldenbin36868 -> 40960 bytes
-rw-r--r--pkg/integrity/testdata/TestSigner_Sign/NoKeyMaterial.goldenbin36868 -> 0 bytes
-rw-r--r--pkg/integrity/testdata/TestSigner_Sign/OneGroup.goldenbin42014 -> 42014 bytes
-rw-r--r--pkg/integrity/testdata/TestSigner_Sign/OptSignDeterministic.goldenbin42014 -> 42014 bytes
-rw-r--r--pkg/integrity/testdata/TestSigner_Sign/OptSignGroup1.goldenbin46110 -> 304158 bytes
-rw-r--r--pkg/integrity/testdata/TestSigner_Sign/OptSignGroup2.goldenbin45911 -> 303959 bytes
-rw-r--r--pkg/integrity/testdata/TestSigner_Sign/OptSignObject1.goldenbin45911 -> 303959 bytes
-rw-r--r--pkg/integrity/testdata/TestSigner_Sign/OptSignObject2.goldenbin45911 -> 303959 bytes
-rw-r--r--pkg/integrity/testdata/TestSigner_Sign/OptSignObject3.goldenbin45911 -> 303959 bytes
-rw-r--r--pkg/integrity/testdata/TestSigner_Sign/OptSignObjects.goldenbin50007 -> 305013 bytes
-rw-r--r--pkg/integrity/testdata/TestSigner_Sign/TwoGroups.goldenbin50007 -> 305013 bytes
-rw-r--r--pkg/integrity/testdata/Test_clearsignEncoder_signMessage/OK.golden15
-rw-r--r--pkg/integrity/verify.go356
-rw-r--r--pkg/integrity/verify_test.go854
-rw-r--r--pkg/sif/arch.go5
-rw-r--r--pkg/sif/buffer.go14
-rw-r--r--pkg/sif/create.go4
-rw-r--r--pkg/sif/descriptor.go36
-rw-r--r--pkg/sif/descriptor_input.go35
-rw-r--r--pkg/sif/descriptor_input_test.go17
-rw-r--r--pkg/sif/descriptor_test.go111
-rw-r--r--pkg/sif/sif.go168
-rw-r--r--pkg/sif/testdata/TestAddObject/Empty.goldenbin32770 -> 32178 bytes
-rw-r--r--pkg/sif/testdata/TestAddObject/NotEmpty.goldenbin36866 -> 32770 bytes
-rw-r--r--pkg/sif/testdata/TestAddObject/NotEmptyAligned.goldenbin32898 -> 32258 bytes
-rw-r--r--pkg/sif/testdata/TestAddObject/NotEmptyNotAligned.goldenbin32772 -> 32180 bytes
-rw-r--r--pkg/sif/testdata/TestAddObject/WithTime.goldenbin32770 -> 32178 bytes
-rw-r--r--pkg/sif/testdata/TestCreateContainer/OneDescriptor.goldenbin32770 -> 32178 bytes
-rw-r--r--pkg/sif/testdata/TestCreateContainer/TwoDescriptors.goldenbin36866 -> 32770 bytes
-rw-r--r--pkg/sif/testdata/TestCreateContainerAtPath/OneDescriptor.goldenbin32770 -> 32178 bytes
-rw-r--r--pkg/sif/testdata/TestCreateContainerAtPath/TwoDescriptors.goldenbin36866 -> 32770 bytes
-rw-r--r--pkg/sif/testdata/TestDeleteObject/WithTime.goldenbin32770 -> 32178 bytes
-rw-r--r--pkg/sif/testdata/TestDeleteObject/Zero.goldenbin32770 -> 32178 bytes
-rw-r--r--pkg/sif/testdata/TestNewDescriptorInput/OptSBOMMetadata.goldenbin0 -> 585 bytes
-rw-r--r--pkg/siftool/add.go65
-rw-r--r--pkg/siftool/add_test.go4
-rw-r--r--pkg/siftool/del_test.go4
-rw-r--r--pkg/siftool/dump_test.go4
-rw-r--r--pkg/siftool/header_test.go4
-rw-r--r--pkg/siftool/info_test.go4
-rw-r--r--pkg/siftool/list_test.go4
-rw-r--r--pkg/siftool/mount.go27
-rw-r--r--pkg/siftool/mount_test.go61
-rw-r--r--pkg/siftool/new_test.go4
-rw-r--r--pkg/siftool/setprim_test.go4
-rw-r--r--pkg/siftool/siftool.go19
-rw-r--r--pkg/siftool/siftool_test.go21
-rw-r--r--pkg/siftool/testdata/TestAddCommands/Add/out.golden14
-rw-r--r--pkg/siftool/testdata/TestAddCommands/Mount/err.golden0
-rw-r--r--pkg/siftool/testdata/TestAddCommands/Mount/out.golden10
-rw-r--r--pkg/siftool/testdata/TestAddCommands/SifToolExperimental/err.golden0
-rw-r--r--pkg/siftool/testdata/TestAddCommands/SifToolExperimental/out.golden19
-rw-r--r--pkg/siftool/testdata/Test_command_getDump/Three/out.golden18
-rw-r--r--pkg/siftool/testdata/Test_command_getDump/Two/out.goldenbin4 -> 4096 bytes
-rw-r--r--pkg/siftool/testdata/Test_command_getHeader/OneGroup/out.golden2
-rw-r--r--pkg/siftool/testdata/Test_command_getHeader/TwoGroups/out.golden2
-rw-r--r--pkg/siftool/testdata/Test_command_getHeader/TwoGroupsSigned/out.golden2
-rw-r--r--pkg/siftool/testdata/Test_command_getInfo/Two/out.golden2
-rw-r--r--pkg/siftool/testdata/Test_command_getList/OneGroup/out.golden2
-rw-r--r--pkg/siftool/testdata/Test_command_getList/OneGroupSigned/out.golden2
-rw-r--r--pkg/siftool/testdata/Test_command_getList/TwoGroups/out.golden4
-rw-r--r--pkg/siftool/testdata/Test_command_getList/TwoGroupsSigned/out.golden8
-rw-r--r--pkg/siftool/testdata/Test_command_getMount/Empty/err.golden1
-rw-r--r--pkg/siftool/testdata/Test_command_getMount/Empty/out.golden9
-rw-r--r--pkg/siftool/testdata/Test_command_getMount/OneGroup/err.golden0
-rw-r--r--pkg/siftool/testdata/Test_command_getMount/OneGroup/out.golden0
-rw-r--r--pkg/siftool/testdata/Test_command_getUnmount/err.golden0
-rw-r--r--pkg/siftool/testdata/Test_command_getUnmount/out.golden0
-rw-r--r--pkg/siftool/unmount.go27
-rw-r--r--pkg/siftool/unmount_test.go42
-rw-r--r--pkg/user/mount.go122
-rw-r--r--pkg/user/unmount.go93
-rw-r--r--pkg/user/unmount_test.go139
-rwxr-xr-xtest/gen_sifs.go35
-rwxr-xr-xtest/images/one-group-signed.sifbin42014 -> 42014 bytes
-rwxr-xr-xtest/images/one-group.sifbin36868 -> 40960 bytes
-rwxr-xr-xtest/images/one-object-crypt-message.sifbin32772 -> 32180 bytes
-rwxr-xr-xtest/images/one-object-generic-json.sifbin32770 -> 32178 bytes
-rwxr-xr-xtest/images/one-object-sbom.sifbin0 -> 32630 bytes
-rwxr-xr-xtest/images/one-object-time.sifbin32770 -> 32178 bytes
-rwxr-xr-xtest/images/two-groups-signed.sifbin50007 -> 305013 bytes
-rwxr-xr-xtest/images/two-groups.sifbin40964 -> 303104 bytes
-rw-r--r--test/input/root.ext3bin0 -> 262144 bytes
-rw-r--r--test/input/root.squashfsbin0 -> 4096 bytes
-rw-r--r--test/input/sbom.cdx.json22
-rw-r--r--test/keys/private.asc70
168 files changed, 2406 insertions, 2353 deletions
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 1d4775b..5f91633 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -6,16 +6,16 @@ orbs:
executors:
node:
docker:
- - image: node:16-slim
+ - image: node:18-slim
golangci-lint:
docker:
- - image: golangci/golangci-lint:v1.43-alpine
+ - image: golangci/golangci-lint:v1.50-alpine
golang-previous:
docker:
- - image: golang:1.16
+ - image: golang:1.18
golang-latest:
docker:
- - image: golang:1.17
+ - image: golang:1.19
jobs:
lint-markdown:
@@ -59,6 +59,17 @@ jobs:
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:
@@ -68,7 +79,7 @@ jobs:
- checkout
- run:
name: Build Source
- command: go run mage.go build:source
+ command: go build ./...
unit-test:
parameters:
@@ -79,7 +90,7 @@ jobs:
- checkout
- run:
name: Run Unit Tests
- command: go run mage.go cover:unit cover.out
+ command: go test -coverprofile cover.out -race ./...
- codecov/upload:
file: cover.out
@@ -88,6 +99,9 @@ jobs:
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
@@ -96,6 +110,9 @@ jobs:
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
@@ -108,6 +125,7 @@ workflows:
- lint-source
- check-go-mod
- check-test-corpus
+ - check-vulnerabilities
- build-source:
matrix:
parameters:
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index d7d1d9d..5ba0ca4 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -9,4 +9,4 @@ updates:
directory: "/"
schedule:
interval: daily
- target-branch: master
+ target-branch: main
diff --git a/.golangci.yml b/.golangci.yml
index 11ceb3a..fb7edca 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -1,19 +1,18 @@
-run:
- build-tags:
- - mage
-
linters:
disable-all: true
enable:
- asciicheck
- bidichk
- bodyclose
+ - containedctx
- contextcheck
- - deadcode
+ - decorder
- depguard
- dogsled
- dupl
+ - dupword
- errcheck
+ - errchkjson
- errname
- errorlint
- exportloopref
@@ -29,23 +28,25 @@ linters:
- gosec
- gosimple
- govet
+ - grouper
- ineffassign
+ - interfacebloat
- ireturn
- lll
+ - maintidx
- misspell
- nilnil
- nolintlint
+ - nonamedreturns
- prealloc
+ - reassign
- revive
- - rowserrcheck
- staticcheck
- - structcheck
- tenv
- typecheck
- unconvert
- unparam
- unused
- - varcheck
- whitespace
linters-settings:
diff --git a/.goreleaser.yml b/.goreleaser.yml
index a761369..c3525e6 100644
--- a/.goreleaser.yml
+++ b/.goreleaser.yml
@@ -1,46 +1,73 @@
-project_name: siftool
-
release:
- github:
- owner: sylabs
- name: sif
prerelease: auto
+changelog:
+ use: github-native
+
+gomod:
+ proxy: true
+ env:
+ - GOPROXY=https://proxy.golang.org,direct
+ - GOSUMDB=sum.golang.org
+
builds:
- - binary: siftool
+ - id: darwin-builds
+ binary: siftool
goos:
- darwin
- - linux
goarch:
- amd64
- - arm
- arm64
- goarm:
- - 6
- - 7
- env:
+ main: &build-main ./cmd/siftool
+ mod_timestamp: &build-timestamp '{{ .CommitTimestamp }}'
+ env: &build-env
- CGO_ENABLED=0
- flags: '-trimpath'
- ldflags: '-s -w -X main.version={{ .Version }} -X main.commit={{ .FullCommit }} -X main.date={{ .CommitDate }} -X main.builtBy=goreleaser'
- main: ./cmd/siftool
- mod_timestamp: '{{ .CommitTimestamp }}'
+ 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:
- - format: tar.gz
- wrap_in_directory: true
- name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
- files:
- - README.md
+ - id: darwin-archives
+ builds:
+ - darwin-builds
-checksum:
- name_template: '{{ .ProjectName }}-{{ .Version }}-checksums.txt'
+ - id: linux-archives
+ builds:
+ - linux-builds
-changelog:
- sort: asc
- filters:
- exclude:
- - '^dev:'
- - '^docs:'
- - '^test:'
- - '^Merge branch'
- - '^Merge pull request'
+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"]
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 1e0c728..2217a43 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -26,8 +26,8 @@ all your interactions with the project members and users.
#### Process
-1. Essential bug fix PRs should be sent to both `master` and release branches (if applicable).
-1. Small bug fix and feature enhancement PRs should be sent to `master` only.
+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.
diff --git a/LICENSE.md b/LICENSE.md
index 30ea0e7..dea3e40 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -1,6 +1,6 @@
# LICENSE
-Copyright (c) 2018-2021, Sylabs Inc. All rights reserved.
+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:
diff --git a/README.md b/README.md
index 4ad5122..0ccf811 100644
--- a/README.md
+++ b/README.md
@@ -2,9 +2,9 @@
[![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/master/graph/badge.svg)](https://app.codecov.io/gh/sylabs/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)
-[![Built with Mage](https://magefile.org/badge.svg)](https://magefile.org)
+[![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.
@@ -12,21 +12,9 @@ This module contains an open source implementation of the Singularity Image Form
Unless otherwise noted, the SIF source files are distributed under the BSD-style license found in the [LICENSE.md](LICENSE.md) file.
-## Download and Install From Source
+## Install Siftool
-To get the sif package to use directly from your programs:
-
-```sh
-go get -d github.com/sylabs/sif/v2
-```
-
-To get the siftool CLI program installed to `$(go env GOPATH)/bin` to manipulate SIF container files:
-
-```sh
-git clone https://github.com/sylabs/sif
-cd sif
-go run mage.go install
-```
+Pre-built binaries are available with the [latest release](https://github.com/sylabs/sif/releases).
## Go Version Compatibility
diff --git a/cmd/siftool/siftool.go b/cmd/siftool/siftool.go
index 41620d8..bad8279 100644
--- a/cmd/siftool/siftool.go
+++ b/cmd/siftool/siftool.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2018-2021, Sylabs Inc. All rights reserved.
+// Copyright (c) 2018-2022, Sylabs Inc. All rights reserved.
// Copyright (c) 2017, SingularityWare, LLC. All rights reserved.
// Copyright (c) 2017, Yannick Cote <yhcote@gmail.com> All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the
@@ -12,6 +12,7 @@ import (
"io"
"os"
"runtime"
+ "strconv"
"text/tabwriter"
"github.com/spf13/cobra"
@@ -24,7 +25,6 @@ var (
date = ""
builtBy = ""
commit = ""
- state = ""
)
func writeVersion(w io.Writer) error {
@@ -38,11 +38,7 @@ func writeVersion(w io.Writer) error {
}
if commit != "" {
- if state == "" {
- fmt.Fprintf(tw, "Commit:\t%v\n", commit)
- } else {
- fmt.Fprintf(tw, "Commit:\t%v (%v)\n", commit, state)
- }
+ fmt.Fprintf(tw, "Commit:\t%v\n", commit)
}
if date != "" {
@@ -79,7 +75,16 @@ possible to modify a SIF file via this tool via the add/del commands.`,
root.AddCommand(getVersion())
- if err := siftool.AddCommands(&root); err != nil {
+ 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)
}
diff --git a/go.mod b/go.mod
index 8db9792..6678ef5 100644
--- a/go.mod
+++ b/go.mod
@@ -1,35 +1,20 @@
module github.com/sylabs/sif/v2
-go 1.17
+go 1.18
require (
- github.com/ProtonMail/go-crypto v0.0.0-20211112122917-428f8eabeeb3
+ github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4
github.com/google/uuid v1.3.0
- github.com/magefile/mage v1.11.0
github.com/sebdah/goldie/v2 v2.5.3
- github.com/spf13/cobra v1.3.0
+ github.com/spf13/cobra v1.6.1
github.com/spf13/pflag v1.0.5
- github.com/sylabs/release-tools v0.1.0
)
require (
- github.com/Microsoft/go-winio v0.5.0 // indirect
- github.com/acomagu/bufpipe v1.0.3 // indirect
- github.com/blang/semver/v4 v4.0.0 // indirect
- github.com/emirpasic/gods v1.12.0 // indirect
- github.com/go-git/gcfg v1.5.0 // indirect
- github.com/go-git/go-billy/v5 v5.3.1 // indirect
- github.com/go-git/go-git/v5 v5.4.2 // indirect
- github.com/imdario/mergo v0.3.12 // indirect
- github.com/inconshreveable/mousetrap v1.0.0 // indirect
- github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
- github.com/kevinburke/ssh_config v1.1.0 // indirect
- github.com/mitchellh/go-homedir v1.1.0 // indirect
+ 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
- github.com/xanzy/ssh-agent v0.3.1 // indirect
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
- golang.org/x/net v0.0.0-20210929193557-e81a3d93ecf6 // indirect
- golang.org/x/sys v0.0.0-20211205182925-97ca703d548d // indirect
- gopkg.in/warnings.v0 v0.1.2 // indirect
+ golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac // indirect
)
diff --git a/go.sum b/go.sum
index a4c273a..4cd3ce1 100644
--- a/go.sum
+++ b/go.sum
@@ -1,853 +1,49 @@
-cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
-cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
-cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
-cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
-cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
-cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
-cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
-cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
-cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
-cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
-cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
-cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
-cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
-cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
-cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
-cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
-cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
-cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
-cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
-cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
-cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
-cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
-cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
-cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
-cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
-cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM=
-cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
-cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
-cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
-cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
-cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
-cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
-cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
-cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
-cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
-cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY=
-cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
-cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
-cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
-cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
-cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
-cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
-cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
-cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
-cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
-dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
-github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
-github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
-github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
-github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
-github.com/Microsoft/go-winio v0.5.0 h1:Elr9Wn+sGKPlkaBvwu4mTrxtmOp3F3yV9qhaHbXGjwU=
-github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
-github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
-github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
-github.com/ProtonMail/go-crypto v0.0.0-20210920160938-87db9fbc61c7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
-github.com/ProtonMail/go-crypto v0.0.0-20211112122917-428f8eabeeb3 h1:XcF0cTDJeiuZ5NU8w7WUDge0HRwwNRmxj/GGk6KSA6g=
-github.com/ProtonMail/go-crypto v0.0.0-20211112122917-428f8eabeeb3/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
-github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk=
-github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
-github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
-github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
-github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
-github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
-github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
-github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
-github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
-github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
-github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
-github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
-github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
-github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
-github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
-github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
-github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
-github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
-github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
-github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
-github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
-github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
-github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
-github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
-github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
-github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
-github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
-github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
-github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
-github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
-github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
-github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
-github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
-github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
-github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
-github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
-github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
-github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
-github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
-github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
-github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
-github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.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/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
-github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
-github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
-github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
-github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
-github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
-github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
-github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
-github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
-github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
-github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ=
-github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
-github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws=
-github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
-github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
-github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
-github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
-github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
-github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
-github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
-github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
-github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
-github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
-github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
-github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34=
-github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
-github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2SubfXjIWgci8=
-github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0=
-github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4=
-github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc=
-github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
-github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
-github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
-github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
-github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
-github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
-github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
-github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
-github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
-github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
-github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
-github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
-github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
-github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
-github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
-github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
-github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
-github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
-github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
-github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
-github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
-github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
-github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
-github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
-github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
-github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
-github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
-github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
-github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
-github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
-github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
-github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
-github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
-github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
-github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
-github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
-github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
-github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
-github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
-github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
-github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
-github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
-github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
-github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
-github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
-github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
-github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
-github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
-github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
-github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
-github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
-github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
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/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
-github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
-github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
-github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
-github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
-github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
-github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
-github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
-github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
-github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
-github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
-github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
-github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
-github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
-github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
-github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
-github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
-github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
-github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
-github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
-github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
-github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
-github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
-github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
-github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
-github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
-github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
-github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
-github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
-github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY=
-github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
-github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
-github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
-github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
-github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
-github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
-github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
-github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
-github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
-github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
-github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
-github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
-github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
-github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
-github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
-github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
-github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
-github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
-github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
-github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
-github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
-github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
-github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
-github.com/kevinburke/ssh_config v1.1.0 h1:pH/t1WS9NzT8go394IqZeJTMHVm6Cr6ZJ6AQ+mdNo/o=
-github.com/kevinburke/ssh_config v1.1.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
-github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
-github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
-github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+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/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
-github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
-github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
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/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
-github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
-github.com/magefile/mage v1.11.0 h1:C/55Ywp9BpgVVclD3lRnSYCwXTYxmSppIgLeDYlNuls=
-github.com/magefile/mage v1.11.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
-github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
-github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
-github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
-github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
-github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
-github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
-github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
-github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
-github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
-github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
-github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
-github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
-github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
-github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
-github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
-github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
-github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
-github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
-github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
-github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
-github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
-github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
-github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
-github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
-github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
-github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
-github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
-github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
-github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
-github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
-github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
-github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
-github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
-github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
-github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
-github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
-github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
-github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
-github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
-github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
-github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
-github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
-github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
-github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
-github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
-github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
-github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
-github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
-github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
-github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
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.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
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/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
-github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
-github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
-github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
-github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
-github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
-github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
-github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
-github.com/spf13/cobra v1.3.0 h1:R7cSvGu+Vv+qX0gW5R/85dx2kmmJT5z5NM8ifdYjdn0=
-github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4=
-github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
+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/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
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=
-github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
-github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
-github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
-github.com/sylabs/release-tools v0.1.0 h1:KFsL5RKctq0s/hloECDhVTXn38zLqv+4TgUeu55kmIQ=
-github.com/sylabs/release-tools v0.1.0/go.mod h1:pqP/z/11/rYMQ0OM/Nn7TxGijw7KfZwW9UolD/J1TUo=
-github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
-github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
-github.com/xanzy/ssh-agent v0.3.1 h1:AmzO1SSWxw73zxFZPRwaMN1MohDw8UyHnmuxyceTEGo=
-github.com/xanzy/ssh-agent v0.3.1/go.mod h1:QIE4lCeL7nkC25x+yA3LBIYfwCc1TFziCtG7cBAac6w=
-github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
-go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
-go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
-go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs=
-go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
-go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
-go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
-go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
-go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
-go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
-go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
-go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
-golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
-golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
-golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
-golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
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/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
-golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
-golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
-golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
-golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
-golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
-golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
-golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
-golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
-golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
-golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
-golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
-golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
-golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
-golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
-golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
-golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
-golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
-golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
-golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
-golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
-golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
-golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
-golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
-golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
-golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
-golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20210929193557-e81a3d93ecf6 h1:Z04ewVs7JhXaYkmDhBERPi41gnltfQpMWDnTnQbaCqk=
-golang.org/x/net v0.0.0-20210929193557-e81a3d93ecf6/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
-golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211001092434-39dca1131b70/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/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211205182925-97ca703d548d h1:FjkYO/PPp4Wi0EAUOVLxePm7qVW4r4ctbWpURyuOD0E=
-golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
-golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
-golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
-golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
-golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
-golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
-golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
-golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
-golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
-golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
-golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
-golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
-golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
-golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
-golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
-golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
-google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
-google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
-google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
-google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
-google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
-google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
-google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
-google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
-google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
-google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
-google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
-google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
-google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
-google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
-google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
-google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
-google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
-google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
-google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
-google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
-google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU=
-google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
-google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw=
-google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
-google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
-google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
-google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
-google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
-google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
-google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
-google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
-google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
-google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
-google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
-google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
-google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
-google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
-google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
-google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
-google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
-google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
-google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
-google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
-google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
-google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
-google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
-google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
-google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
-google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
-google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
-google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
-google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
-google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
-google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
-google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
-google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
-google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
-google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
-google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
-google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
-google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
-google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
-google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
-google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
-google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
-google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
-google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
-google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
-google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
-google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
-google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
-google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
-google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
-google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
-google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
-google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
-google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
-google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
-google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
-google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
-google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
-google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
-google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
-google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
-google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
-google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
-google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
-google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
-google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
-gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
-gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
-gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
-gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
-gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.3/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.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
-gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
-honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
-rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
-rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/internal/app/siftool/app.go b/internal/app/siftool/app.go
index 1a7014c..8cc6377 100644
--- a/internal/app/siftool/app.go
+++ b/internal/app/siftool/app.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021, Sylabs Inc. All rights reserved.
+// 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.
@@ -13,6 +13,7 @@ import (
// appOpts contains configured options.
type appOpts struct {
out io.Writer
+ err io.Writer
}
// AppOpt are used to configure optional behavior.
@@ -31,11 +32,23 @@ func OptAppOutput(w io.Writer) AppOpt {
}
}
+// 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,
},
}
diff --git a/internal/app/siftool/file.go b/internal/app/siftool/file.go
index 1722a1f..4e58e76 100644
--- a/internal/app/siftool/file.go
+++ b/internal/app/siftool/file.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021, Sylabs Inc. All rights reserved.
+// 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.
@@ -12,7 +12,7 @@ import (
)
// withFileImage calls fn with a FileImage loaded from path.
-func withFileImage(path string, writable bool, fn func(*sif.FileImage) error) (err error) {
+func withFileImage(path string, writable bool, fn func(*sif.FileImage) error) error {
flag := os.O_RDONLY
if writable {
flag = os.O_RDWR
@@ -22,11 +22,12 @@ func withFileImage(path string, writable bool, fn func(*sif.FileImage) error) (e
if err != nil {
return err
}
- defer func() {
- if uerr := f.UnloadContainer(); uerr != nil && err == nil {
- err = uerr
- }
- }()
- return fn(f)
+ err = fn(f)
+
+ if uerr := f.UnloadContainer(); err == nil {
+ err = uerr
+ }
+
+ return err
}
diff --git a/internal/app/siftool/info.go b/internal/app/siftool/info.go
index f1f6f32..44a0933 100644
--- a/internal/app/siftool/info.go
+++ b/internal/app/siftool/info.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2018-2021, Sylabs Inc. All rights reserved.
+// Copyright (c) 2018-2022, Sylabs Inc. All rights reserved.
// Copyright (c) 2018, Divya Cote <divya.cote@gmail.com> All rights reserved.
// Copyright (c) 2017, SingularityWare, LLC. All rights reserved.
// Copyright (c) 2017, Yannick Cote <yhcote@gmail.com> All rights reserved.
@@ -111,16 +111,25 @@ func writeList(w io.Writer, f *sif.FileImage) error {
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)
}
@@ -195,7 +204,10 @@ func writeInfo(w io.Writer, v sif.Descriptor) error {
}
fmt.Fprintf(tw, "\tHash Type:\t%v\n", ht)
- fmt.Fprintf(tw, "\tEntity:\t%X\n", fp)
+
+ if len(fp) > 0 {
+ fmt.Fprintf(tw, "\tEntity:\t%X\n", fp)
+ }
case sif.DataCryptoMessage:
ft, mt, err := v.CryptoMessageMetadata()
@@ -205,6 +217,14 @@ func writeInfo(w io.Writer, v sif.Descriptor) error {
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()
diff --git a/internal/app/siftool/info_test.go b/internal/app/siftool/info_test.go
index e3ce091..d6deab3 100644
--- a/internal/app/siftool/info_test.go
+++ b/internal/app/siftool/info_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021, Sylabs Inc. All rights reserved.
+// 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.
@@ -113,6 +113,10 @@ func TestApp_Header(t *testing.T) {
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"),
},
@@ -211,6 +215,10 @@ func TestApp_List(t *testing.T) {
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"),
},
@@ -300,6 +308,11 @@ func TestApp_Info(t *testing.T) {
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,
diff --git a/internal/app/siftool/modif_test.go b/internal/app/siftool/modif_test.go
index 98a3351..c4a73b3 100644
--- a/internal/app/siftool/modif_test.go
+++ b/internal/app/siftool/modif_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021, Sylabs Inc. All rights reserved.
+// 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.
@@ -73,6 +73,14 @@ func TestApp_Add(t *testing.T) {
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) {
diff --git a/internal/app/siftool/mount.go b/internal/app/siftool/mount.go
new file mode 100644
index 0000000..5da071b
--- /dev/null
+++ b/internal/app/siftool/mount.go
@@ -0,0 +1,20 @@
+// 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),
+ )
+}
diff --git a/internal/app/siftool/testdata/TestApp_Dump/Three.golden b/internal/app/siftool/testdata/TestApp_Dump/Three.golden
index 0299536..4e354d9 100644
--- a/internal/app/siftool/testdata/TestApp_Dump/Three.golden
+++ b/internal/app/siftool/testdata/TestApp_Dump/Three.golden
@@ -1,15 +1,15 @@
-----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:db74cb63348414def73535c9f0f83e8ad7df61229ed2806f4da8b69d6d7464d6","objectDigest":"sha256:5f78c33274e43fa9de5659265c1d917e25c03722dcb0b8d27db8d5feaa813953"}]}
+{"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/e6hBahBBIEXIwLEATQWN5L7aIMJ+5/97qE
-AABQ4QgAkWcLNLcghZ96VnJ9+67qbsdwp51rfERCKN0dZLBTHKN5Qjn1BWM/XbPj
-Qnl0F6D6YBId7c/KO0sbb3EHUdpmMEQlouQYFOTHWtdyvwO6spRLBx5EQA7Iv0rF
-jREz/jC7GaREK94u+hXRr94+FH5gEnHUL+Vg7pW/+cGiwLY1ddoL8ELgYhxqxd9J
-sET+vU1E4GJ3TyYFhVFsMsNeW7dQauqjQSJxMLTwXNphxTH19ePbJ2uDE2UJ3fn7
-up5ruugRyEe5qgRICGxRSDp8/INGRvoDUi32T9uLORzS+umRX5YW0b6RWD+5R72V
-0ewbMTJIx2lpfQGPMWROwcF7nkLdWQ==
-=WWGX
+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----- \ No newline at end of file
diff --git a/internal/app/siftool/testdata/TestApp_Dump/Two.golden b/internal/app/siftool/testdata/TestApp_Dump/Two.golden
index 7d174b1..cf6539a 100644
--- a/internal/app/siftool/testdata/TestApp_Dump/Two.golden
+++ b/internal/app/siftool/testdata/TestApp_Dump/Two.golden
Binary files differ
diff --git a/internal/app/siftool/testdata/TestApp_Header/OneGroup.golden b/internal/app/siftool/testdata/TestApp_Header/OneGroup.golden
index bc631d6..a2941c1 100644
--- a/internal/app/siftool/testdata/TestApp_Header/OneGroup.golden
+++ b/internal/app/siftool/testdata/TestApp_Header/OneGroup.golden
@@ -5,4 +5,4 @@ Descriptors Total: 48
Descriptors Offset: 4096
Descriptors Size: 27 KiB
Data Offset: 32176
-Data Size: 5 KiB
+Data Size: 9 KiB
diff --git a/internal/app/siftool/testdata/TestApp_Header/OneObjectCryptMessage.golden b/internal/app/siftool/testdata/TestApp_Header/OneObjectCryptMessage.golden
index 6f75a7f..4843632 100644
--- a/internal/app/siftool/testdata/TestApp_Header/OneObjectCryptMessage.golden
+++ b/internal/app/siftool/testdata/TestApp_Header/OneObjectCryptMessage.golden
@@ -4,4 +4,4 @@ Descriptors Total: 48
Descriptors Offset: 4096
Descriptors Size: 27 KiB
Data Offset: 32176
-Data Size: 596 B
+Data Size: 4 B
diff --git a/internal/app/siftool/testdata/TestApp_Header/OneObjectGenericJSON.golden b/internal/app/siftool/testdata/TestApp_Header/OneObjectGenericJSON.golden
index 55a2fbc..7daf5ad 100644
--- a/internal/app/siftool/testdata/TestApp_Header/OneObjectGenericJSON.golden
+++ b/internal/app/siftool/testdata/TestApp_Header/OneObjectGenericJSON.golden
@@ -4,4 +4,4 @@ Descriptors Total: 48
Descriptors Offset: 4096
Descriptors Size: 27 KiB
Data Offset: 32176
-Data Size: 594 B
+Data Size: 2 B
diff --git a/internal/app/siftool/testdata/TestApp_Header/OneObjectSBOM.golden b/internal/app/siftool/testdata/TestApp_Header/OneObjectSBOM.golden
new file mode 100644
index 0000000..6d2c254
--- /dev/null
+++ b/internal/app/siftool/testdata/TestApp_Header/OneObjectSBOM.golden
@@ -0,0 +1,7 @@
+Version: 01
+Descriptors Free: 47
+Descriptors Total: 48
+Descriptors Offset: 4096
+Descriptors Size: 27 KiB
+Data Offset: 32176
+Data Size: 454 B
diff --git a/internal/app/siftool/testdata/TestApp_Header/OneObjectTime.golden b/internal/app/siftool/testdata/TestApp_Header/OneObjectTime.golden
index 4d57200..ead1712 100644
--- a/internal/app/siftool/testdata/TestApp_Header/OneObjectTime.golden
+++ b/internal/app/siftool/testdata/TestApp_Header/OneObjectTime.golden
@@ -6,4 +6,4 @@ Descriptors Total: 48
Descriptors Offset: 4096
Descriptors Size: 27 KiB
Data Offset: 32176
-Data Size: 594 B
+Data Size: 2 B
diff --git a/internal/app/siftool/testdata/TestApp_Header/TwoGroups.golden b/internal/app/siftool/testdata/TestApp_Header/TwoGroups.golden
index 0db18f0..c16ef18 100644
--- a/internal/app/siftool/testdata/TestApp_Header/TwoGroups.golden
+++ b/internal/app/siftool/testdata/TestApp_Header/TwoGroups.golden
@@ -5,4 +5,4 @@ Descriptors Total: 48
Descriptors Offset: 4096
Descriptors Size: 27 KiB
Data Offset: 32176
-Data Size: 9 KiB
+Data Size: 265 KiB
diff --git a/internal/app/siftool/testdata/TestApp_Header/TwoGroupsSigned.golden b/internal/app/siftool/testdata/TestApp_Header/TwoGroupsSigned.golden
index acf8b17..44854c9 100644
--- a/internal/app/siftool/testdata/TestApp_Header/TwoGroupsSigned.golden
+++ b/internal/app/siftool/testdata/TestApp_Header/TwoGroupsSigned.golden
@@ -5,4 +5,4 @@ Descriptors Total: 48
Descriptors Offset: 4096
Descriptors Size: 27 KiB
Data Offset: 32176
-Data Size: 17 KiB
+Data Size: 266 KiB
diff --git a/internal/app/siftool/testdata/TestApp_Info/CryptMessage.golden b/internal/app/siftool/testdata/TestApp_Info/CryptMessage.golden
index 8a97f27..5394810 100644
--- a/internal/app/siftool/testdata/TestApp_Info/CryptMessage.golden
+++ b/internal/app/siftool/testdata/TestApp_Info/CryptMessage.golden
@@ -2,7 +2,7 @@
ID: 1
Group ID: 1
Linked ID: NONE
- Offset: 32768
+ Offset: 32176
Size: 4
Format Type: OpenPGP
Message Type: Clear Signature
diff --git a/internal/app/siftool/testdata/TestApp_Info/DataPartitionEXT3.golden b/internal/app/siftool/testdata/TestApp_Info/DataPartitionEXT3.golden
index 34e9dde..4fec1ec 100644
--- a/internal/app/siftool/testdata/TestApp_Info/DataPartitionEXT3.golden
+++ b/internal/app/siftool/testdata/TestApp_Info/DataPartitionEXT3.golden
@@ -3,7 +3,7 @@
Group ID: 2
Linked ID: NONE
Offset: 40960
- Size: 4
+ Size: 262144
Filesystem Type: Ext3
Partition Type: System
Architecture: amd64
diff --git a/internal/app/siftool/testdata/TestApp_Info/DataPartitionSquashFS.golden b/internal/app/siftool/testdata/TestApp_Info/DataPartitionSquashFS.golden
index f34651f..f49a554 100644
--- a/internal/app/siftool/testdata/TestApp_Info/DataPartitionSquashFS.golden
+++ b/internal/app/siftool/testdata/TestApp_Info/DataPartitionSquashFS.golden
@@ -3,7 +3,7 @@
Group ID: 1
Linked ID: NONE
Offset: 36864
- Size: 4
+ Size: 4096
Filesystem Type: Squashfs
Partition Type: *System
Architecture: 386
diff --git a/internal/app/siftool/testdata/TestApp_Info/DataSignature.golden b/internal/app/siftool/testdata/TestApp_Info/DataSignature.golden
index 411f3de..cb2859f 100644
--- a/internal/app/siftool/testdata/TestApp_Info/DataSignature.golden
+++ b/internal/app/siftool/testdata/TestApp_Info/DataSignature.golden
@@ -2,7 +2,7 @@
ID: 4
Group ID: NONE
Linked ID: 1 (G)
- Offset: 45056
+ Offset: 303104
Size: 1054
Hash Type: SHA-256
Entity: 12045C8C0B1004D058DE4BEDA20C27EE7FF7BA84
diff --git a/internal/app/siftool/testdata/TestApp_Info/GenericJSON.golden b/internal/app/siftool/testdata/TestApp_Info/GenericJSON.golden
index f73a6d0..ec29c50 100644
--- a/internal/app/siftool/testdata/TestApp_Info/GenericJSON.golden
+++ b/internal/app/siftool/testdata/TestApp_Info/GenericJSON.golden
@@ -2,6 +2,6 @@
ID: 1
Group ID: 1
Linked ID: NONE
- Offset: 32768
+ Offset: 32176
Size: 2
Name: data.json
diff --git a/internal/app/siftool/testdata/TestApp_Info/SBOM.golden b/internal/app/siftool/testdata/TestApp_Info/SBOM.golden
new file mode 100644
index 0000000..e77d60f
--- /dev/null
+++ b/internal/app/siftool/testdata/TestApp_Info/SBOM.golden
@@ -0,0 +1,7 @@
+ Data Type: SBOM
+ ID: 1
+ Group ID: 1
+ Linked ID: NONE
+ Offset: 32176
+ Size: 454
+ Format: cyclonedx-json
diff --git a/internal/app/siftool/testdata/TestApp_Info/Time.golden b/internal/app/siftool/testdata/TestApp_Info/Time.golden
index f5d6146..0352e46 100644
--- a/internal/app/siftool/testdata/TestApp_Info/Time.golden
+++ b/internal/app/siftool/testdata/TestApp_Info/Time.golden
@@ -2,7 +2,7 @@
ID: 1
Group ID: 1
Linked ID: NONE
- Offset: 32768
+ Offset: 32176
Size: 2
Created At: 2020-06-30 00:01:56 +0000 UTC
Modified At: 2020-06-30 00:01:56 +0000 UTC
diff --git a/internal/app/siftool/testdata/TestApp_List/OneGroup.golden b/internal/app/siftool/testdata/TestApp_List/OneGroup.golden
index fe78f62..01400f9 100644
--- a/internal/app/siftool/testdata/TestApp_List/OneGroup.golden
+++ b/internal/app/siftool/testdata/TestApp_List/OneGroup.golden
@@ -2,4 +2,4 @@
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)
+2 |1 |NONE |36864-40960 |FS (Squashfs/*System/386)
diff --git a/internal/app/siftool/testdata/TestApp_List/OneGroupSigned.golden b/internal/app/siftool/testdata/TestApp_List/OneGroupSigned.golden
index 98030e3..5b663d3 100644
--- a/internal/app/siftool/testdata/TestApp_List/OneGroupSigned.golden
+++ b/internal/app/siftool/testdata/TestApp_List/OneGroupSigned.golden
@@ -2,5 +2,5 @@
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)
+2 |1 |NONE |36864-40960 |FS (Squashfs/*System/386)
3 |NONE |1 (G) |40960-42014 |Signature (SHA-256)
diff --git a/internal/app/siftool/testdata/TestApp_List/OneObjectCryptMessage.golden b/internal/app/siftool/testdata/TestApp_List/OneObjectCryptMessage.golden
index c2ae3bc..b0b0b3c 100644
--- a/internal/app/siftool/testdata/TestApp_List/OneObjectCryptMessage.golden
+++ b/internal/app/siftool/testdata/TestApp_List/OneObjectCryptMessage.golden
@@ -1,4 +1,4 @@
------------------------------------------------------------------------------
ID |GROUP |LINK |SIF POSITION (start-end) |TYPE
------------------------------------------------------------------------------
-1 |1 |NONE |32768-32772 |Cryptographic Message (OpenPGP/Clear Signature)
+1 |1 |NONE |32176-32180 |Cryptographic Message (OpenPGP/Clear Signature)
diff --git a/internal/app/siftool/testdata/TestApp_List/OneObjectGenericJSON.golden b/internal/app/siftool/testdata/TestApp_List/OneObjectGenericJSON.golden
index f76be9c..3027d1d 100644
--- a/internal/app/siftool/testdata/TestApp_List/OneObjectGenericJSON.golden
+++ b/internal/app/siftool/testdata/TestApp_List/OneObjectGenericJSON.golden
@@ -1,4 +1,4 @@
------------------------------------------------------------------------------
ID |GROUP |LINK |SIF POSITION (start-end) |TYPE
------------------------------------------------------------------------------
-1 |1 |NONE |32768-32770 |JSON.Generic
+1 |1 |NONE |32176-32178 |JSON.Generic
diff --git a/internal/app/siftool/testdata/TestApp_List/OneObjectSBOM.golden b/internal/app/siftool/testdata/TestApp_List/OneObjectSBOM.golden
new file mode 100644
index 0000000..00cddaa
--- /dev/null
+++ b/internal/app/siftool/testdata/TestApp_List/OneObjectSBOM.golden
@@ -0,0 +1,4 @@
+------------------------------------------------------------------------------
+ID |GROUP |LINK |SIF POSITION (start-end) |TYPE
+------------------------------------------------------------------------------
+1 |1 |NONE |32176-32630 |SBOM (cyclonedx-json)
diff --git a/internal/app/siftool/testdata/TestApp_List/OneObjectTime.golden b/internal/app/siftool/testdata/TestApp_List/OneObjectTime.golden
index f76be9c..3027d1d 100644
--- a/internal/app/siftool/testdata/TestApp_List/OneObjectTime.golden
+++ b/internal/app/siftool/testdata/TestApp_List/OneObjectTime.golden
@@ -1,4 +1,4 @@
------------------------------------------------------------------------------
ID |GROUP |LINK |SIF POSITION (start-end) |TYPE
------------------------------------------------------------------------------
-1 |1 |NONE |32768-32770 |JSON.Generic
+1 |1 |NONE |32176-32178 |JSON.Generic
diff --git a/internal/app/siftool/testdata/TestApp_List/TwoGroups.golden b/internal/app/siftool/testdata/TestApp_List/TwoGroups.golden
index 648c280..1eca2ab 100644
--- a/internal/app/siftool/testdata/TestApp_List/TwoGroups.golden
+++ b/internal/app/siftool/testdata/TestApp_List/TwoGroups.golden
@@ -2,5 +2,5 @@
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)
+2 |1 |NONE |36864-40960 |FS (Squashfs/*System/386)
+3 |2 |NONE |40960-303104 |FS (Ext3/System/amd64)
diff --git a/internal/app/siftool/testdata/TestApp_List/TwoGroupsSigned.golden b/internal/app/siftool/testdata/TestApp_List/TwoGroupsSigned.golden
index f21bf6d..17240ba 100644
--- a/internal/app/siftool/testdata/TestApp_List/TwoGroupsSigned.golden
+++ b/internal/app/siftool/testdata/TestApp_List/TwoGroupsSigned.golden
@@ -2,7 +2,7 @@
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-46110 |Signature (SHA-256)
-5 |NONE |2 (G) |49152-50007 |Signature (SHA-256)
+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)
diff --git a/internal/app/siftool/unmount.go b/internal/app/siftool/unmount.go
new file mode 100644
index 0000000..52d8492
--- /dev/null
+++ b/internal/app/siftool/unmount.go
@@ -0,0 +1,20 @@
+// 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),
+ )
+}
diff --git a/mage.go b/mage.go
deleted file mode 100644
index 93b18ed..0000000
--- a/mage.go
+++ /dev/null
@@ -1,17 +0,0 @@
-// 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.
-
-//go:build ignore
-// +build ignore
-
-package main
-
-import (
- "os"
-
- "github.com/magefile/mage/mage"
-)
-
-func main() { os.Exit(mage.Main()) }
diff --git a/magefile.go b/magefile.go
deleted file mode 100644
index 4bf62c7..0000000
--- a/magefile.go
+++ /dev/null
@@ -1,112 +0,0 @@
-// 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.
-
-//go:build mage
-// +build mage
-
-package main
-
-import (
- "github.com/magefile/mage/mg"
- "github.com/magefile/mage/sh"
- "github.com/sylabs/release-tools/pkg/cmd"
- "github.com/sylabs/release-tools/pkg/git"
-)
-
-// Aliases defines command-line aliases exposed by Mage.
-//nolint:deadcode
-var Aliases = map[string]interface{}{
- "build": Build.All,
- "cover": Cover.All,
- "install": Install.All,
- "test": Test.All,
-}
-
-type Build mg.Namespace
-
-// All compiles all assets.
-func (ns Build) All() {
- mg.Deps(ns.Source)
-}
-
-// Source compiles all source code.
-func (Build) Source() error {
- d, err := git.Describe(".")
- if err != nil {
- return err
- }
-
- c, err := cmd.NewBuildCommand(
- cmd.OptBuildWithBuiltBy("mage"),
- cmd.OptBuildWithGitDescription(d),
- )
- if err != nil {
- return err
- }
-
- return sh.RunWith(c.Env(), mg.GoCmd(), c.Args()...)
-}
-
-type Install mg.Namespace
-
-// All installs all assets.
-func (ns Install) All() {
- mg.Deps(ns.Bin)
-}
-
-// Bin installs binary to GOBIN.
-func (Install) Bin() error {
- d, err := git.Describe(".")
- if err != nil {
- return err
- }
-
- c, err := cmd.NewInstallCommand(
- cmd.OptBuildPackages("./cmd/siftool"),
- cmd.OptBuildWithBuiltBy("mage"),
- cmd.OptBuildWithGitDescription(d),
- )
- if err != nil {
- return err
- }
-
- return sh.RunWith(c.Env(), mg.GoCmd(), c.Args()...)
-}
-
-type Test mg.Namespace
-
-// All runs all tests.
-func (ns Test) All() {
- mg.Deps(ns.Unit)
-}
-
-// Unit runs all unit tests.
-func (Test) Unit() error {
- c, err := cmd.NewTestCommand()
- if err != nil {
- return err
- }
-
- return sh.RunWithV(c.Env(), mg.GoCmd(), c.Args()...)
-}
-
-type Cover mg.Namespace
-
-// All runs all tests, writing coverage profile to the specified path.
-func (ns Cover) All(path string) {
- mg.Deps(mg.F(ns.Unit, path))
-}
-
-// Unit runs all unit tests, writing coverage profile to the specified path.
-func (Cover) Unit(path string) error {
- c, err := cmd.NewTestCommand(
- cmd.OptTestWithCoverPath(path),
- )
- if err != nil {
- return err
- }
-
- return sh.RunWithV(c.Env(), mg.GoCmd(), c.Args()...)
-}
diff --git a/pkg/integrity/clearsign.go b/pkg/integrity/clearsign.go
index e2985cb..2a99d98 100644
--- a/pkg/integrity/clearsign.go
+++ b/pkg/integrity/clearsign.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2020, Sylabs Inc. All rights reserved.
+// 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.
@@ -7,9 +7,10 @@ package integrity
import (
"bytes"
- "encoding/json"
+ "crypto"
"errors"
"io"
+ "time"
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/ProtonMail/go-crypto/openpgp/clearsign"
@@ -18,61 +19,81 @@ import (
var errClearsignedMsgNotFound = errors.New("clearsigned message not found")
-// signAndEncodeJSON encodes v, clear-signs it with privateKey, and writes it to w. If config is
-// nil, sensible defaults are used.
-func signAndEncodeJSON(w io.Writer, v interface{}, privateKey *packet.PrivateKey, config *packet.Config) error {
- // Get clearsign encoder.
- plaintext, err := clearsign.Encode(w, privateKey, config)
+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 err
+ return 0, err
}
defer plaintext.Close()
- // Wrap clearsign encoder with JSON encoder.
- return json.NewEncoder(plaintext).Encode(v)
+ _, err = io.Copy(plaintext, r)
+ return en.config.Hash(), err
}
-// verifyAndDecodeJSON reads the first clearsigned message in data, verifies its signature, and
-// returns the signing entity any suffix of data which follows the message. The plaintext is
-// unmarshalled to v (if not nil).
-func verifyAndDecodeJSON(data []byte, v interface{}, kr openpgp.KeyRing) (*openpgp.Entity, []byte, error) {
- // Decode clearsign block and check signature.
- e, plaintext, rest, err := verifyAndDecode(data, kr)
- if err != nil {
- return e, rest, err
- }
+type clearsignDecoder struct {
+ kr openpgp.KeyRing
+}
- // Unmarshal plaintext, if requested.
- if v != nil {
- err = json.Unmarshal(plaintext, v)
+// 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,
}
- return e, rest, err
}
-// verifyAndDecode reads the first clearsigned message in data, verifies its signature, and returns
-// the signing entity, plaintext and suffix of data which follows the message.
-func verifyAndDecode(data []byte, kr openpgp.KeyRing) (*openpgp.Entity, []byte, []byte, error) {
- // Decode clearsign block.
- b, rest := clearsign.Decode(data)
- if b == nil {
- return nil, nil, rest, errClearsignedMsgNotFound
+// 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
}
- // Check signature.
- e, err := openpgp.CheckDetachedSignature(kr, bytes.NewReader(b.Bytes), b.ArmoredSignature.Body, nil)
- return e, b.Plaintext, rest, err
-}
-
-// isLegacySignature reads the first clearsigned message in data, and returns true if the plaintext
-// contains a legacy signature.
-func isLegacySignature(data []byte) (bool, error) {
// Decode clearsign block.
b, _ := clearsign.Decode(data)
if b == nil {
- return false, errClearsignedMsgNotFound
+ 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
}
- // 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")), nil
+ return b.Plaintext, err
}
diff --git a/pkg/integrity/clearsign_test.go b/pkg/integrity/clearsign_test.go
index 6620884..1131424 100644
--- a/pkg/integrity/clearsign_test.go
+++ b/pkg/integrity/clearsign_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2020-2021, Sylabs Inc. All rights reserved.
+// 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.
@@ -21,31 +21,31 @@ import (
"github.com/sebdah/goldie/v2"
)
-type testType struct {
- One int
- Two int
-}
+var testMessage = `{"One":1,"Two":2}
+`
-func TestSignAndEncodeJSON(t *testing.T) {
+func Test_clearsignEncoder_signMessage(t *testing.T) {
e := getTestEntity(t)
- // Fake an encrypted key.
- encryptedKey := *e.PrivateKey
- encryptedKey.Encrypted = true
+ encrypted := getTestEntity(t)
+ encrypted.PrivateKey.Encrypted = true
tests := []struct {
- name string
- key *packet.PrivateKey
- hash crypto.Hash
- wantErr bool
+ name string
+ en *clearsignEncoder
+ wantErr bool
+ wantHash crypto.Hash
}{
- {name: "EncryptedKey", key: &encryptedKey, wantErr: true},
- {name: "DefaultHash", key: e.PrivateKey},
- {name: "SHA1", key: e.PrivateKey, hash: crypto.SHA1},
- {name: "SHA224", key: e.PrivateKey, hash: crypto.SHA224},
- {name: "SHA256", key: e.PrivateKey, hash: crypto.SHA256},
- {name: "SHA384", key: e.PrivateKey, hash: crypto.SHA384},
- {name: "SHA512", key: e.PrivateKey, hash: crypto.SHA512},
+ {
+ name: "EncryptedKey",
+ en: newClearsignEncoder(encrypted, fixedTime),
+ wantErr: true,
+ },
+ {
+ name: "OK",
+ en: newClearsignEncoder(e, fixedTime),
+ wantHash: crypto.SHA256,
+ },
}
for _, tt := range tests {
@@ -53,17 +53,16 @@ func TestSignAndEncodeJSON(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
b := bytes.Buffer{}
- config := packet.Config{
- DefaultHash: tt.hash,
- Time: fixedTime,
- }
-
- err := signAndEncodeJSON(&b, testType{1, 2}, tt.key, &config)
+ 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())
}
@@ -71,11 +70,9 @@ func TestSignAndEncodeJSON(t *testing.T) {
}
}
-func TestVerifyAndDecodeJSON(t *testing.T) {
+func Test_clearsignDecoder_verifyMessage(t *testing.T) {
e := getTestEntity(t)
- testValue := testType{1, 2}
-
// 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)
@@ -110,22 +107,63 @@ func TestVerifyAndDecodeJSON(t *testing.T) {
tests := []struct {
name string
hash crypto.Hash
- el openpgp.EntityList
corrupter func(w io.Writer, s string) error
- output interface{}
+ de *clearsignDecoder
wantErr error
wantEntity *openpgp.Entity
}{
- {name: "ErrUnknownIssuer", el: openpgp.EntityList{}, wantErr: pgperrors.ErrUnknownIssuer},
- {name: "CorruptedClearsign", el: openpgp.EntityList{e}, corrupter: corruptClearsign},
- {name: "CorruptedSignature", el: openpgp.EntityList{e}, corrupter: corruptSignature},
- {name: "VerifyOnly", el: openpgp.EntityList{e}, wantEntity: e},
- {name: "DefaultHash", el: openpgp.EntityList{e}, output: &testType{}, wantEntity: e},
- {name: "SHA1", hash: crypto.SHA1, el: openpgp.EntityList{e}, output: &testType{}, wantEntity: e},
- {name: "SHA224", hash: crypto.SHA224, el: openpgp.EntityList{e}, output: &testType{}, wantEntity: e},
- {name: "SHA256", hash: crypto.SHA256, el: openpgp.EntityList{e}, output: &testType{}, wantEntity: e},
- {name: "SHA384", hash: crypto.SHA384, el: openpgp.EntityList{e}, output: &testType{}, wantEntity: e},
- {name: "SHA512", hash: crypto.SHA512, el: openpgp.EntityList{e}, output: &testType{}, wantEntity: e},
+ {
+ 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 {
@@ -133,10 +171,15 @@ func TestVerifyAndDecodeJSON(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
b := bytes.Buffer{}
- config := packet.Config{
- DefaultHash: tt.hash,
+ // Sign and encode message.
+ en := clearsignEncoder{
+ e: e,
+ config: &packet.Config{
+ DefaultHash: tt.hash,
+ Time: fixedTime,
+ },
}
- err := signAndEncodeJSON(&b, testValue, e.PrivateKey, &config)
+ h, err := en.signMessage(&b, strings.NewReader(testMessage))
if err != nil {
t.Fatal(err)
}
@@ -150,31 +193,20 @@ func TestVerifyAndDecodeJSON(t *testing.T) {
}
}
- // Verify and decode.
- e, rest, err := verifyAndDecodeJSON(b.Bytes(), tt.output, tt.el)
-
- // Shouldn't be any trailing bytes.
- if n := len(rest); n != 0 {
- t.Errorf("%v trailing bytes", n)
- }
+ // Decode and verify message.
+ var vr VerifyResult
+ message, err := tt.de.verifyMessage(bytes.NewReader(b.Bytes()), h, &vr)
- // Verify the error (if any) is appropriate.
- if tt.corrupter == nil {
- if got, want := err, tt.wantErr; !errors.Is(got, want) {
- t.Fatalf("got error %v, want %v", got, want)
- }
- } else if err == nil {
- t.Errorf("got nil error despite corruption")
+ if got, want := err, tt.wantErr; !errors.Is(got, want) {
+ t.Fatalf("got error %v, want %v", got, want)
}
if err == nil {
- if tt.output != nil {
- if got, want := tt.output, &testValue; !reflect.DeepEqual(got, want) {
- t.Errorf("got value %v, want %v", got, want)
- }
+ if got, want := string(message), testMessage; got != want {
+ t.Errorf("got message %v, want %v", got, want)
}
- if got, want := e, tt.wantEntity; !reflect.DeepEqual(got, want) {
+ if got, want := vr.e, tt.wantEntity; !reflect.DeepEqual(got, want) {
t.Errorf("got entity %+v, want %+v", got, want)
}
}
diff --git a/pkg/integrity/digest.go b/pkg/integrity/digest.go
index 5b1ff2c..0783ed6 100644
--- a/pkg/integrity/digest.go
+++ b/pkg/integrity/digest.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2020-2021, Sylabs Inc. All rights reserved.
+// 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.
@@ -22,12 +22,14 @@ var (
errDigestMalformed = errors.New("digest malformed")
)
-var supportedAlgorithms = map[crypto.Hash]string{
- crypto.SHA1: "sha1",
- crypto.SHA224: "sha224",
- crypto.SHA256: "sha256",
- crypto.SHA384: "sha384",
- crypto.SHA512: "sha512",
+// 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
@@ -52,7 +54,7 @@ type digest struct {
// 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 := supportedAlgorithms[h]; !ok {
+ if _, ok := supportedDigestAlgorithms[h]; !ok {
return digest{}, errHashUnsupported
}
@@ -78,8 +80,8 @@ func newDigestReader(h crypto.Hash, r io.Reader) (digest, error) {
// For reference, the plaintext of legacy signatures is comprised of the string "SIFHASH:\n",
// followed by a digest value. For example:
//
-// SIFHASH:
-// 2f0b3dca0ec42683d306338f68689aba29cdb83625b8cc0b8a789f8de92342495a6264b0c134e706630636bf90c6f331
+// SIFHASH:
+// 2f0b3dca0ec42683d306338f68689aba29cdb83625b8cc0b8a789f8de92342495a6264b0c134e706630636bf90c6f331
func newLegacyDigest(ht crypto.Hash, b []byte) (digest, error) {
b = bytes.TrimPrefix(b, []byte("SIFHASH:\n"))
b = bytes.TrimSuffix(b, []byte("\n"))
@@ -104,7 +106,7 @@ func (d digest) matches(r io.Reader) (bool, error) {
// MarshalJSON marshals d into string of format "alg:value".
func (d digest) MarshalJSON() ([]byte, error) {
- n, ok := supportedAlgorithms[d.hash]
+ n, ok := supportedDigestAlgorithms[d.hash]
if !ok {
return nil, errHashUnsupported
}
@@ -130,7 +132,7 @@ func (d *digest) UnmarshalJSON(data []byte) error {
return fmt.Errorf("%w: %v", errDigestMalformed, err)
}
- for h, n := range supportedAlgorithms {
+ for h, n := range supportedDigestAlgorithms {
if n == name {
digest, err := newDigest(h, v)
if err != nil {
diff --git a/pkg/integrity/digest_test.go b/pkg/integrity/digest_test.go
index f6d2f45..128e10c 100644
--- a/pkg/integrity/digest_test.go
+++ b/pkg/integrity/digest_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2020-2021, Sylabs Inc. All rights reserved.
+// 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.
@@ -74,7 +74,7 @@ func TestNewLegacyDigest(t *testing.T) {
{
name: "SHA512",
ht: crypto.SHA512,
- text: "SIFHASH:\nee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff\n", // nolint:lll
+ text: "SIFHASH:\nee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff\n", //nolint:lll
wantDigest: digest{
hash: crypto.SHA512,
value: []byte{
@@ -114,14 +114,14 @@ func TestDigest_MarshalJSON(t *testing.T) {
wantErr error
}{
{
- name: "UnsupportedHash",
+ name: "HashUnsupportedMD5",
hash: crypto.MD5,
wantErr: errHashUnsupported,
},
{
- name: "SHA1",
- hash: crypto.SHA1,
- value: "597f6a540010f94c15d71806a99a2c8710e747bd",
+ name: "HashUnsupportedSHA1",
+ hash: crypto.SHA1,
+ wantErr: errHashUnsupported,
},
{
name: "SHA224",
@@ -141,7 +141,17 @@ func TestDigest_MarshalJSON(t *testing.T) {
{
name: "SHA512",
hash: crypto.SHA512,
- value: "db3974a97f2407b7cae1ae637c0030687a11913274d578492558e39c16c017de84eacdc8c62fe34ee4e12b4b1428817f09b6a2760c3f8a664ceae94d2434a593", // nolint:lll
+ value: "db3974a97f2407b7cae1ae637c0030687a11913274d578492558e39c16c017de84eacdc8c62fe34ee4e12b4b1428817f09b6a2760c3f8a664ceae94d2434a593", //nolint:lll
+ },
+ {
+ name: "SHA512_224",
+ hash: crypto.SHA512_224,
+ value: "06001bf08dfb17d2b54925116823be230e98b5c6c278303bc4909a8c",
+ },
+ {
+ name: "SHA512_256",
+ hash: crypto.SHA512_256,
+ value: "3d37fe58435e0d87323dee4a2c1b339ef954de63716ee79f5747f94d974f913f",
},
}
@@ -193,49 +203,60 @@ func TestDigest_UnmarshalJSON(t *testing.T) {
wantErr: errDigestMalformed,
},
{
- name: "UnsupportedHash",
+ name: "HashUnsupportedMD5",
r: strings.NewReader(`"md5:b0804ec967f48520697662a204f5fe72"`),
wantErr: errHashUnsupported,
},
{
+ name: "HashUnsupportedSHA1",
+ r: strings.NewReader(`"sha1:597f6a540010f94c15d71806a99a2c8710e747bd"`),
+ wantErr: errHashUnsupported,
+ },
+ {
name: "DigestMalformedNotHex",
- r: strings.NewReader(`"sha1:oops"`),
+ r: strings.NewReader(`"sha256:oops"`),
wantErr: errDigestMalformed,
},
{
name: "DigestMalformedIncorrectLen",
- r: strings.NewReader(`"sha1:597f"`),
+ r: strings.NewReader(`"sha256:597f"`),
wantErr: errDigestMalformed,
},
{
- name: "SHA1",
- r: strings.NewReader(`"sha1:597f6a540010f94c15d71806a99a2c8710e747bd"`),
- wantHash: crypto.SHA1,
- wantValue: "597f6a540010f94c15d71806a99a2c8710e747bd",
- },
- {
name: "SHA224",
r: strings.NewReader(`"sha224:95041dd60ab08c0bf5636d50be85fe9790300f39eb84602858a9b430"`),
- wantHash: crypto.SHA1,
+ wantHash: crypto.SHA224,
wantValue: "95041dd60ab08c0bf5636d50be85fe9790300f39eb84602858a9b430",
},
{
name: "SHA256",
r: strings.NewReader(`"sha256:a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447"`),
- wantHash: crypto.SHA1,
+ wantHash: crypto.SHA256,
wantValue: "a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447",
},
{
name: "SHA384",
- r: strings.NewReader(`"sha384:6b3b69ff0a404f28d75e98a066d3fc64fffd9940870cc68bece28545b9a75086b343d7a1366838083e4b8f3ca6fd3c80"`), // nolint:lll
- wantHash: crypto.SHA1,
+ r: strings.NewReader(`"sha384:6b3b69ff0a404f28d75e98a066d3fc64fffd9940870cc68bece28545b9a75086b343d7a1366838083e4b8f3ca6fd3c80"`), //nolint:lll
+ wantHash: crypto.SHA384,
wantValue: "6b3b69ff0a404f28d75e98a066d3fc64fffd9940870cc68bece28545b9a75086b343d7a1366838083e4b8f3ca6fd3c80",
},
{
name: "SHA512",
- r: strings.NewReader(`"sha512:db3974a97f2407b7cae1ae637c0030687a11913274d578492558e39c16c017de84eacdc8c62fe34ee4e12b4b1428817f09b6a2760c3f8a664ceae94d2434a593"`), // nolint:lll
- wantHash: crypto.SHA1,
- wantValue: "db3974a97f2407b7cae1ae637c0030687a11913274d578492558e39c16c017de84eacdc8c62fe34ee4e12b4b1428817f09b6a2760c3f8a664ceae94d2434a593", // nolint:lll
+ 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",
},
}
@@ -243,10 +264,19 @@ func TestDigest_UnmarshalJSON(t *testing.T) {
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)
+ }
})
}
}
diff --git a/pkg/integrity/doc.go b/pkg/integrity/doc.go
index 19739fd..37850a5 100644
--- a/pkg/integrity/doc.go
+++ b/pkg/integrity/doc.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2020, Sylabs Inc. All rights reserved.
+// 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.
@@ -7,7 +7,7 @@
Package integrity implements functions to add, examine, and verify digital signatures in a SIF
image.
-Sign
+# Sign
To add one or more digital signatures to a SIF, create a Signer, and supply a signing PGP entity:
@@ -23,7 +23,7 @@ Finally, to apply the signature(s):
err := s.Sign()
-Verify
+# Verify
To examine and/or verify digital signatures in a SIF, create a Verifier:
diff --git a/pkg/integrity/metadata_test.go b/pkg/integrity/metadata_test.go
index bc85985..b78ec2f 100644
--- a/pkg/integrity/metadata_test.go
+++ b/pkg/integrity/metadata_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2020-2021, Sylabs Inc. All rights reserved.
+// 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.
@@ -35,12 +35,14 @@ func TestGetHeaderMetadata(t *testing.T) {
wantErr error
}{
{name: "HashUnavailable", header: bytes.NewReader(b), hash: crypto.MD4, wantErr: errHashUnavailable},
- {name: "HashUnsupported", header: bytes.NewReader(b), hash: crypto.MD5, wantErr: errHashUnsupported},
- {name: "SHA1", header: bytes.NewReader(b), hash: crypto.SHA1},
+ {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 {
@@ -88,13 +90,15 @@ func TestGetObjectMetadata(t *testing.T) {
wantErr error
}{
{name: "HashUnavailable", descr: bytes.NewReader(rid0), hash: crypto.MD4, wantErr: errHashUnavailable},
- {name: "HashUnsupported", descr: bytes.NewReader(rid0), hash: crypto.MD5, wantErr: errHashUnsupported},
- {name: "RelativeID", relativeID: 1, descr: bytes.NewReader(rid1), data: strings.NewReader("blah"), hash: crypto.SHA1},
- {name: "SHA1", descr: bytes.NewReader(rid0), data: strings.NewReader("blah"), hash: crypto.SHA1},
+ {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 {
@@ -139,11 +143,11 @@ func TestGetImageMetadata(t *testing.T) {
wantErr error
}{
{name: "HashUnavailable", hash: crypto.MD4, wantErr: errHashUnavailable},
- {name: "HashUnsupported", hash: crypto.MD5, wantErr: errHashUnsupported},
- {name: "MinimumIDInvalid", minID: 2, ods: []sif.Descriptor{od1}, hash: crypto.SHA1, wantErr: errMinimumIDInvalid},
- {name: "Object1", minID: 1, ods: []sif.Descriptor{od1}, hash: crypto.SHA1},
- {name: "Object2", minID: 1, ods: []sif.Descriptor{od2}, hash: crypto.SHA1},
- {name: "SHA1", minID: 1, ods: []sif.Descriptor{od1, od2}, hash: crypto.SHA1},
+ {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},
diff --git a/pkg/integrity/select.go b/pkg/integrity/select.go
index 3f2e794..5fde05b 100644
--- a/pkg/integrity/select.go
+++ b/pkg/integrity/select.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2020-2021, Sylabs Inc. All rights reserved.
+// 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.
@@ -11,6 +11,7 @@ import (
"fmt"
"sort"
+ "github.com/ProtonMail/go-crypto/openpgp/clearsign"
"github.com/sylabs/sif/v2/pkg/sif"
)
@@ -97,6 +98,19 @@ func getObjectSignatures(f *sif.FileImage, id uint32) ([]sif.Descriptor, error)
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
@@ -112,8 +126,7 @@ func getGroupSignatures(f *sif.FileImage, groupID uint32, legacy bool) ([]sif.De
return false, err
}
- isLegacy, err := isLegacySignature(b)
- return isLegacy == legacy, err
+ return isLegacySignature(b) == legacy, err
},
)
if err != nil {
@@ -152,7 +165,9 @@ func getGroupMinObjectID(f *sif.FileImage, groupID uint32) (uint32, error) {
// 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) (groupIDs []uint32, err error) {
+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)
@@ -161,10 +176,10 @@ func getGroupIDs(f *sif.FileImage) (groupIDs []uint32, err error) {
})
if len(groupIDs) == 0 {
- err = errNoGroupsFound
+ return nil, errNoGroupsFound
}
- return groupIDs, err
+ return groupIDs, nil
}
// getFingerprints returns a sorted list of unique fingerprints contained in sigs.
@@ -177,6 +192,10 @@ func getFingerprints(sigs []sif.Descriptor) ([][]byte, error) {
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
diff --git a/pkg/integrity/sign.go b/pkg/integrity/sign.go
index 9d3cb99..46a14e7 100644
--- a/pkg/integrity/sign.go
+++ b/pkg/integrity/sign.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2020-2021, Sylabs Inc. All rights reserved.
+// 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.
@@ -8,13 +8,14 @@ package integrity
import (
"bytes"
"crypto"
+ "encoding/json"
"errors"
"fmt"
+ "io"
"sort"
"time"
"github.com/ProtonMail/go-crypto/openpgp"
- "github.com/ProtonMail/go-crypto/openpgp/packet"
"github.com/sylabs/sif/v2/pkg/sif"
)
@@ -27,12 +28,19 @@ var (
// 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 {
- 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.
- sigConfig *packet.Config // Configuration for signature.
+ 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.
@@ -68,27 +76,30 @@ func optSignGroupMetadataHash(h crypto.Hash) groupSignerOpt {
}
}
-// optSignGroupSignatureConfig sets c as the configuration used for signature generation.
-func optSignGroupSignatureConfig(c *packet.Config) groupSignerOpt {
+// optSignGroupFingerprint sets fp as the fingerprint of the signing entity.
+func optSignGroupFingerprint(fp []byte) groupSignerOpt {
return func(gs *groupSigner) error {
- gs.sigConfig = c
+ gs.fp = fp
return nil
}
}
-// newGroupSigner returns a new groupSigner to add a digital signature for the specified group to
-// f, according to opts.
+// 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(). To override the default PGP configuration for signature generation,
-// use optSignGroupSignatureConfig().
-func newGroupSigner(f *sif.FileImage, groupID uint32, opts ...groupSignerOpt) (*groupSigner, error) {
+// 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,
@@ -136,8 +147,8 @@ func (gs *groupSigner) addObject(od sif.Descriptor) error {
return nil
}
-// signWithEntity signs the objects specified by gs with e.
-func (gs *groupSigner) signWithEntity(e *openpgp.Entity) (sif.DescriptorInput, error) {
+// 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 {
@@ -150,17 +161,24 @@ func (gs *groupSigner) signWithEntity(e *openpgp.Entity) (sif.DescriptorInput, e
return sif.DescriptorInput{}, fmt.Errorf("failed to get image metadata: %w", err)
}
- // Sign and encode image metadata.
+ // 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{}
- if err := signAndEncodeJSON(&b, md, e.PrivateKey, gs.sigConfig); err != nil {
- return sif.DescriptorInput{}, fmt.Errorf("failed to encode signature: %w", err)
+ 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(gs.sigConfig.Hash(), e.PrimaryKey.Fingerprint),
+ sif.OptSignatureMetadata(ht, gs.fp),
)
}
@@ -264,9 +282,10 @@ type Signer struct {
signers []*groupSigner
}
-// NewSigner returns a Signer to add digital signature(s) to f, according to opts.
+// 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.
//
-// Sign requires key material be provided. OptSignWithEntity can be used for this purpose.
+// 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.
@@ -294,15 +313,21 @@ func NewSigner(f *sif.FileImage, opts ...SignerOpt) (*Signer, error) {
opts: so,
}
- commonOpts := []groupSignerOpt{
- optSignGroupSignatureConfig(&packet.Config{
- Time: so.timeFunc,
- }),
+ 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(f, groupID, commonOpts...)
+ gs, err := newGroupSigner(en, f, groupID, commonOpts...)
if err != nil {
return nil, fmt.Errorf("integrity: %w", err)
}
@@ -315,7 +340,7 @@ func NewSigner(f *sif.FileImage, opts ...SignerOpt) (*Signer, error) {
opts := commonOpts
opts = append(opts, optSignGroupObjects(ids...))
- gs, err := newGroupSigner(f, groupID, opts...)
+ gs, err := newGroupSigner(en, f, groupID, opts...)
if err != nil {
return err
}
@@ -336,7 +361,7 @@ func NewSigner(f *sif.FileImage, opts ...SignerOpt) (*Signer, error) {
}
for _, id := range ids {
- gs, err := newGroupSigner(f, id, commonOpts...)
+ gs, err := newGroupSigner(en, f, id, commonOpts...)
if err != nil {
return nil, fmt.Errorf("integrity: %w", err)
}
@@ -348,16 +373,9 @@ func NewSigner(f *sif.FileImage, opts ...SignerOpt) (*Signer, error) {
}
// Sign adds digital signatures as specified by s.
-//
-// If key material was not provided when s was created, Sign returns an error wrapping
-// ErrNoKeyMaterial.
func (s *Signer) Sign() error {
- if s.opts.e == nil {
- return fmt.Errorf("integrity: %w", ErrNoKeyMaterial)
- }
-
for _, gs := range s.signers {
- di, err := gs.signWithEntity(s.opts.e)
+ di, err := gs.sign()
if err != nil {
return fmt.Errorf("integrity: %w", err)
}
diff --git a/pkg/integrity/sign_test.go b/pkg/integrity/sign_test.go
index 7eeb86e..a550c89 100644
--- a/pkg/integrity/sign_test.go
+++ b/pkg/integrity/sign_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2020-2021, Sylabs Inc. All rights reserved.
+// 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.
@@ -6,6 +6,7 @@
package integrity
import (
+ "bytes"
"crypto"
"errors"
"os"
@@ -14,7 +15,6 @@ import (
"testing"
"github.com/ProtonMail/go-crypto/openpgp"
- "github.com/ProtonMail/go-crypto/openpgp/packet"
"github.com/sebdah/goldie/v2"
"github.com/sylabs/sif/v2/pkg/sif"
)
@@ -102,6 +102,7 @@ func TestNewGroupSigner(t *testing.T) {
wantErr error
wantObjects []uint32
wantMDHash crypto.Hash
+ wantFP []byte
}{
{
name: "InvalidGroupID",
@@ -174,17 +175,40 @@ func TestNewGroupSigner(t *testing.T) {
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) {
- s, err := newGroupSigner(tt.fi, tt.groupID, tt.opts...)
+ 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)
}
@@ -204,12 +228,16 @@ func TestNewGroupSigner(t *testing.T) {
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_SignWithEntity(t *testing.T) {
+func TestGroupSigner_Sign(t *testing.T) {
twoGroups := loadContainer(t, filepath.Join(corpus, "two-groups.sif"))
d1, err := twoGroups.GetDescriptor(sif.WithID(1))
@@ -228,144 +256,92 @@ func TestGroupSigner_SignWithEntity(t *testing.T) {
}
e := getTestEntity(t)
+ clearsign := newClearsignEncoder(e, fixedTime)
encrypted := getTestEntity(t)
encrypted.PrivateKey.Encrypted = true
+ clearsignEncrypted := newClearsignEncoder(encrypted, fixedTime)
+
tests := []struct {
name string
gs groupSigner
- e *openpgp.Entity
wantErr bool
}{
{
name: "HashUnavailable",
gs: groupSigner{
+ en: clearsign,
f: twoGroups,
id: 1,
ods: []sif.Descriptor{d1},
mdHash: crypto.MD4,
- sigConfig: &packet.Config{
- Time: fixedTime,
- },
+ fp: e.PrimaryKey.Fingerprint,
},
- e: e,
wantErr: true,
},
{
name: "EncryptedKey",
gs: groupSigner{
+ en: clearsignEncrypted,
f: twoGroups,
id: 1,
ods: []sif.Descriptor{d1},
mdHash: crypto.SHA1,
- sigConfig: &packet.Config{
- Time: fixedTime,
- },
+ fp: encrypted.PrimaryKey.Fingerprint,
},
- e: encrypted,
wantErr: true,
},
{
name: "Object1",
gs: groupSigner{
+ en: clearsign,
f: twoGroups,
id: 1,
ods: []sif.Descriptor{d1},
- mdHash: crypto.SHA1,
- sigConfig: &packet.Config{
- Time: fixedTime,
- },
+ mdHash: crypto.SHA256,
+ fp: e.PrimaryKey.Fingerprint,
},
- e: e,
},
{
name: "Object2",
gs: groupSigner{
+ en: clearsign,
f: twoGroups,
id: 1,
ods: []sif.Descriptor{d2},
- mdHash: crypto.SHA1,
- sigConfig: &packet.Config{
- Time: fixedTime,
- },
+ mdHash: crypto.SHA256,
+ fp: e.PrimaryKey.Fingerprint,
},
- e: e,
},
{
name: "Group1",
gs: groupSigner{
+ en: clearsign,
f: twoGroups,
id: 1,
ods: []sif.Descriptor{d1, d2},
- mdHash: crypto.SHA1,
- sigConfig: &packet.Config{
- Time: fixedTime,
- },
+ mdHash: crypto.SHA256,
+ fp: e.PrimaryKey.Fingerprint,
},
- e: e,
},
{
name: "Group2",
gs: groupSigner{
+ en: clearsign,
f: twoGroups,
id: 2,
ods: []sif.Descriptor{d3},
- mdHash: crypto.SHA1,
- sigConfig: &packet.Config{
- Time: fixedTime,
- },
+ mdHash: crypto.SHA256,
+ fp: e.PrimaryKey.Fingerprint,
},
- e: e,
- },
- {
- name: "SignatureConfigSHA256",
- gs: groupSigner{
- f: twoGroups,
- id: 1,
- ods: []sif.Descriptor{d1, d2},
- mdHash: crypto.SHA1,
- sigConfig: &packet.Config{
- DefaultHash: crypto.SHA256,
- Time: fixedTime,
- },
- },
- e: e,
- },
- {
- name: "SignatureConfigSHA384",
- gs: groupSigner{
- f: twoGroups,
- id: 1,
- ods: []sif.Descriptor{d1, d2},
- mdHash: crypto.SHA1,
- sigConfig: &packet.Config{
- DefaultHash: crypto.SHA384,
- Time: fixedTime,
- },
- },
- e: e,
- },
- {
- name: "SignatureConfigSHA512",
- gs: groupSigner{
- f: twoGroups,
- id: 1,
- ods: []sif.Descriptor{d1, d2},
- mdHash: crypto.SHA1,
- sigConfig: &packet.Config{
- DefaultHash: crypto.SHA512,
- Time: fixedTime,
- },
- },
- e: e,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
- di, err := tt.gs.signWithEntity(tt.e)
+ di, err := tt.gs.sign()
if (err != nil) != tt.wantErr {
t.Fatalf("got error %v, want %v", err, tt.wantErr)
}
@@ -413,92 +389,125 @@ func TestNewSigner(t *testing.T) {
wantEntity *openpgp.Entity
}{
{
- name: "NilFileImage",
- fi: nil,
+ name: "NilFileImage",
+ fi: nil,
+ opts: []SignerOpt{
+ OptSignWithEntity(e),
+ },
wantErr: errNilFileImage,
},
{
- name: "NoGroupsFound",
- fi: emptyImage,
+ name: "NoGroupsFound",
+ fi: emptyImage,
+ opts: []SignerOpt{
+ OptSignWithEntity(e),
+ },
wantErr: errNoGroupsFound,
},
{
- name: "InvalidGroupID",
- fi: emptyImage,
- opts: []SignerOpt{OptSignGroup(0)},
+ name: "InvalidGroupID",
+ fi: emptyImage,
+ opts: []SignerOpt{
+ OptSignWithEntity(e),
+ OptSignGroup(0),
+ },
wantErr: sif.ErrInvalidGroupID,
},
{
- name: "NoObjectsSpecified",
- fi: emptyImage,
- opts: []SignerOpt{OptSignObjects()},
+ name: "NoObjectsSpecified",
+ fi: emptyImage,
+ opts: []SignerOpt{
+ OptSignWithEntity(e),
+ OptSignObjects(),
+ },
wantErr: errNoObjectsSpecified,
},
{
- name: "NoObjects",
- fi: emptyImage,
- opts: []SignerOpt{OptSignObjects(1)},
+ name: "NoObjects",
+ fi: emptyImage,
+ opts: []SignerOpt{
+ OptSignWithEntity(e),
+ OptSignObjects(1),
+ },
wantErr: sif.ErrNoObjects,
},
{
- name: "InvalidObjectID",
- fi: oneGroupImage,
- opts: []SignerOpt{OptSignObjects(0)},
+ name: "InvalidObjectID",
+ fi: oneGroupImage,
+ opts: []SignerOpt{
+ OptSignWithEntity(e),
+ OptSignObjects(0),
+ },
wantErr: sif.ErrInvalidObjectID,
},
{
- name: "OneGroupDefaultObjects",
- fi: oneGroupImage,
- opts: []SignerOpt{},
+ name: "OneGroupDefaultObjects",
+ fi: oneGroupImage,
+ opts: []SignerOpt{
+ OptSignWithEntity(e),
+ },
wantGroupObjects: map[uint32][]uint32{1: {1, 2}},
},
{
- name: "TwoGroupDefaultObjects",
- fi: twoGroupImage,
- opts: []SignerOpt{},
- wantGroupObjects: map[uint32][]uint32{1: {1, 2}, 2: {3}},
- },
- {
- name: "OptSignWithEntity",
- fi: twoGroupImage,
- opts: []SignerOpt{OptSignWithEntity(e)},
+ name: "TwoGroupDefaultObjects",
+ fi: twoGroupImage,
+ opts: []SignerOpt{
+ OptSignWithEntity(e),
+ },
wantGroupObjects: map[uint32][]uint32{1: {1, 2}, 2: {3}},
- wantEntity: e,
},
{
- name: "OptSignGroup1",
- fi: twoGroupImage,
- opts: []SignerOpt{OptSignGroup(1)},
+ name: "OptSignGroup1",
+ fi: twoGroupImage,
+ opts: []SignerOpt{
+ OptSignWithEntity(e),
+ OptSignGroup(1),
+ },
wantGroupObjects: map[uint32][]uint32{1: {1, 2}},
},
{
- name: "OptSignGroup2",
- fi: twoGroupImage,
- opts: []SignerOpt{OptSignGroup(2)},
+ name: "OptSignGroup2",
+ fi: twoGroupImage,
+ opts: []SignerOpt{
+ OptSignWithEntity(e),
+ OptSignGroup(2),
+ },
wantGroupObjects: map[uint32][]uint32{2: {3}},
},
{
- name: "OptSignObject1",
- fi: twoGroupImage,
- opts: []SignerOpt{OptSignObjects(1)},
+ name: "OptSignObject1",
+ fi: twoGroupImage,
+ opts: []SignerOpt{
+ OptSignWithEntity(e),
+ OptSignObjects(1),
+ },
wantGroupObjects: map[uint32][]uint32{1: {1}},
},
{
- name: "OptSignObject2",
- fi: twoGroupImage,
- opts: []SignerOpt{OptSignObjects(2)},
+ name: "OptSignObject2",
+ fi: twoGroupImage,
+ opts: []SignerOpt{
+ OptSignWithEntity(e),
+ OptSignObjects(2),
+ },
wantGroupObjects: map[uint32][]uint32{1: {2}},
},
{
- name: "OptSignObject3",
- fi: twoGroupImage,
- opts: []SignerOpt{OptSignObjects(3)},
+ name: "OptSignObject3",
+ fi: twoGroupImage,
+ opts: []SignerOpt{
+ OptSignWithEntity(e),
+ OptSignObjects(3),
+ },
wantGroupObjects: map[uint32][]uint32{2: {3}},
},
{
- name: "OptSignObjects",
- fi: twoGroupImage,
- opts: []SignerOpt{OptSignObjects(1, 2, 3)},
+ name: "OptSignObjects",
+ fi: twoGroupImage,
+ opts: []SignerOpt{
+ OptSignWithEntity(e),
+ OptSignObjects(1, 2, 3),
+ },
wantGroupObjects: map[uint32][]uint32{1: {1, 2}, 2: {3}},
},
}
@@ -554,11 +563,6 @@ func TestSigner_Sign(t *testing.T) {
wantErr bool
}{
{
- name: "NoKeyMaterial",
- inputFile: "one-group.sif",
- wantErr: true,
- },
- {
name: "EncryptedKey",
inputFile: "one-group.sif",
opts: []SignerOpt{OptSignWithEntity(encrypted)},
diff --git a/pkg/integrity/testdata/TestDigest_MarshalJSON/SHA1.golden b/pkg/integrity/testdata/TestDigest_MarshalJSON/SHA1.golden
deleted file mode 100644
index c39798c..0000000
--- a/pkg/integrity/testdata/TestDigest_MarshalJSON/SHA1.golden
+++ /dev/null
@@ -1 +0,0 @@
-"sha1:597f6a540010f94c15d71806a99a2c8710e747bd"
diff --git a/pkg/integrity/testdata/TestDigest_MarshalJSON/SHA512_224.golden b/pkg/integrity/testdata/TestDigest_MarshalJSON/SHA512_224.golden
new file mode 100644
index 0000000..96aff7b
--- /dev/null
+++ b/pkg/integrity/testdata/TestDigest_MarshalJSON/SHA512_224.golden
@@ -0,0 +1 @@
+"sha512_224:06001bf08dfb17d2b54925116823be230e98b5c6c278303bc4909a8c"
diff --git a/pkg/integrity/testdata/TestDigest_MarshalJSON/SHA512_256.golden b/pkg/integrity/testdata/TestDigest_MarshalJSON/SHA512_256.golden
new file mode 100644
index 0000000..c4a1fbc
--- /dev/null
+++ b/pkg/integrity/testdata/TestDigest_MarshalJSON/SHA512_256.golden
@@ -0,0 +1 @@
+"sha512_256:3d37fe58435e0d87323dee4a2c1b339ef954de63716ee79f5747f94d974f913f"
diff --git a/pkg/integrity/testdata/TestGetHeaderMetadata/SHA1.golden b/pkg/integrity/testdata/TestGetHeaderMetadata/SHA1.golden
deleted file mode 100644
index 51232ca..0000000
--- a/pkg/integrity/testdata/TestGetHeaderMetadata/SHA1.golden
+++ /dev/null
@@ -1 +0,0 @@
-{"digest":"sha1:bd6b562b49ff04470f641ad3c971822303049826"}
diff --git a/pkg/integrity/testdata/TestGetHeaderMetadata/SHA512_224.golden b/pkg/integrity/testdata/TestGetHeaderMetadata/SHA512_224.golden
new file mode 100644
index 0000000..9848911
--- /dev/null
+++ b/pkg/integrity/testdata/TestGetHeaderMetadata/SHA512_224.golden
@@ -0,0 +1 @@
+{"digest":"sha512_224:d5f9767e096056fcf381b801e2b0b80b33acdc09b7a7e3a1c504231e"}
diff --git a/pkg/integrity/testdata/TestGetHeaderMetadata/SHA512_256.golden b/pkg/integrity/testdata/TestGetHeaderMetadata/SHA512_256.golden
new file mode 100644
index 0000000..449166a
--- /dev/null
+++ b/pkg/integrity/testdata/TestGetHeaderMetadata/SHA512_256.golden
@@ -0,0 +1 @@
+{"digest":"sha512_256:eb199aeab4047ca6430890372769681045c20b1a0a4a78b595ab62dbdfc9285f"}
diff --git a/pkg/integrity/testdata/TestGetImageMetadata/Object1.golden b/pkg/integrity/testdata/TestGetImageMetadata/Object1.golden
index cbddd5d..142d7f0 100644
--- a/pkg/integrity/testdata/TestGetImageMetadata/Object1.golden
+++ b/pkg/integrity/testdata/TestGetImageMetadata/Object1.golden
@@ -1 +1 @@
-{"version":1,"header":{"digest":"sha1:86696357e7806b51baf75fc0bf9b8fc677e5cdd0"},"objects":[{"relativeId":0,"descriptorDigest":"sha1:1406a1a9c75a332fc50cb8519a9a7f9f2531480e","objectDigest":"sha1:15146b9bf4f1f5f9bf176a398d8c4f0321c63064"}]}
+{"version":1,"header":{"digest":"sha256:635fa0a14a8ef0c0351ed3e985799ed1d4f75ce973dea3cc76c99710795cc3f1"},"objects":[{"relativeId":0,"descriptorDigest":"sha256:3634ad01db0dd5482ecf685267b53d6201690438ca27c3d7ea91c971a1f41f92","objectDigest":"sha256:004dfc8da678c309de28b5386a1e9efd57f536b150c40d29b31506aa0fb17ec2"}]}
diff --git a/pkg/integrity/testdata/TestGetImageMetadata/Object2.golden b/pkg/integrity/testdata/TestGetImageMetadata/Object2.golden
index 9485be4..e130e64 100644
--- a/pkg/integrity/testdata/TestGetImageMetadata/Object2.golden
+++ b/pkg/integrity/testdata/TestGetImageMetadata/Object2.golden
@@ -1 +1 @@
-{"version":1,"header":{"digest":"sha1:86696357e7806b51baf75fc0bf9b8fc677e5cdd0"},"objects":[{"relativeId":1,"descriptorDigest":"sha1:ddf7e6609fec1f565545207f28d4b39f499d78c5","objectDigest":"sha1:d78f8bb992a56a597f6c7a1fb918bb78271367eb"}]}
+{"version":1,"header":{"digest":"sha256:635fa0a14a8ef0c0351ed3e985799ed1d4f75ce973dea3cc76c99710795cc3f1"},"objects":[{"relativeId":1,"descriptorDigest":"sha256:04b5f87c9692a54f80d10fb6af00c779763aeca29d610348854bd97cd8bf66fd","objectDigest":"sha256:9f9c4e5e131934969b4ac8f495691c70b8c6c8e3f489c2c9ab5f1af82bce0604"}]}
diff --git a/pkg/integrity/testdata/TestGetImageMetadata/SHA1.golden b/pkg/integrity/testdata/TestGetImageMetadata/SHA1.golden
deleted file mode 100644
index 7d86f1a..0000000
--- a/pkg/integrity/testdata/TestGetImageMetadata/SHA1.golden
+++ /dev/null
@@ -1 +0,0 @@
-{"version":1,"header":{"digest":"sha1:86696357e7806b51baf75fc0bf9b8fc677e5cdd0"},"objects":[{"relativeId":0,"descriptorDigest":"sha1:1406a1a9c75a332fc50cb8519a9a7f9f2531480e","objectDigest":"sha1:15146b9bf4f1f5f9bf176a398d8c4f0321c63064"},{"relativeId":1,"descriptorDigest":"sha1:ddf7e6609fec1f565545207f28d4b39f499d78c5","objectDigest":"sha1:d78f8bb992a56a597f6c7a1fb918bb78271367eb"}]}
diff --git a/pkg/integrity/testdata/TestGetImageMetadata/SHA224.golden b/pkg/integrity/testdata/TestGetImageMetadata/SHA224.golden
index 82dbc77..457318d 100644
--- a/pkg/integrity/testdata/TestGetImageMetadata/SHA224.golden
+++ b/pkg/integrity/testdata/TestGetImageMetadata/SHA224.golden
@@ -1 +1 @@
-{"version":1,"header":{"digest":"sha224:88ecbdbaa9bf8410c9362213ddae5e6771fd2525da3eb390300fb666"},"objects":[{"relativeId":0,"descriptorDigest":"sha224:8ac2ffbf24282ce5f49fc591eee1e0a879b0ae2a9fa813b897b94113","objectDigest":"sha224:071bce5faa03c2016d3e1e086ccb60b6ea3cabc493c9aa1013594efd"},{"relativeId":1,"descriptorDigest":"sha224:fc5b7f5b3622982bcebfbb8b3a0d81b6f850228f2ff3631d03deaab6","objectDigest":"sha224:55b9eee5f60cc362ddc07676f620372611e22272f60fdbec94f243f8"}]}
+{"version":1,"header":{"digest":"sha224:88ecbdbaa9bf8410c9362213ddae5e6771fd2525da3eb390300fb666"},"objects":[{"relativeId":0,"descriptorDigest":"sha224:8ac2ffbf24282ce5f49fc591eee1e0a879b0ae2a9fa813b897b94113","objectDigest":"sha224:071bce5faa03c2016d3e1e086ccb60b6ea3cabc493c9aa1013594efd"},{"relativeId":1,"descriptorDigest":"sha224:15f2307c74c24b5aff01556df7642009a968ad717514da242821b1cb","objectDigest":"sha224:1f26e23245e830c5aa90735377d43535b0a080d03981ea58e4b94372"}]}
diff --git a/pkg/integrity/testdata/TestGetImageMetadata/SHA256.golden b/pkg/integrity/testdata/TestGetImageMetadata/SHA256.golden
index 7286dc4..60eba6b 100644
--- a/pkg/integrity/testdata/TestGetImageMetadata/SHA256.golden
+++ b/pkg/integrity/testdata/TestGetImageMetadata/SHA256.golden
@@ -1 +1 @@
-{"version":1,"header":{"digest":"sha256:635fa0a14a8ef0c0351ed3e985799ed1d4f75ce973dea3cc76c99710795cc3f1"},"objects":[{"relativeId":0,"descriptorDigest":"sha256:3634ad01db0dd5482ecf685267b53d6201690438ca27c3d7ea91c971a1f41f92","objectDigest":"sha256:004dfc8da678c309de28b5386a1e9efd57f536b150c40d29b31506aa0fb17ec2"},{"relativeId":1,"descriptorDigest":"sha256:db74cb63348414def73535c9f0f83e8ad7df61229ed2806f4da8b69d6d7464d6","objectDigest":"sha256:5f78c33274e43fa9de5659265c1d917e25c03722dcb0b8d27db8d5feaa813953"}]}
+{"version":1,"header":{"digest":"sha256:635fa0a14a8ef0c0351ed3e985799ed1d4f75ce973dea3cc76c99710795cc3f1"},"objects":[{"relativeId":0,"descriptorDigest":"sha256:3634ad01db0dd5482ecf685267b53d6201690438ca27c3d7ea91c971a1f41f92","objectDigest":"sha256:004dfc8da678c309de28b5386a1e9efd57f536b150c40d29b31506aa0fb17ec2"},{"relativeId":1,"descriptorDigest":"sha256:04b5f87c9692a54f80d10fb6af00c779763aeca29d610348854bd97cd8bf66fd","objectDigest":"sha256:9f9c4e5e131934969b4ac8f495691c70b8c6c8e3f489c2c9ab5f1af82bce0604"}]}
diff --git a/pkg/integrity/testdata/TestGetImageMetadata/SHA384.golden b/pkg/integrity/testdata/TestGetImageMetadata/SHA384.golden
index a324654..9e3b6de 100644
--- a/pkg/integrity/testdata/TestGetImageMetadata/SHA384.golden
+++ b/pkg/integrity/testdata/TestGetImageMetadata/SHA384.golden
@@ -1 +1 @@
-{"version":1,"header":{"digest":"sha384:92bdcff06f2cde591d8af1f0ab80687e5eccf1dfe8fe7a8a8ef4b80d28bd2c7a77bcc4abfbcfdb3fc84ec7992ed54334"},"objects":[{"relativeId":0,"descriptorDigest":"sha384:ed532e8496b916182a4185a3d12f3a6d4c59d204965ef1a732013d1c05050291ec29b48b06ba100a948468868023fb82","objectDigest":"sha384:f8722c6694c4997334525090678b2148f6263502c3eb144a44e8be0d2bfd039f4067a3f8152f94ab3af7c63acfe78ce6"},{"relativeId":1,"descriptorDigest":"sha384:d06c5144e805d18f8ebbce1eef073dd711ab9a6550e76e1fb9b82e5a822dd83273069d258d099a437461e674f519ef9c","objectDigest":"sha384:0b7e0522460767c74abb4245bc0d3a27209a5aed111059faead54ffc74a93759160ac9642d7a7df3038ece62f2fa9815"}]}
+{"version":1,"header":{"digest":"sha384:92bdcff06f2cde591d8af1f0ab80687e5eccf1dfe8fe7a8a8ef4b80d28bd2c7a77bcc4abfbcfdb3fc84ec7992ed54334"},"objects":[{"relativeId":0,"descriptorDigest":"sha384:ed532e8496b916182a4185a3d12f3a6d4c59d204965ef1a732013d1c05050291ec29b48b06ba100a948468868023fb82","objectDigest":"sha384:f8722c6694c4997334525090678b2148f6263502c3eb144a44e8be0d2bfd039f4067a3f8152f94ab3af7c63acfe78ce6"},{"relativeId":1,"descriptorDigest":"sha384:de7f8e386d3c1679711711f31812d12a913b0f5202503e46e9e1c5fd36c49908c5e288a3c08e6b72285dba177596668d","objectDigest":"sha384:da6cf2d305a04a53623df94d5e74bc22aee7961a5a62b289f99db693e5a980ee276526f254f6504f9e66621ce821b977"}]}
diff --git a/pkg/integrity/testdata/TestGetImageMetadata/SHA512.golden b/pkg/integrity/testdata/TestGetImageMetadata/SHA512.golden
index d067015..1028bd3 100644
--- a/pkg/integrity/testdata/TestGetImageMetadata/SHA512.golden
+++ b/pkg/integrity/testdata/TestGetImageMetadata/SHA512.golden
@@ -1 +1 @@
-{"version":1,"header":{"digest":"sha512:503a1101d5a7f66e440f157576597c5ab9a3517b025259da985402f3b7de7c90c6034b8b5d3da992a9cae5b47dd355fce3f9932e92bc47422134cc5b7347e1e7"},"objects":[{"relativeId":0,"descriptorDigest":"sha512:4ccbff33c9be45cf2ba25412de4a87bd623fd48bf00598b756ea5a12a4eddb83aa93176a91a875b595750837b3ba77b5dfbbbd90e2126ca4d4828763db6dc591","objectDigest":"sha512:808e1f67ffbdbdae30946529b920a1ad6d49c0c50423bc0c9d41ece566e291b6c3e6b6839f3095fbab6bc15a5b971b07d4b8b2f22b982ce3c2b8fd05eef7e1b3"},{"relativeId":1,"descriptorDigest":"sha512:4759070398e2e62d47fc166051918ae99493c31e0cb533ad6b041941c69a81a3cd3141a665fb5d1adef21a5ab9ac3247ab2bd4b1551b9377728acf87e56d9870","objectDigest":"sha512:1284b2d521535196f22175d5f558104220a6ad7680e78b49fa6f20e57ea7b185d71ec1edb137e70eba528dedb141f5d2f8bb53149d262932b27cf41fed96aa7f"}]}
+{"version":1,"header":{"digest":"sha512:503a1101d5a7f66e440f157576597c5ab9a3517b025259da985402f3b7de7c90c6034b8b5d3da992a9cae5b47dd355fce3f9932e92bc47422134cc5b7347e1e7"},"objects":[{"relativeId":0,"descriptorDigest":"sha512:4ccbff33c9be45cf2ba25412de4a87bd623fd48bf00598b756ea5a12a4eddb83aa93176a91a875b595750837b3ba77b5dfbbbd90e2126ca4d4828763db6dc591","objectDigest":"sha512:808e1f67ffbdbdae30946529b920a1ad6d49c0c50423bc0c9d41ece566e291b6c3e6b6839f3095fbab6bc15a5b971b07d4b8b2f22b982ce3c2b8fd05eef7e1b3"},{"relativeId":1,"descriptorDigest":"sha512:39bc9e8ecf3192e0656f0b4de529b7e6b1b0d892a6b19fd6f619bf476558bc15255ead250e440c3fe69bbf061b49c0ca0d7de18616c0b3172b2ca2d0753ef331","objectDigest":"sha512:c948c053d5494e944dc251ba774882c58c6b18dae241caa84123c779170d1ed0a22ced3af76d67c7090b668fa7d80531e5b9e3f4677b1b5aae64e8d0d24999bf"}]}
diff --git a/pkg/integrity/testdata/TestGetObjectMetadata/RelativeID.golden b/pkg/integrity/testdata/TestGetObjectMetadata/RelativeID.golden
index dc7a4fc..409c57d 100644
--- a/pkg/integrity/testdata/TestGetObjectMetadata/RelativeID.golden
+++ b/pkg/integrity/testdata/TestGetObjectMetadata/RelativeID.golden
@@ -1 +1 @@
-{"relativeId":1,"descriptorDigest":"sha1:f3681c97de35ea124cd2e3687ed62988c7138f3a","objectDigest":"sha1:5bf1fd927dfb8679496a2e6cf00cbe50c1c87145"}
+{"relativeId":1,"descriptorDigest":"sha256:a1e6ca1d0cce1fbd71b186ac7a5c5a805c833ecc419a78d017558e79c0862790","objectDigest":"sha256:8b7df143d91c716ecfa5fc1730022f6b421b05cedee8fd52b1fc65a96030ad52"}
diff --git a/pkg/integrity/testdata/TestGetObjectMetadata/SHA1.golden b/pkg/integrity/testdata/TestGetObjectMetadata/SHA1.golden
deleted file mode 100644
index c1265fc..0000000
--- a/pkg/integrity/testdata/TestGetObjectMetadata/SHA1.golden
+++ /dev/null
@@ -1 +0,0 @@
-{"relativeId":0,"descriptorDigest":"sha1:042874d3fd63a516c5abe45b221ed8db1e5cfd84","objectDigest":"sha1:5bf1fd927dfb8679496a2e6cf00cbe50c1c87145"}
diff --git a/pkg/integrity/testdata/TestGetObjectMetadata/SHA512_224.golden b/pkg/integrity/testdata/TestGetObjectMetadata/SHA512_224.golden
new file mode 100644
index 0000000..856a115
--- /dev/null
+++ b/pkg/integrity/testdata/TestGetObjectMetadata/SHA512_224.golden
@@ -0,0 +1 @@
+{"relativeId":0,"descriptorDigest":"sha512_224:ba5b52f4337756f9efb0c7d35f16e0365ba5845b0dd9df5e9edfce3a","objectDigest":"sha512_224:b1d15ae18bb05265b44e9e0137f08078f53f5b239a78c49c2cfc2c9c"}
diff --git a/pkg/integrity/testdata/TestGetObjectMetadata/SHA512_256.golden b/pkg/integrity/testdata/TestGetObjectMetadata/SHA512_256.golden
new file mode 100644
index 0000000..3271f6d
--- /dev/null
+++ b/pkg/integrity/testdata/TestGetObjectMetadata/SHA512_256.golden
@@ -0,0 +1 @@
+{"relativeId":0,"descriptorDigest":"sha512_256:aef151cf86aaab28a4e086c9e1f9d19c8f85e4eb794336d909a6844ce7fb52ef","objectDigest":"sha512_256:9a801762c512490303535d35c221e2dc1d24f5094d038041dc4303ba7ac04f0e"}
diff --git a/pkg/integrity/testdata/TestGroupSigner_SignWithEntity/Group1.golden b/pkg/integrity/testdata/TestGroupSigner_Sign/Group1.golden
index 53cded0..108a9da 100644
--- a/pkg/integrity/testdata/TestGroupSigner_SignWithEntity/Group1.golden
+++ b/pkg/integrity/testdata/TestGroupSigner_Sign/Group1.golden
Binary files differ
diff --git a/pkg/integrity/testdata/TestGroupSigner_SignWithEntity/Group2.golden b/pkg/integrity/testdata/TestGroupSigner_Sign/Group2.golden
index acd896c..9e5737b 100644
--- a/pkg/integrity/testdata/TestGroupSigner_SignWithEntity/Group2.golden
+++ b/pkg/integrity/testdata/TestGroupSigner_Sign/Group2.golden
Binary files differ
diff --git a/pkg/integrity/testdata/TestGroupSigner_SignWithEntity/Object1.golden b/pkg/integrity/testdata/TestGroupSigner_Sign/Object1.golden
index 4acca9d..a559012 100644
--- a/pkg/integrity/testdata/TestGroupSigner_SignWithEntity/Object1.golden
+++ b/pkg/integrity/testdata/TestGroupSigner_Sign/Object1.golden
Binary files differ
diff --git a/pkg/integrity/testdata/TestGroupSigner_SignWithEntity/Object2.golden b/pkg/integrity/testdata/TestGroupSigner_Sign/Object2.golden
index 69c540d..f57bd02 100644
--- a/pkg/integrity/testdata/TestGroupSigner_SignWithEntity/Object2.golden
+++ b/pkg/integrity/testdata/TestGroupSigner_Sign/Object2.golden
Binary files differ
diff --git a/pkg/integrity/testdata/TestGroupSigner_SignWithEntity/SignatureConfigSHA256.golden b/pkg/integrity/testdata/TestGroupSigner_SignWithEntity/SignatureConfigSHA256.golden
deleted file mode 100644
index 53cded0..0000000
--- a/pkg/integrity/testdata/TestGroupSigner_SignWithEntity/SignatureConfigSHA256.golden
+++ /dev/null
Binary files differ
diff --git a/pkg/integrity/testdata/TestGroupSigner_SignWithEntity/SignatureConfigSHA384.golden b/pkg/integrity/testdata/TestGroupSigner_SignWithEntity/SignatureConfigSHA384.golden
deleted file mode 100644
index 717dd0e..0000000
--- a/pkg/integrity/testdata/TestGroupSigner_SignWithEntity/SignatureConfigSHA384.golden
+++ /dev/null
Binary files differ
diff --git a/pkg/integrity/testdata/TestGroupSigner_SignWithEntity/SignatureConfigSHA512.golden b/pkg/integrity/testdata/TestGroupSigner_SignWithEntity/SignatureConfigSHA512.golden
deleted file mode 100644
index c231fbf..0000000
--- a/pkg/integrity/testdata/TestGroupSigner_SignWithEntity/SignatureConfigSHA512.golden
+++ /dev/null
Binary files differ
diff --git a/pkg/integrity/testdata/TestSignAndEncodeJSON/DefaultHash.golden b/pkg/integrity/testdata/TestSignAndEncodeJSON/DefaultHash.golden
deleted file mode 100644
index 94cf19f..0000000
--- a/pkg/integrity/testdata/TestSignAndEncodeJSON/DefaultHash.golden
+++ /dev/null
@@ -1,15 +0,0 @@
------BEGIN PGP SIGNED MESSAGE-----
-Hash: SHA256
-
-{"One":1,"Two":2}
------BEGIN PGP SIGNATURE-----
-
-wsBzBAEBCAAnBQJZr0CRCZCiDCfuf/e6hBahBBIEXIwLEATQWN5L7aIMJ+5/97qE
-AABw3Af+JOwZe9vRSBAUbuv0akGte0vmiyBAOOJgMgP8G6PtSVMOuyk87hMs9WyB
-Gfgm8st0jkYsCP60lSziNd24cevrqSW+8CnbcWuyhSpl9DBtKnjt/BXc0AturkQA
-/8Yn/9XImSYGrcqjJeCWhVBrJTlPaYu8swbXhvDoHvrVG/t6POO4xYumN3Nf8uoY
-zP8TtK8L1c2H1sY1MbngL/rVaFapdChQw8XCc280JMqnnjixnqSdjaeVPoQJItjG
-m6k2nTUBn7OS9lHYRc9batc38ty13tVSCSd62nvfQILL5YOPqfRtGJzLU0LGMrIO
-aJv5oAPI8SaPaFGuFOPxc7oTckNCIw==
-=FYE/
------END PGP SIGNATURE----- \ No newline at end of file
diff --git a/pkg/integrity/testdata/TestSignAndEncodeJSON/SHA1.golden b/pkg/integrity/testdata/TestSignAndEncodeJSON/SHA1.golden
deleted file mode 100644
index 872a24a..0000000
--- a/pkg/integrity/testdata/TestSignAndEncodeJSON/SHA1.golden
+++ /dev/null
@@ -1,15 +0,0 @@
------BEGIN PGP SIGNED MESSAGE-----
-Hash: SHA1
-
-{"One":1,"Two":2}
------BEGIN PGP SIGNATURE-----
-
-wsBzBAEBAgAnBQJZr0CRCZCiDCfuf/e6hBahBBIEXIwLEATQWN5L7aIMJ+5/97qE
-AACnHAf+J7rou/ZbPfY+fodtzyIBTrCKyYv4R37Kiu7DQclLfBDvEiRp8ISo6VqE
-siGSUapkKCyAzgKcSs3W/A2PFdeI+w5qZ4+Vb/SMjtLCBraKdukNVbsXw8JcE9Mb
-mJGFkVvZu5afpNDQqiNAOJnpV4J1+jpnAZAMu7TQs1FmN6/hCwo4GLCtFDwbI8fO
-dPna3k1lNB/M0UDiPMhn2qdF6vMWF1m41aQ5CwvCAsMOFZi+3sBA8yZngXJ29fjL
-Wng0xaFZPfR/3dEFhlMcqCpM4OFpsVTP5b99GPXUEwaZpwPJuGVUSk7Ck8hfbndQ
-pCYg/sAXojQj69Ymc88utJq4GfR7eg==
-=EY0t
------END PGP SIGNATURE----- \ No newline at end of file
diff --git a/pkg/integrity/testdata/TestSignAndEncodeJSON/SHA224.golden b/pkg/integrity/testdata/TestSignAndEncodeJSON/SHA224.golden
deleted file mode 100644
index e01869b..0000000
--- a/pkg/integrity/testdata/TestSignAndEncodeJSON/SHA224.golden
+++ /dev/null
@@ -1,15 +0,0 @@
------BEGIN PGP SIGNED MESSAGE-----
-Hash: SHA224
-
-{"One":1,"Two":2}
------BEGIN PGP SIGNATURE-----
-
-wsBzBAEBCwAnBQJZr0CRCZCiDCfuf/e6hBahBBIEXIwLEATQWN5L7aIMJ+5/97qE
-AADZhQf8DDXI6Ac4RQwpb6dWEhSOKiKC/voPS1+NGEvFeZ0XE9aqdXgPxQkT5RSJ
-sAG7Hoe8tRRrcagXgcHSGVOnE6tqpL6CITFs6g5MtvlZRxRftpNEHo4xqbUMM1ce
-JPBKrg0u4nCARdq3XCQbuUjVJ2R24XojBjfBXsmNzDsiyekYyllpK/mmWouzQSdC
-IU1sa6/P0JyA0vYzuYPOAqJgFO/cRzXSDEuqW6I07kIbKCHi1joGYb5xVUofJ+2B
-Rt3QnlNrTzMCUxLYE6Rzy0JyzjpjhozY5ldVRkE2NrFkxrgLrItCIMNPjqud7n88
-/DPHuLBdTK6+g6qVMUBLF8QKB5X3yA==
-=Npht
------END PGP SIGNATURE----- \ No newline at end of file
diff --git a/pkg/integrity/testdata/TestSignAndEncodeJSON/SHA256.golden b/pkg/integrity/testdata/TestSignAndEncodeJSON/SHA256.golden
deleted file mode 100644
index 94cf19f..0000000
--- a/pkg/integrity/testdata/TestSignAndEncodeJSON/SHA256.golden
+++ /dev/null
@@ -1,15 +0,0 @@
------BEGIN PGP SIGNED MESSAGE-----
-Hash: SHA256
-
-{"One":1,"Two":2}
------BEGIN PGP SIGNATURE-----
-
-wsBzBAEBCAAnBQJZr0CRCZCiDCfuf/e6hBahBBIEXIwLEATQWN5L7aIMJ+5/97qE
-AABw3Af+JOwZe9vRSBAUbuv0akGte0vmiyBAOOJgMgP8G6PtSVMOuyk87hMs9WyB
-Gfgm8st0jkYsCP60lSziNd24cevrqSW+8CnbcWuyhSpl9DBtKnjt/BXc0AturkQA
-/8Yn/9XImSYGrcqjJeCWhVBrJTlPaYu8swbXhvDoHvrVG/t6POO4xYumN3Nf8uoY
-zP8TtK8L1c2H1sY1MbngL/rVaFapdChQw8XCc280JMqnnjixnqSdjaeVPoQJItjG
-m6k2nTUBn7OS9lHYRc9batc38ty13tVSCSd62nvfQILL5YOPqfRtGJzLU0LGMrIO
-aJv5oAPI8SaPaFGuFOPxc7oTckNCIw==
-=FYE/
------END PGP SIGNATURE----- \ No newline at end of file
diff --git a/pkg/integrity/testdata/TestSignAndEncodeJSON/SHA384.golden b/pkg/integrity/testdata/TestSignAndEncodeJSON/SHA384.golden
deleted file mode 100644
index 7269fb4..0000000
--- a/pkg/integrity/testdata/TestSignAndEncodeJSON/SHA384.golden
+++ /dev/null
@@ -1,15 +0,0 @@
------BEGIN PGP SIGNED MESSAGE-----
-Hash: SHA384
-
-{"One":1,"Two":2}
------BEGIN PGP SIGNATURE-----
-
-wsBzBAEBCQAnBQJZr0CRCZCiDCfuf/e6hBahBBIEXIwLEATQWN5L7aIMJ+5/97qE
-AACoHQgAlRlRxWaxCkiQPFFC8DtsV7GSw6dN4ELFxzMXd6slWhxFOJYnHX2VH1uC
-/9/1Wqzvvrv5uha5Fy3dRQT1Zpa802sJU0GorPxv6LIvOEcJ8UMUj2dTQ4traSSW
-aPb7+Dyvfsosai2it1dFU25+IFScsCk6xaf8l2mSTZxj/0jjx8lxwKUZaEjKTNNf
-uL9U46UmP3w7lN6g+Nk4lz+dqU6lzMCcyJmgekGNl7/1mmOdFxqR/QKgiK5etHzZ
-WxIy1yHpV7FLtwGYA6O7vXpw3uktl8Muoc78pU1E1YMibLfygj+VO5RINMyuYb/4
-uGn6I+LJczqFpgSKcnP7/+yRVTOHQg==
-=SYuD
------END PGP SIGNATURE----- \ No newline at end of file
diff --git a/pkg/integrity/testdata/TestSignAndEncodeJSON/SHA512.golden b/pkg/integrity/testdata/TestSignAndEncodeJSON/SHA512.golden
deleted file mode 100644
index 2bbc680..0000000
--- a/pkg/integrity/testdata/TestSignAndEncodeJSON/SHA512.golden
+++ /dev/null
@@ -1,15 +0,0 @@
------BEGIN PGP SIGNED MESSAGE-----
-Hash: SHA512
-
-{"One":1,"Two":2}
------BEGIN PGP SIGNATURE-----
-
-wsBzBAEBCgAnBQJZr0CRCZCiDCfuf/e6hBahBBIEXIwLEATQWN5L7aIMJ+5/97qE
-AADw9QgAl3ZD0vCmvxyPHiz3PuQpgEUM17z6msOtYJ8ZxL+uNe1niTeBYShvPxm1
-6VULiYnBEJ4npkHMKqIXUAnHIN9wUD/FO7/JkEW1ttxZSoQKIiJeL7m/C5E/qEG8
-ikFLlhYproo5lN18xf64h9oi2UaZGlF4jLufWflRNemSm/d5hPpns2gvP47L3jF2
-vDZO7VGi4Q5P6zO2Zg4E4tLBWZkH5PxQls5QC13VCu36zOXmEt2TRLeSQ47Y60du
-Sp+LFuSpnIC9qJmj6OWabfIvOzLyuoZQpcCjDmYOpxSftrF8b0pFXN/tXdbJzWI5
-dH8EBvdCJ15EWL41hgxyOps9uGAVaw==
-=aBQa
------END PGP SIGNATURE----- \ No newline at end of file
diff --git a/pkg/integrity/testdata/TestSigner_Sign/EncryptedKey.golden b/pkg/integrity/testdata/TestSigner_Sign/EncryptedKey.golden
index ea68b45..fb72e4a 100644
--- a/pkg/integrity/testdata/TestSigner_Sign/EncryptedKey.golden
+++ b/pkg/integrity/testdata/TestSigner_Sign/EncryptedKey.golden
Binary files differ
diff --git a/pkg/integrity/testdata/TestSigner_Sign/NoKeyMaterial.golden b/pkg/integrity/testdata/TestSigner_Sign/NoKeyMaterial.golden
deleted file mode 100644
index ea68b45..0000000
--- a/pkg/integrity/testdata/TestSigner_Sign/NoKeyMaterial.golden
+++ /dev/null
Binary files differ
diff --git a/pkg/integrity/testdata/TestSigner_Sign/OneGroup.golden b/pkg/integrity/testdata/TestSigner_Sign/OneGroup.golden
index a8694c2..94225e7 100644
--- a/pkg/integrity/testdata/TestSigner_Sign/OneGroup.golden
+++ b/pkg/integrity/testdata/TestSigner_Sign/OneGroup.golden
Binary files differ
diff --git a/pkg/integrity/testdata/TestSigner_Sign/OptSignDeterministic.golden b/pkg/integrity/testdata/TestSigner_Sign/OptSignDeterministic.golden
index ddd0767..6a497c8 100644
--- a/pkg/integrity/testdata/TestSigner_Sign/OptSignDeterministic.golden
+++ b/pkg/integrity/testdata/TestSigner_Sign/OptSignDeterministic.golden
Binary files differ
diff --git a/pkg/integrity/testdata/TestSigner_Sign/OptSignGroup1.golden b/pkg/integrity/testdata/TestSigner_Sign/OptSignGroup1.golden
index 7ab0bc4..838cbf2 100644
--- a/pkg/integrity/testdata/TestSigner_Sign/OptSignGroup1.golden
+++ b/pkg/integrity/testdata/TestSigner_Sign/OptSignGroup1.golden
Binary files differ
diff --git a/pkg/integrity/testdata/TestSigner_Sign/OptSignGroup2.golden b/pkg/integrity/testdata/TestSigner_Sign/OptSignGroup2.golden
index 0deb841..8a0f4fa 100644
--- a/pkg/integrity/testdata/TestSigner_Sign/OptSignGroup2.golden
+++ b/pkg/integrity/testdata/TestSigner_Sign/OptSignGroup2.golden
Binary files differ
diff --git a/pkg/integrity/testdata/TestSigner_Sign/OptSignObject1.golden b/pkg/integrity/testdata/TestSigner_Sign/OptSignObject1.golden
index bfc3afe..87aaa13 100644
--- a/pkg/integrity/testdata/TestSigner_Sign/OptSignObject1.golden
+++ b/pkg/integrity/testdata/TestSigner_Sign/OptSignObject1.golden
Binary files differ
diff --git a/pkg/integrity/testdata/TestSigner_Sign/OptSignObject2.golden b/pkg/integrity/testdata/TestSigner_Sign/OptSignObject2.golden
index 58b9acc..b5d103c 100644
--- a/pkg/integrity/testdata/TestSigner_Sign/OptSignObject2.golden
+++ b/pkg/integrity/testdata/TestSigner_Sign/OptSignObject2.golden
Binary files differ
diff --git a/pkg/integrity/testdata/TestSigner_Sign/OptSignObject3.golden b/pkg/integrity/testdata/TestSigner_Sign/OptSignObject3.golden
index 0deb841..8a0f4fa 100644
--- a/pkg/integrity/testdata/TestSigner_Sign/OptSignObject3.golden
+++ b/pkg/integrity/testdata/TestSigner_Sign/OptSignObject3.golden
Binary files differ
diff --git a/pkg/integrity/testdata/TestSigner_Sign/OptSignObjects.golden b/pkg/integrity/testdata/TestSigner_Sign/OptSignObjects.golden
index fd37b0d..a496b2e 100644
--- a/pkg/integrity/testdata/TestSigner_Sign/OptSignObjects.golden
+++ b/pkg/integrity/testdata/TestSigner_Sign/OptSignObjects.golden
Binary files differ
diff --git a/pkg/integrity/testdata/TestSigner_Sign/TwoGroups.golden b/pkg/integrity/testdata/TestSigner_Sign/TwoGroups.golden
index fd37b0d..a496b2e 100644
--- a/pkg/integrity/testdata/TestSigner_Sign/TwoGroups.golden
+++ b/pkg/integrity/testdata/TestSigner_Sign/TwoGroups.golden
Binary files differ
diff --git a/pkg/integrity/testdata/Test_clearsignEncoder_signMessage/OK.golden b/pkg/integrity/testdata/Test_clearsignEncoder_signMessage/OK.golden
new file mode 100644
index 0000000..b8682b3
--- /dev/null
+++ b/pkg/integrity/testdata/Test_clearsignEncoder_signMessage/OK.golden
@@ -0,0 +1,15 @@
+-----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----- \ No newline at end of file
diff --git a/pkg/integrity/verify.go b/pkg/integrity/verify.go
index ef9e3e5..0d7e6b0 100644
--- a/pkg/integrity/verify.go
+++ b/pkg/integrity/verify.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2020-2021, Sylabs Inc. All rights reserved.
+// 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.
@@ -7,7 +7,9 @@ package integrity
import (
"bytes"
+ "crypto"
"encoding/hex"
+ "encoding/json"
"errors"
"fmt"
"io"
@@ -67,7 +69,6 @@ type VerifyCallback func(r VerifyResult) (ignoreError bool)
type groupVerifier struct {
f *sif.FileImage // SIF image to verify.
- cb VerifyCallback // Verification callback.
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.
@@ -75,8 +76,8 @@ type groupVerifier struct {
// 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, cb VerifyCallback, groupID uint32, ods ...sif.Descriptor) (*groupVerifier, error) { // nolint:lll
- v := groupVerifier{f: f, cb: cb, groupID: groupID, ods: ods}
+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)
@@ -91,163 +92,114 @@ func newGroupVerifier(f *sif.FileImage, cb VerifyCallback, groupID uint32, ods .
return &v, nil
}
-// fingerprints returns a sorted list of unique fingerprints of entities that have signed the
-// objects specified by v.
-func (v *groupVerifier) fingerprints() ([][]byte, error) {
- sigs, err := getGroupSignatures(v.f, v.groupID, false)
- if errors.Is(err, &SignatureNotFoundError{}) {
- return nil, nil
- } else if err != nil {
- return nil, err
- }
- return getFingerprints(sigs)
+// 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 verifies the objects specified by v against signature sig using keyring kr.
+// 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, kr openpgp.KeyRing) ([]sif.Descriptor, *openpgp.Entity, error) { // nolint:lll
- b, err := sig.GetData()
+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 nil, nil, err
+ return &SignatureNotValidError{ID: sig.ID(), Err: err}
}
- // Verify signature and decode image metadata.
+ // Unmarshal image metadata.
var im imageMetadata
- e, _, err := verifyAndDecodeJSON(b, &im, kr)
- if err != nil {
- return nil, e, &SignatureNotValidError{ID: sig.ID(), Err: err}
+ 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 nil, e, err
+ return err
}
im.populateAbsoluteObjectIDs(minID)
// Ensure signing entity matches fingerprint in descriptor.
- _, fp, err := sig.SignatureMetadata()
- if err != nil {
- return nil, e, err
- }
- if !bytes.Equal(e.PrimaryKey.Fingerprint, fp) {
- return nil, e, errFingerprintMismatch
+ 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 nil, e, err
- }
- }
-
- // Verify header and object integrity.
- verified, err := im.matches(v.f, v.ods)
- if err != nil {
- return verified, e, err
- }
-
- return verified, e, nil
-}
-
-// verifyWithKeyRing performs verification of the objects specified by v using keyring kr.
-//
-// If no signatures are found for the object group specified by v, a SignatureNotFoundError is
-// returned. 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) verifyWithKeyRing(kr openpgp.KeyRing) error {
- // Obtain all signatures related to group.
- sigs, err := getGroupSignatures(v.f, v.groupID, false)
- if err != nil {
- return err
- }
-
- for _, sig := range sigs {
- verified, e, err := v.verifySignature(sig, kr)
-
- // Call verify callback, if applicable.
- if v.cb != nil {
- r := VerifyResult{sig: sig, verified: verified, e: e, err: err}
- if ignoreError := v.cb(r); ignoreError {
- err = nil
- }
- }
-
- if err != nil {
return err
}
}
- return nil
+ // 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.
- cb VerifyCallback // Verification callback.
groupID uint32 // Object group ID.
ods []sif.Descriptor // Object descriptors.
}
// newLegacyGroupVerifier constructs a new legacy group verifier.
-func newLegacyGroupVerifier(f *sif.FileImage, cb VerifyCallback, groupID uint32) (*legacyGroupVerifier, error) {
+func newLegacyGroupVerifier(f *sif.FileImage, groupID uint32) (*legacyGroupVerifier, error) {
ods, err := getGroupObjects(f, groupID)
if err != nil {
return nil, err
}
- return &legacyGroupVerifier{f: f, cb: cb, groupID: groupID, ods: ods}, nil
+
+ return &legacyGroupVerifier{f: f, groupID: groupID, ods: ods}, nil
}
-// fingerprints returns a sorted list of unique fingerprints of entities that have signed the
-// objects specified by v.
-func (v *legacyGroupVerifier) fingerprints() ([][]byte, error) {
- sigs, err := getGroupSignatures(v.f, v.groupID, true)
- if errors.Is(err, &SignatureNotFoundError{}) {
- return nil, nil
- } else if err != nil {
- return nil, err
- }
- return getFingerprints(sigs)
+// 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 verifies the objects specified by v against signature sig using keyring kr.
+// 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, kr openpgp.KeyRing) (*openpgp.Entity, error) {
- b, err := sig.GetData()
+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 nil, err
+ return &SignatureNotValidError{ID: sig.ID(), Err: err}
}
- // Verify signature and decode plaintext.
- e, b, _, err := verifyAndDecode(b, kr)
+ ht, fp, err := sig.SignatureMetadata()
if err != nil {
- return e, &SignatureNotValidError{ID: sig.ID(), Err: err}
+ return err
}
// Ensure signing entity matches fingerprint in descriptor.
- ht, fp, err := sig.SignatureMetadata()
- if err != nil {
- return e, err
- }
- if !bytes.Equal(e.PrimaryKey.Fingerprint, fp) {
- return e, errFingerprintMismatch
+ 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 e, err
+ return err
}
// Get reader covering all non-signature objects.
@@ -259,156 +211,93 @@ func (v *legacyGroupVerifier) verifySignature(sig sif.Descriptor, kr openpgp.Key
// Verify integrity of objects.
if ok, err := d.matches(r); err != nil {
- return e, err
- } else if !ok {
- return e, &ObjectIntegrityError{}
- }
-
- return e, nil
-}
-
-// verifyWithKeyRing performs verification of the objects specified by v using keyring kr.
-//
-// If no signatures are found for the object group specified by v, a SignatureNotFoundError is
-// returned. If an invalid signature is encountered, a SignatureNotValidError is returned.
-//
-// If verification of the data object group fails, a ObjectIntegrityError is returned.
-func (v *legacyGroupVerifier) verifyWithKeyRing(kr openpgp.KeyRing) error {
- // Obtain all signatures related to object.
- sigs, err := getGroupSignatures(v.f, v.groupID, true)
- if err != nil {
return err
+ } else if !ok {
+ return &ObjectIntegrityError{}
}
- for _, sig := range sigs {
- e, err := v.verifySignature(sig, kr)
-
- // Call verify callback, if applicable.
- if v.cb != nil {
- r := VerifyResult{sig: sig, e: e, err: err}
- if err == nil {
- r.verified = v.ods
- }
- if ignoreError := v.cb(r); ignoreError {
- err = nil
- }
- }
-
- if err != nil {
- return err
- }
- }
-
+ vr.verified = v.ods
return nil
}
type legacyObjectVerifier struct {
f *sif.FileImage // SIF image to verify.
- cb VerifyCallback // Verification callback.
od sif.Descriptor // Object descriptor.
}
// newLegacyObjectVerifier constructs a new legacy object verifier.
-func newLegacyObjectVerifier(f *sif.FileImage, cb VerifyCallback, id uint32) (*legacyObjectVerifier, error) {
- od, err := f.GetDescriptor(sif.WithID(id))
- if err != nil {
- return nil, err
- }
- return &legacyObjectVerifier{f: f, cb: cb, od: od}, nil
+func newLegacyObjectVerifier(f *sif.FileImage, od sif.Descriptor) *legacyObjectVerifier {
+ return &legacyObjectVerifier{f: f, od: od}
}
-// fingerprints returns a sorted list of unique fingerprints of entities that have signed the
-// objects specified by v.
-func (v *legacyObjectVerifier) fingerprints() ([][]byte, error) {
- sigs, err := getObjectSignatures(v.f, v.od.ID())
- if errors.Is(err, &SignatureNotFoundError{}) {
- return nil, nil
- } else if err != nil {
- return nil, err
- }
- return getFingerprints(sigs)
+// 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 verifies the objects specified by v against signature sig using keyring kr.
+// 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, kr openpgp.KeyRing) (*openpgp.Entity, error) {
- b, err := sig.GetData()
+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 nil, err
+ return &SignatureNotValidError{ID: sig.ID(), Err: err}
}
- // Verify signature and decode plaintext.
- e, b, _, err := verifyAndDecode(b, kr)
+ ht, fp, err := sig.SignatureMetadata()
if err != nil {
- return e, &SignatureNotValidError{ID: sig.ID(), Err: err}
+ return err
}
// Ensure signing entity matches fingerprint in descriptor.
- ht, fp, err := sig.SignatureMetadata()
- if err != nil {
- return e, err
- }
- if !bytes.Equal(e.PrimaryKey.Fingerprint, fp) {
- return e, errFingerprintMismatch
+ 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 e, err
+ return err
}
// Verify object integrity.
if ok, err := d.matches(v.od.GetReader()); err != nil {
- return e, err
+ return err
} else if !ok {
- return e, &ObjectIntegrityError{ID: v.od.ID()}
+ return &ObjectIntegrityError{ID: v.od.ID()}
}
- return e, nil
+ vr.verified = []sif.Descriptor{v.od}
+ return nil
}
-// verifyWithKeyRing performs verification of the objects specified by v using keyring kr.
-//
-// If no signatures are found for the object specified by v, a SignatureNotFoundError is returned.
-// If an invalid signature is encountered, a SignatureNotValidError is returned.
-//
-// If verification of the data object fails, a ObjectIntegrityError is returned.
-func (v *legacyObjectVerifier) verifyWithKeyRing(kr openpgp.KeyRing) error {
- // Obtain all signatures related to object.
- sigs, err := getObjectSignatures(v.f, v.od.ID())
- if err != nil {
- return err
- }
-
- for _, sig := range sigs {
- e, err := v.verifySignature(sig, kr)
-
- // Call verify callback, if applicable.
- if v.cb != nil {
- r := VerifyResult{sig: sig, e: e, err: err}
- if err == nil {
- r.verified = []sif.Descriptor{v.od}
- }
- if ignoreError := v.cb(r); ignoreError {
- err = nil
- }
- }
-
- if err != nil {
- return err
- }
- }
-
- 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 {
- fingerprints() ([][]byte, error)
- verifyWithKeyRing(kr openpgp.KeyRing) error
+ // 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 {
@@ -493,11 +382,11 @@ func OptVerifyCallback(cb VerifyCallback) VerifierOpt {
}
// getTasks returns verification tasks corresponding to groupIDs and objectIDs.
-func getTasks(f *sif.FileImage, cb VerifyCallback, groupIDs, objectIDs []uint32) ([]verifyTask, error) {
+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, cb, groupID)
+ v, err := newGroupVerifier(f, groupID)
if err != nil {
return nil, err
}
@@ -510,7 +399,7 @@ func getTasks(f *sif.FileImage, cb VerifyCallback, groupIDs, objectIDs []uint32)
return nil, err
}
- v, err := newGroupVerifier(f, cb, od.GroupID(), od)
+ v, err := newGroupVerifier(f, od.GroupID(), od)
if err != nil {
return nil, err
}
@@ -521,11 +410,11 @@ func getTasks(f *sif.FileImage, cb VerifyCallback, groupIDs, objectIDs []uint32)
}
// getLegacyTasks returns legacy verification tasks corresponding to groupIDs and objectIDs.
-func getLegacyTasks(f *sif.FileImage, cb VerifyCallback, groupIDs, objectIDs []uint32) ([]verifyTask, error) {
+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, cb, groupID)
+ v, err := newLegacyGroupVerifier(f, groupID)
if err != nil {
return nil, err
}
@@ -533,11 +422,12 @@ func getLegacyTasks(f *sif.FileImage, cb VerifyCallback, groupIDs, objectIDs []u
}
for _, id := range objectIDs {
- v, err := newLegacyObjectVerifier(f, cb, id)
+ od, err := f.GetDescriptor(sif.WithID(id))
if err != nil {
return nil, err
}
- t = append(t, v)
+
+ t = append(t, newLegacyObjectVerifier(f, od))
}
return t, nil
@@ -546,8 +436,9 @@ func getLegacyTasks(f *sif.FileImage, cb VerifyCallback, groupIDs, objectIDs []u
// Verifier describes a SIF image verifier.
type Verifier struct {
f *sif.FileImage
- kr openpgp.KeyRing
tasks []verifyTask
+ kr openpgp.KeyRing
+ cb VerifyCallback
}
// NewVerifier returns a Verifier to examine and/or verify digital signatures(s) in f according to
@@ -598,15 +489,16 @@ func NewVerifier(f *sif.FileImage, opts ...VerifierOpt) (*Verifier, error) {
if vo.isLegacy {
getTasksFunc = getLegacyTasks
}
- t, err := getTasksFunc(f, vo.cb, vo.groups, vo.objects)
+ t, err := getTasksFunc(f, vo.groups, vo.objects)
if err != nil {
return nil, fmt.Errorf("integrity: %w", err)
}
v := Verifier{
f: f,
- kr: vo.kr,
tasks: t,
+ kr: vo.kr,
+ cb: vo.cb,
}
return &v, nil
}
@@ -619,7 +511,12 @@ func (v *Verifier) fingerprints(any bool) ([][]byte, error) {
// Build up a map containing fingerprints, and the number of tasks they are participating in.
for _, t := range v.tasks {
- fps, err := t.fingerprints()
+ sigs, err := t.signatures()
+ if err != nil && !errors.Is(err, &SignatureNotFoundError{}) {
+ return nil, err
+ }
+
+ fps, err := getFingerprints(sigs)
if err != nil {
return nil, err
}
@@ -688,7 +585,12 @@ func (v *Verifier) AllSignedBy() ([][]byte, error) {
// DescriptorIntegrityError is returned. If verification of a data object fails, an error wrapping
// a ObjectIntegrityError is returned.
func (v *Verifier) Verify() error {
- if v.kr == nil {
+ // Get message decoder.
+ var de decoder
+ switch {
+ case v.kr != nil:
+ de = newClearsignDecoder(v.kr)
+ default:
return fmt.Errorf("integrity: %w", ErrNoKeyMaterial)
}
@@ -703,10 +605,32 @@ func (v *Verifier) Verify() error {
}
}
+ // Verify signature(s) associated with each task.
for _, t := range v.tasks {
- if err := t.verifyWithKeyRing(v.kr); err != nil {
+ 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
}
diff --git a/pkg/integrity/verify_test.go b/pkg/integrity/verify_test.go
index e32b5fd..8c29782 100644
--- a/pkg/integrity/verify_test.go
+++ b/pkg/integrity/verify_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2020-2021, Sylabs Inc. All rights reserved.
+// 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.
@@ -6,10 +6,12 @@
package integrity
import (
+ "crypto"
"errors"
"io"
"path/filepath"
"reflect"
+ "strings"
"testing"
"github.com/ProtonMail/go-crypto/openpgp"
@@ -17,29 +19,33 @@ import (
"github.com/sylabs/sif/v2/pkg/sif"
)
-func TestGroupVerifier_fingerprints(t *testing.T) {
+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"))
- e := getTestEntity(t)
+ sigs, err := oneGroupSignedImage.GetDescriptors(sif.WithDataType(sif.DataSignature))
+ if err != nil {
+ t.Fatal(err)
+ }
tests := []struct {
- name string
- f *sif.FileImage
- groupID uint32
- wantFPs [][]byte
- wantErr error
+ 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,
- wantFPs: [][]byte{e.PrimaryKey.Fingerprint},
+ name: "Signed",
+ f: oneGroupSignedImage,
+ groupID: 1,
+ wantSigs: sigs,
},
}
@@ -51,114 +57,88 @@ func TestGroupVerifier_fingerprints(t *testing.T) {
groupID: tt.groupID,
}
- got, err := v.fingerprints()
+ sigs, err := v.signatures()
- if !errors.Is(err, tt.wantErr) {
- t.Errorf("got error %v, want %v", err, tt.wantErr)
+ if got, want := err, tt.wantErr; !errors.Is(got, want) {
+ t.Fatalf("got error %v, want %v", got, want)
}
- if !reflect.DeepEqual(got, tt.wantFPs) {
- t.Errorf("got fingerprints %v, want %v", got, tt.wantFPs)
+ if got, want := sigs, tt.wantSigs; !reflect.DeepEqual(got, want) {
+ t.Errorf("got signatures %v, want %v", got, want)
}
})
}
}
-func TestGroupVerifier_verifyWithKeyRing(t *testing.T) {
- oneGroupImage := loadContainer(t, filepath.Join(corpus, "one-group.sif"))
+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)
- kr := openpgp.EntityList{e}
tests := []struct {
- name string
- f *sif.FileImage
- testCallback bool
- ignoreError bool
- groupID uint32
- objectIDs []uint32
- subsetOK bool
- kr openpgp.KeyRing
- wantCBSignature uint32
- wantCBVerified []uint32
- wantCBEntity *openpgp.Entity
- wantCBErr error
- wantErr error
+ 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: "SignatureNotFound",
- f: oneGroupImage,
- groupID: 1,
- objectIDs: []uint32{1, 2},
- kr: kr,
- wantErr: &SignatureNotFoundError{},
- },
- {
- name: "SignedObjectNotFound",
- f: oneGroupSignedImage,
- groupID: 1,
- objectIDs: []uint32{1},
- kr: kr,
- wantErr: errSignedObjectNotFound,
+ 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},
- kr: openpgp.EntityList{},
- wantErr: &SignatureNotValidError{ID: 3, Err: pgperrors.ErrUnknownIssuer},
- },
- {
- name: "IgnoreError",
- f: oneGroupSignedImage,
- testCallback: true,
- ignoreError: true,
- groupID: 1,
- objectIDs: []uint32{1, 2},
- kr: openpgp.EntityList{},
- wantCBSignature: 3,
- wantCBErr: &SignatureNotValidError{ID: 3, Err: pgperrors.ErrUnknownIssuer},
- wantErr: nil,
- },
- {
- name: "OneGroupSigned",
- f: oneGroupSignedImage,
- groupID: 1,
- objectIDs: []uint32{1, 2},
- kr: kr,
- },
- {
- name: "OneGroupSignedWithCallback",
- f: oneGroupSignedImage,
- testCallback: true,
- groupID: 1,
- objectIDs: []uint32{1, 2},
- kr: kr,
- wantCBSignature: 3,
- wantCBVerified: []uint32{1, 2},
- wantCBEntity: e,
+ sig: sig,
+ de: newClearsignDecoder(openpgp.EntityList{}),
+ wantErr: &SignatureNotValidError{
+ ID: 3,
+ Err: pgperrors.ErrUnknownIssuer,
+ },
},
{
- name: "OneGroupSignedSubset",
- f: oneGroupSignedImage,
- groupID: 1,
- objectIDs: []uint32{1},
- subsetOK: true,
- kr: kr,
+ name: "OneGroupSigned",
+ f: oneGroupSignedImage,
+ groupID: 1,
+ objectIDs: []uint32{1, 2},
+ sig: sig,
+ de: newClearsignDecoder(openpgp.EntityList{e}),
+ wantVerified: verified,
+ wantEntity: e,
},
{
- name: "OneGroupSignedSubsetWithCallback",
- f: oneGroupSignedImage,
- testCallback: true,
- groupID: 1,
- objectIDs: []uint32{1},
- subsetOK: true,
- kr: kr,
- wantCBSignature: 3,
- wantCBVerified: []uint32{1},
- wantCBEntity: e,
+ name: "OneGroupSignedSubset",
+ f: oneGroupSignedImage,
+ groupID: 1,
+ objectIDs: []uint32{1},
+ subsetOK: true,
+ sig: sig,
+ de: newClearsignDecoder(openpgp.EntityList{e}),
+ wantVerified: verified[:1],
+ wantEntity: e,
},
}
@@ -174,75 +154,58 @@ func TestGroupVerifier_verifyWithKeyRing(t *testing.T) {
ods[i] = od
}
- // Test callback functionality, if requested.
- var cb VerifyCallback
-
- //nolint:dupl
- if tt.testCallback {
- cb = func(r VerifyResult) bool {
- if got, want := r.Signature().ID(), tt.wantCBSignature; got != want {
- t.Errorf("got signature %v, want %v", got, want)
- }
-
- if got, want := len(r.Verified()), len(tt.wantCBVerified); got != want {
- t.Fatalf("got %v verified objects, want %v", got, want)
- }
- for i, od := range r.Verified() {
- if got, want := od.ID(), tt.wantCBVerified[i]; got != want {
- t.Errorf("got verified ID %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
- }
- }
-
v := &groupVerifier{
f: tt.f,
- cb: cb,
groupID: tt.groupID,
ods: ods,
subsetOK: tt.subsetOK,
}
- if got, want := v.verifyWithKeyRing(tt.kr), tt.wantErr; !errors.Is(got, want) {
+ 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_fingerprints(t *testing.T) {
+func TestLegacyGroupVerifier_signatures(t *testing.T) {
oneGroupImage := loadContainer(t, filepath.Join(corpus, "one-group.sif"))
- oneGroupImageSigned := loadContainer(t, filepath.Join(corpus, "one-group-signed-legacy-group.sif"))
+ oneGroupSignedImage := loadContainer(t, filepath.Join(corpus, "one-group-signed-legacy-group.sif"))
- e := getTestEntity(t)
+ sigs, err := oneGroupSignedImage.GetDescriptors(sif.WithDataType(sif.DataSignature))
+ if err != nil {
+ t.Fatal(err)
+ }
tests := []struct {
- name string
- f *sif.FileImage
- id uint32
- wantFPs [][]byte
- wantErr error
+ name string
+ f *sif.FileImage
+ id uint32
+ wantSigs []sif.Descriptor
+ wantErr error
}{
{
- name: "Unsigned",
- f: oneGroupImage,
- id: 1,
+ name: "Unsigned",
+ f: oneGroupImage,
+ id: 1,
+ wantErr: &SignatureNotFoundError{},
},
{
- name: "Signed",
- f: oneGroupImageSigned,
- id: 1,
- wantFPs: [][]byte{e.PrimaryKey.Fingerprint},
+ name: "Signed",
+ f: oneGroupSignedImage,
+ id: 1,
+ wantSigs: sigs,
},
}
@@ -254,132 +217,69 @@ func TestLegacyGroupVerifier_fingerprints(t *testing.T) {
groupID: 1,
}
- got, err := v.fingerprints()
+ sigs, err := v.signatures()
- if !errors.Is(err, tt.wantErr) {
- t.Errorf("got error %v, want %v", err, tt.wantErr)
+ if got, want := err, tt.wantErr; !errors.Is(got, want) {
+ t.Fatalf("got error %v, want %v", got, want)
}
- if !reflect.DeepEqual(got, tt.wantFPs) {
- t.Errorf("got fingerprints %v, want %v", got, tt.wantFPs)
+ if got, want := sigs, tt.wantSigs; !reflect.DeepEqual(got, want) {
+ t.Errorf("got signatures %v, want %v", got, want)
}
})
}
}
-func TestLegacyGroupVerifier_verifyWithKeyRing(t *testing.T) {
- oneGroupImage := loadContainer(t, filepath.Join(corpus, "one-group.sif"))
+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)
- kr := openpgp.EntityList{e}
tests := []struct {
- name string
- f *sif.FileImage
- testCallback bool
- ignoreError bool
- groupID uint32
- kr openpgp.KeyRing
- wantCBSignature uint32
- wantCBVerified []uint32
- wantCBEntity *openpgp.Entity
- wantCBErr error
- wantErr error
+ name string
+ f *sif.FileImage
+ groupID uint32
+ sig sif.Descriptor
+ de decoder
+ wantErr error
+ wantVerified []sif.Descriptor
+ wantEntity *openpgp.Entity
}{
{
- name: "SignatureNotFound",
- f: oneGroupImage,
- groupID: 1,
- kr: kr,
- wantErr: &SignatureNotFoundError{},
- },
- {
name: "UnknownIssuer",
f: oneGroupSignedImage,
groupID: 1,
- kr: openpgp.EntityList{},
- wantErr: pgperrors.ErrUnknownIssuer,
- },
- {
- name: "IgnoreError",
- f: oneGroupSignedImage,
- testCallback: true,
- ignoreError: true,
- groupID: 1,
- kr: openpgp.EntityList{},
- wantCBSignature: 3,
- wantCBErr: pgperrors.ErrUnknownIssuer,
- wantErr: nil,
- },
- {
- name: "OneGroupSigned",
- f: oneGroupSignedImage,
- groupID: 1,
- kr: kr,
- },
- {
- name: "OneGroupSignedWithCallback",
- f: oneGroupSignedImage,
- testCallback: true,
- groupID: 1,
- kr: kr,
- wantCBSignature: 3,
- wantCBVerified: []uint32{1, 2},
- wantCBEntity: e,
- },
- {
- name: "OneGroupSignedSubset",
- f: oneGroupSignedImage,
- groupID: 1,
- kr: kr,
+ sig: sig,
+ de: newClearsignDecoder(openpgp.EntityList{}),
+ wantErr: &SignatureNotValidError{
+ ID: 3,
+ Err: pgperrors.ErrUnknownIssuer,
+ },
},
{
- name: "OneGroupSignedSubsetWithCallback",
- f: oneGroupSignedImage,
- testCallback: true,
- groupID: 1,
- kr: kr,
- wantCBSignature: 3,
- wantCBVerified: []uint32{1, 2},
- wantCBEntity: e,
+ 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) {
- // Test callback functionality, if requested.
- var cb VerifyCallback
-
- //nolint:dupl
- if tt.testCallback {
- cb = func(r VerifyResult) bool {
- if got, want := r.Signature().ID(), tt.wantCBSignature; got != want {
- t.Errorf("got signature %v, want %v", got, want)
- }
-
- if got, want := len(r.Verified()), len(tt.wantCBVerified); got != want {
- t.Fatalf("got %v verified objects, want %v", got, want)
- }
- for i, od := range r.Verified() {
- if got, want := od.ID(), tt.wantCBVerified[i]; got != want {
- t.Errorf("got verified ID %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
- }
- }
-
ods, err := getGroupObjects(tt.f, tt.groupID)
if err != nil {
t.Fatal(err)
@@ -387,41 +287,58 @@ func TestLegacyGroupVerifier_verifyWithKeyRing(t *testing.T) {
v := &legacyGroupVerifier{
f: tt.f,
- cb: cb,
groupID: tt.groupID,
ods: ods,
}
- if got, want := v.verifyWithKeyRing(tt.kr), tt.wantErr; !errors.Is(got, want) {
+ 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_fingerprints(t *testing.T) {
+func TestLegacyObjectVerifier_signatures(t *testing.T) {
oneGroupImage := loadContainer(t, filepath.Join(corpus, "one-group.sif"))
- oneGroupImageSigned := loadContainer(t, filepath.Join(corpus, "one-group-signed-legacy-all.sif"))
+ oneGroupSignedImage := loadContainer(t, filepath.Join(corpus, "one-group-signed-legacy-all.sif"))
- e := getTestEntity(t)
+ 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
- wantFPs [][]byte
- wantErr error
+ name string
+ f *sif.FileImage
+ id uint32
+ wantSigs []sif.Descriptor
+ wantErr error
}{
{
- name: "Unsigned",
- f: oneGroupImage,
- id: 1,
+ name: "Unsigned",
+ f: oneGroupImage,
+ id: 1,
+ wantErr: &SignatureNotFoundError{},
},
{
- name: "Signed",
- f: oneGroupImageSigned,
- id: 1,
- wantFPs: [][]byte{e.PrimaryKey.Fingerprint},
+ name: "Signed",
+ f: oneGroupSignedImage,
+ id: 1,
+ wantSigs: sigs,
},
}
@@ -438,132 +355,72 @@ func TestLegacyObjectVerifier_fingerprints(t *testing.T) {
od: od,
}
- got, err := v.fingerprints()
+ sigs, err := v.signatures()
- if !errors.Is(err, tt.wantErr) {
- t.Errorf("got error %v, want %v", err, tt.wantErr)
+ if got, want := err, tt.wantErr; !errors.Is(got, want) {
+ t.Fatalf("got error %v, want %v", got, want)
}
- if !reflect.DeepEqual(got, tt.wantFPs) {
- t.Errorf("got fingerprints %v, want %v", got, tt.wantFPs)
+ if got, want := sigs, tt.wantSigs; !reflect.DeepEqual(got, want) {
+ t.Errorf("got signatures %v, want %v", got, want)
}
})
}
}
-func TestLegacyObjectVerifier_verifyWithKeyRing(t *testing.T) {
- oneGroupImage := loadContainer(t, filepath.Join(corpus, "one-group.sif"))
+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)
- kr := openpgp.EntityList{e}
tests := []struct {
- name string
- f *sif.FileImage
- testCallback bool
- ignoreError bool
- id uint32
- kr openpgp.KeyRing
- wantCBSignature uint32
- wantCBVerified []uint32
- wantCBEntity *openpgp.Entity
- wantCBErr error
- wantErr error
+ name string
+ f *sif.FileImage
+ id uint32
+ sig sif.Descriptor
+ de decoder
+ wantErr error
+ wantVerified []sif.Descriptor
+ wantEntity *openpgp.Entity
}{
{
- name: "SignatureNotFound",
- f: oneGroupImage,
- id: 1,
- kr: kr,
- wantErr: &SignatureNotFoundError{},
- },
- {
- name: "UnknownIssuer",
- f: oneGroupSignedImage,
- id: 1,
- kr: openpgp.EntityList{},
- wantErr: pgperrors.ErrUnknownIssuer,
- },
- {
- name: "IgnoreError",
- f: oneGroupSignedImage,
- testCallback: true,
- ignoreError: true,
- id: 1,
- kr: openpgp.EntityList{},
- wantCBSignature: 3,
- wantCBErr: pgperrors.ErrUnknownIssuer,
- wantErr: nil,
- },
- {
- name: "OneGroupSigned",
+ name: "UnknownIssuer",
f: oneGroupSignedImage,
id: 1,
- kr: kr,
- },
- {
- name: "OneGroupSignedWithCallback",
- f: oneGroupSignedImage,
- testCallback: true,
- id: 1,
- kr: kr,
- wantCBSignature: 3,
- wantCBVerified: []uint32{1},
- wantCBEntity: e,
- },
- {
- name: "OneGroupSignedSubset",
- f: oneGroupSignedImage,
- id: 1,
- kr: kr,
+ sig: sig,
+ de: newClearsignDecoder(openpgp.EntityList{}),
+ wantErr: &SignatureNotValidError{
+ ID: 3,
+ Err: pgperrors.ErrUnknownIssuer,
+ },
},
{
- name: "OneGroupSignedSubsetWithCallback",
- f: oneGroupSignedImage,
- testCallback: true,
- id: 1,
- kr: kr,
- wantCBSignature: 3,
- wantCBVerified: []uint32{1},
- wantCBEntity: e,
+ 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) {
- // Test callback functionality, if requested.
- var cb VerifyCallback
-
- //nolint:dupl
- if tt.testCallback {
- cb = func(r VerifyResult) bool {
- if got, want := r.Signature().ID(), tt.wantCBSignature; got != want {
- t.Errorf("got signature %v, want %v", got, want)
- }
-
- if got, want := len(r.Verified()), len(tt.wantCBVerified); got != want {
- t.Fatalf("got %v verified objects, want %v", got, want)
- }
- for i, od := range r.Verified() {
- if got, want := od.ID(), tt.wantCBVerified[i]; got != want {
- t.Errorf("got verified ID %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
- }
- }
-
od, err := tt.f.GetDescriptor(sif.WithID(tt.id))
if err != nil {
t.Fatal(err)
@@ -571,13 +428,23 @@ func TestLegacyObjectVerifier_verifyWithKeyRing(t *testing.T) {
v := &legacyObjectVerifier{
f: tt.f,
- cb: cb,
od: od,
}
- if got, want := v.verifyWithKeyRing(tt.kr), tt.wantErr; !errors.Is(got, want) {
+ 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)
+ }
})
}
}
@@ -811,16 +678,68 @@ func TestNewVerifier(t *testing.T) {
}
type mockVerifier struct {
- fps [][]byte
- err error
+ sigs []sif.Descriptor
+ sigsErr error
+
+ verified []sif.Descriptor
+ e *openpgp.Entity
+ verifyErr error
}
-func (v mockVerifier) fingerprints() ([][]byte, error) {
- return v.fps, v.err
+func (v mockVerifier) signatures() ([]sif.Descriptor, error) {
+ return v.sigs, v.sigsErr
}
-func (v mockVerifier) verifyWithKeyRing(kr openpgp.KeyRing) error {
- return v.err
+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) {
@@ -834,6 +753,13 @@ func TestVerifier_AnySignedBy(t *testing.T) {
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
@@ -843,48 +769,48 @@ func TestVerifier_AnySignedBy(t *testing.T) {
{
name: "OneTaskEOF",
tasks: []verifyTask{
- mockVerifier{err: io.EOF},
+ mockVerifier{sigsErr: io.EOF},
},
wantErr: io.EOF,
},
{
name: "TwoTasksEOF",
tasks: []verifyTask{
- mockVerifier{fps: [][]byte{fp1}},
- mockVerifier{err: io.EOF},
+ mockVerifier{sigs: sigs[:1]},
+ mockVerifier{sigsErr: io.EOF},
},
wantErr: io.EOF,
},
{
name: "OneTaskOneFP",
tasks: []verifyTask{
- mockVerifier{fps: [][]byte{fp1}},
+ mockVerifier{sigs: sigs[:1]},
},
wantFingerprints: [][]byte{fp1},
},
{
name: "TwoTasksSameFP",
tasks: []verifyTask{
- mockVerifier{fps: [][]byte{fp1}},
- mockVerifier{fps: [][]byte{fp1}},
+ mockVerifier{sigs: sigs[:1]},
+ mockVerifier{sigs: sigs[:1]},
},
wantFingerprints: [][]byte{fp1},
},
{
name: "TwoTasksTwoFP",
tasks: []verifyTask{
- mockVerifier{fps: [][]byte{fp1}},
- mockVerifier{fps: [][]byte{fp2}},
+ mockVerifier{sigs: sigs[:1]},
+ mockVerifier{sigs: sigs[1:]},
},
wantFingerprints: [][]byte{fp1, fp2},
},
{
name: "KitchenSink",
tasks: []verifyTask{
- mockVerifier{fps: [][]byte{}},
- mockVerifier{fps: [][]byte{fp1}},
- mockVerifier{fps: [][]byte{fp2}},
- mockVerifier{fps: [][]byte{fp1, fp2}},
+ mockVerifier{},
+ mockVerifier{sigs: sigs[:1]},
+ mockVerifier{sigs: sigs[1:]},
+ mockVerifier{sigs: sigs},
},
wantFingerprints: [][]byte{fp1, fp2},
},
@@ -919,6 +845,13 @@ func TestVerifier_AllSignedBy(t *testing.T) {
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
@@ -928,53 +861,53 @@ func TestVerifier_AllSignedBy(t *testing.T) {
{
name: "OneTaskEOF",
tasks: []verifyTask{
- mockVerifier{err: io.EOF},
+ mockVerifier{sigsErr: io.EOF},
},
wantErr: io.EOF,
},
{
name: "TwoTasksEOF",
tasks: []verifyTask{
- mockVerifier{fps: [][]byte{fp1}},
- mockVerifier{err: io.EOF},
+ mockVerifier{sigs: sigs[:1]},
+ mockVerifier{sigsErr: io.EOF},
},
wantErr: io.EOF,
},
{
name: "OneTaskNoFP",
tasks: []verifyTask{
- mockVerifier{fps: [][]byte{}},
+ mockVerifier{},
},
},
{
name: "OneTaskOneFP",
tasks: []verifyTask{
- mockVerifier{fps: [][]byte{fp1}},
+ mockVerifier{sigs: sigs[:1]},
},
wantFingerprints: [][]byte{fp1},
},
{
name: "TwoTasksSameFP",
tasks: []verifyTask{
- mockVerifier{fps: [][]byte{fp1}},
- mockVerifier{fps: [][]byte{fp1}},
+ mockVerifier{sigs: sigs[:1]},
+ mockVerifier{sigs: sigs[:1]},
},
wantFingerprints: [][]byte{fp1},
},
{
name: "TwoTasksTwoFP",
tasks: []verifyTask{
- mockVerifier{fps: [][]byte{fp1}},
- mockVerifier{fps: [][]byte{fp2}},
+ mockVerifier{sigs: sigs[:1]},
+ mockVerifier{sigs: sigs[1:]},
},
},
{
name: "KitchenSink",
tasks: []verifyTask{
- mockVerifier{fps: [][]byte{}},
- mockVerifier{fps: [][]byte{fp1}},
- mockVerifier{fps: [][]byte{fp2}},
- mockVerifier{fps: [][]byte{fp1, fp2}},
+ mockVerifier{},
+ mockVerifier{sigs: sigs[:1]},
+ mockVerifier{sigs: sigs[1:]},
+ mockVerifier{sigs: sigs},
},
},
}
@@ -998,35 +931,109 @@ func TestVerifier_AllSignedBy(t *testing.T) {
}
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"))
- kr := openpgp.EntityList{getTestEntity(t)}
+ 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
- kr openpgp.KeyRing
- tasks []verifyTask
- wantErr error
+ 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: "ErrNoKeyMaterial",
- f: oneGroupSignedImage,
- tasks: []verifyTask{mockVerifier{}},
+ name: "NoKeyMaterial",
+ f: oneGroupSignedImage,
+ tasks: []verifyTask{
+ mockVerifier{},
+ },
wantErr: ErrNoKeyMaterial,
},
{
- name: "EOF",
- f: oneGroupSignedImage,
+ name: "SignatureNotFound",
+ f: oneGroupImage,
+ tasks: []verifyTask{
+ mockVerifier{
+ sigsErr: &SignatureNotFoundError{ID: 1, IsGroup: true},
+ },
+ },
kr: kr,
- tasks: []verifyTask{mockVerifier{err: io.EOF}},
- wantErr: io.EOF,
+ 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: "OK",
- f: oneGroupSignedImage,
- kr: kr,
- tasks: []verifyTask{mockVerifier{}},
+ 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,
},
}
@@ -1035,8 +1042,31 @@ func TestVerifier_Verify(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
v := Verifier{
f: tt.f,
- kr: tt.kr,
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) {
diff --git a/pkg/sif/arch.go b/pkg/sif/arch.go
index d7acbb6..d6f6673 100644
--- a/pkg/sif/arch.go
+++ b/pkg/sif/arch.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021, Sylabs Inc. All rights reserved.
+// 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.
@@ -18,6 +18,7 @@ var (
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
@@ -36,6 +37,7 @@ func getSIFArch(arch string) archType {
"mips64": hdrArchMIPS64,
"mips64le": hdrArchMIPS64le,
"s390x": hdrArchS390x,
+ "riscv64": hdrArchRISCV64,
}
t, ok := archMap[arch]
@@ -59,6 +61,7 @@ func (t archType) GoArch() string {
hdrArchMIPS64: "mips64",
hdrArchMIPS64le: "mips64le",
hdrArchS390x: "s390x",
+ hdrArchRISCV64: "riscv64",
}
arch, ok := archMap[t]
diff --git a/pkg/sif/buffer.go b/pkg/sif/buffer.go
index d706fb1..1d73a47 100644
--- a/pkg/sif/buffer.go
+++ b/pkg/sif/buffer.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021, Sylabs Inc. All rights reserved.
+// 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.
@@ -25,7 +25,7 @@ func NewBuffer(buf []byte) *Buffer {
var errNegativeOffset = errors.New("negative offset")
// ReadAt implements the io.ReaderAt interface.
-func (b *Buffer) ReadAt(p []byte, off int64) (n int, err error) {
+func (b *Buffer) ReadAt(p []byte, off int64) (int, error) {
if off < 0 {
return 0, errNegativeOffset
}
@@ -34,17 +34,17 @@ func (b *Buffer) ReadAt(p []byte, off int64) (n int, err error) {
return 0, io.EOF
}
- n = copy(p, b.buf[off:])
+ n := copy(p, b.buf[off:])
if n < len(p) {
- err = io.EOF
+ return n, io.EOF
}
- return n, err
+ return n, nil
}
var errNegativePosition = errors.New("negative position")
// Write implements the io.Writer interface.
-func (b *Buffer) Write(p []byte) (n int, err error) {
+func (b *Buffer) Write(p []byte) (int, error) {
if b.pos < 0 {
return 0, errNegativePosition
}
@@ -53,7 +53,7 @@ func (b *Buffer) Write(p []byte) (n int, err error) {
b.buf = append(b.buf, make([]byte, need-have)...)
}
- n = copy(b.buf[b.pos:], p)
+ n := copy(b.buf[b.pos:], p)
b.pos += int64(n)
return n, nil
}
diff --git a/pkg/sif/create.go b/pkg/sif/create.go
index e65bdb7..104e9ea 100644
--- a/pkg/sif/create.go
+++ b/pkg/sif/create.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2018-2021, Sylabs Inc. All rights reserved.
+// Copyright (c) 2018-2022, Sylabs Inc. All rights reserved.
// Copyright (c) 2017, SingularityWare, LLC. All rights reserved.
// Copyright (c) 2017, Yannick Cote <yhcote@gmail.com> All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the
@@ -104,7 +104,7 @@ func (f *FileImage) writeDescriptors() error {
return binary.Write(f.rw, binary.LittleEndian, f.rds)
}
-// writeHeader writes the the global header in f to backing storage.
+// 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
diff --git a/pkg/sif/descriptor.go b/pkg/sif/descriptor.go
index da7a6a7..8fa926a 100644
--- a/pkg/sif/descriptor.go
+++ b/pkg/sif/descriptor.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2018-2021, Sylabs Inc. All rights reserved.
+// Copyright (c) 2018-2022, Sylabs Inc. All rights reserved.
// Copyright (c) 2017, SingularityWare, LLC. All rights reserved.
// Copyright (c) 2017, Yannick Cote <yhcote@gmail.com> All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the
@@ -56,6 +56,11 @@ type cryptoMessage struct {
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.
@@ -96,7 +101,7 @@ func (d *rawDescriptor) setExtra(v interface{}) error {
}
// getPartitionMetadata gets metadata for a partition data object.
-func (d rawDescriptor) getPartitionMetadata() (fs FSType, pt PartType, arch string, err error) {
+func (d rawDescriptor) getPartitionMetadata() (FSType, PartType, string, error) {
if got, want := d.DataType, DataPartition; got != want {
return 0, 0, "", &unexpectedDataTypeError{got, []DataType{want}}
}
@@ -142,6 +147,8 @@ 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
}
@@ -162,6 +169,8 @@ func (d Descriptor) ModifiedAt() time.Time { return time.Unix(d.raw.ModifiedAt,
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()
}
@@ -186,6 +195,8 @@ func getHashType(ht hashType) (crypto.Hash, error) {
}
// 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}}
@@ -203,6 +214,11 @@ func (d Descriptor) SignatureMetadata() (ht crypto.Hash, fp []byte, err error) {
}
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
@@ -224,6 +240,22 @@ func (d Descriptor) CryptoMessageMetadata() (FormatType, MessageType, error) {
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)
diff --git a/pkg/sif/descriptor_input.go b/pkg/sif/descriptor_input.go
index c55cf51..3e81c39 100644
--- a/pkg/sif/descriptor_input.go
+++ b/pkg/sif/descriptor_input.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021, Sylabs Inc. All rights reserved.
+// 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.
@@ -10,7 +10,6 @@ import (
"errors"
"fmt"
"io"
- "os"
"time"
)
@@ -227,6 +226,24 @@ func OptSignatureMetadata(ht crypto.Hash, fp []byte) DescriptorInputOpt {
}
}
+// 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
@@ -242,14 +259,15 @@ const DefaultObjectGroup = 1
//
// It is possible (and often necessary) to store additional metadata related to certain types of
// data objects. Consider supplying options such as OptCryptoMessageMetadata, OptPartitionMetadata,
-// and OptSignatureMetadata for this purpose.
+// 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 be aligned according to the system's memory page size. To
-// override this behavior, consider using OptObjectAlignment.
+// 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.
//
@@ -258,8 +276,11 @@ const DefaultObjectGroup = 1
// 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,
- alignment: os.Getpagesize(),
+ groupID: DefaultObjectGroup,
+ }
+
+ if t == DataPartition {
+ dopts.alignment = 4096
}
for _, opt := range opts {
diff --git a/pkg/sif/descriptor_input_test.go b/pkg/sif/descriptor_input_test.go
index 8431231..5c34099 100644
--- a/pkg/sif/descriptor_input_test.go
+++ b/pkg/sif/descriptor_input_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021, Sylabs Inc. All rights reserved.
+// 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.
@@ -163,6 +163,21 @@ func TestNewDescriptorInput(t *testing.T) {
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
diff --git a/pkg/sif/descriptor_test.go b/pkg/sif/descriptor_test.go
index 470d91a..21280d9 100644
--- a/pkg/sif/descriptor_test.go
+++ b/pkg/sif/descriptor_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2018-2021, Sylabs Inc. All rights reserved.
+// 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.
@@ -168,48 +168,51 @@ func TestDescriptor_PartitionMetadata(t *testing.T) {
}
func TestDescriptor_SignatureMetadata(t *testing.T) {
- s := signature{
- Hashtype: hashSHA384,
- }
- copy(s.Entity[:], []byte{
- 0x12, 0x04, 0x5c, 0x8c, 0x0b, 0x10, 0x04, 0xd0, 0x58, 0xde,
- 0x4b, 0xed, 0xa2, 0x0c, 0x27, 0xee, 0x7f, 0xf7, 0xba, 0x84,
- })
-
- rd := rawDescriptor{
- DataType: DataSignature,
- }
- if err := rd.setExtra(s); err != nil {
- t.Fatal(err)
- }
-
tests := []struct {
name string
- rd rawDescriptor
- wantHT crypto.Hash
- wantFP []byte
+ dt DataType
+ ht hashType
+ fp []byte
wantErr error
+ wantHT crypto.Hash
}{
{
- name: "UnexpectedDataType",
- rd: rawDescriptor{
- DataType: DataGeneric,
- },
+ name: "UnexpectedDataType",
+ dt: DataGeneric,
wantErr: &unexpectedDataTypeError{DataGeneric, []DataType{DataSignature}},
},
{
- name: "OK",
- rd: rd,
- wantHT: crypto.SHA384,
- wantFP: []byte{
+ 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) {
- d := Descriptor{raw: tt.rd}
+ 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()
@@ -222,7 +225,7 @@ func TestDescriptor_SignatureMetadata(t *testing.T) {
t.Fatalf("got hash type %v, want %v", got, want)
}
- if got, want := fp, tt.wantFP; !bytes.Equal(got, want) {
+ if got, want := fp, tt.fp; !bytes.Equal(got, want) {
t.Fatalf("got entity %v, want %v", got, want)
}
}
@@ -287,6 +290,56 @@ func TestDescriptor_CryptoMessageMetadata(t *testing.T) {
}
}
+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,
diff --git a/pkg/sif/sif.go b/pkg/sif/sif.go
index 704acee..2d1c209 100644
--- a/pkg/sif/sif.go
+++ b/pkg/sif/sif.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2018-2021, Sylabs Inc. All rights reserved.
+// Copyright (c) 2018-2022, Sylabs Inc. All rights reserved.
// Copyright (c) 2017, SingularityWare, LLC. All rights reserved.
// Copyright (c) 2017, Yannick Cote <yhcote@gmail.com> All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the
@@ -10,69 +10,68 @@
//
// 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 |
-// | . |
-// | . |
-// | . |
-// `================================================'
-//
+// .================================================.
+// | 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 (
@@ -133,6 +132,7 @@ const (
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.
@@ -154,6 +154,8 @@ func (t DataType) String() string {
return "Generic/Raw"
case DataCryptoMessage:
return "Cryptographic Message"
+ case DataSBOM:
+ return "SBOM"
}
return "Unknown"
}
@@ -268,6 +270,44 @@ func (t MessageType) String() string {
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
diff --git a/pkg/sif/testdata/TestAddObject/Empty.golden b/pkg/sif/testdata/TestAddObject/Empty.golden
index c236112..74b6489 100644
--- a/pkg/sif/testdata/TestAddObject/Empty.golden
+++ b/pkg/sif/testdata/TestAddObject/Empty.golden
Binary files differ
diff --git a/pkg/sif/testdata/TestAddObject/NotEmpty.golden b/pkg/sif/testdata/TestAddObject/NotEmpty.golden
index c3dd3d4..df09aee 100644
--- a/pkg/sif/testdata/TestAddObject/NotEmpty.golden
+++ b/pkg/sif/testdata/TestAddObject/NotEmpty.golden
Binary files differ
diff --git a/pkg/sif/testdata/TestAddObject/NotEmptyAligned.golden b/pkg/sif/testdata/TestAddObject/NotEmptyAligned.golden
index 6980717..68feda4 100644
--- a/pkg/sif/testdata/TestAddObject/NotEmptyAligned.golden
+++ b/pkg/sif/testdata/TestAddObject/NotEmptyAligned.golden
Binary files differ
diff --git a/pkg/sif/testdata/TestAddObject/NotEmptyNotAligned.golden b/pkg/sif/testdata/TestAddObject/NotEmptyNotAligned.golden
index f09b553..2855fff 100644
--- a/pkg/sif/testdata/TestAddObject/NotEmptyNotAligned.golden
+++ b/pkg/sif/testdata/TestAddObject/NotEmptyNotAligned.golden
Binary files differ
diff --git a/pkg/sif/testdata/TestAddObject/WithTime.golden b/pkg/sif/testdata/TestAddObject/WithTime.golden
index 269992d..f7d78b1 100644
--- a/pkg/sif/testdata/TestAddObject/WithTime.golden
+++ b/pkg/sif/testdata/TestAddObject/WithTime.golden
Binary files differ
diff --git a/pkg/sif/testdata/TestCreateContainer/OneDescriptor.golden b/pkg/sif/testdata/TestCreateContainer/OneDescriptor.golden
index c236112..74b6489 100644
--- a/pkg/sif/testdata/TestCreateContainer/OneDescriptor.golden
+++ b/pkg/sif/testdata/TestCreateContainer/OneDescriptor.golden
Binary files differ
diff --git a/pkg/sif/testdata/TestCreateContainer/TwoDescriptors.golden b/pkg/sif/testdata/TestCreateContainer/TwoDescriptors.golden
index c3dd3d4..df09aee 100644
--- a/pkg/sif/testdata/TestCreateContainer/TwoDescriptors.golden
+++ b/pkg/sif/testdata/TestCreateContainer/TwoDescriptors.golden
Binary files differ
diff --git a/pkg/sif/testdata/TestCreateContainerAtPath/OneDescriptor.golden b/pkg/sif/testdata/TestCreateContainerAtPath/OneDescriptor.golden
index c236112..74b6489 100644
--- a/pkg/sif/testdata/TestCreateContainerAtPath/OneDescriptor.golden
+++ b/pkg/sif/testdata/TestCreateContainerAtPath/OneDescriptor.golden
Binary files differ
diff --git a/pkg/sif/testdata/TestCreateContainerAtPath/TwoDescriptors.golden b/pkg/sif/testdata/TestCreateContainerAtPath/TwoDescriptors.golden
index c3dd3d4..df09aee 100644
--- a/pkg/sif/testdata/TestCreateContainerAtPath/TwoDescriptors.golden
+++ b/pkg/sif/testdata/TestCreateContainerAtPath/TwoDescriptors.golden
Binary files differ
diff --git a/pkg/sif/testdata/TestDeleteObject/WithTime.golden b/pkg/sif/testdata/TestDeleteObject/WithTime.golden
index c669c00..dd4e608 100644
--- a/pkg/sif/testdata/TestDeleteObject/WithTime.golden
+++ b/pkg/sif/testdata/TestDeleteObject/WithTime.golden
Binary files differ
diff --git a/pkg/sif/testdata/TestDeleteObject/Zero.golden b/pkg/sif/testdata/TestDeleteObject/Zero.golden
index 85b217d..036b369 100644
--- a/pkg/sif/testdata/TestDeleteObject/Zero.golden
+++ b/pkg/sif/testdata/TestDeleteObject/Zero.golden
Binary files differ
diff --git a/pkg/sif/testdata/TestNewDescriptorInput/OptSBOMMetadata.golden b/pkg/sif/testdata/TestNewDescriptorInput/OptSBOMMetadata.golden
new file mode 100644
index 0000000..02516b1
--- /dev/null
+++ b/pkg/sif/testdata/TestNewDescriptorInput/OptSBOMMetadata.golden
Binary files differ
diff --git a/pkg/siftool/add.go b/pkg/siftool/add.go
index 941f9b0..75f11df 100644
--- a/pkg/siftool/add.go
+++ b/pkg/siftool/add.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2019-2021, Sylabs Inc. All rights reserved.
+// Copyright (c) 2019-2022, Sylabs Inc. All rights reserved.
// Copyright (c) 2017, SingularityWare, LLC. All rights reserved.
// Copyright (c) 2017, Yannick Cote <yhcote@gmail.com> All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the
@@ -27,6 +27,7 @@ var (
partArch *int32
signHash *int32
signEntity *string
+ sbomFormat *string
groupID *uint32
linkID *uint32
alignment *int
@@ -50,9 +51,9 @@ func getAddExamples(rootPath string) string {
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`)
+ 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,
@@ -66,7 +67,7 @@ func addFlags(fs *pflag.FlagSet) {
1-386, 2-amd64, 3-arm,
4-arm64, 5-ppc64, 6-ppc64le,
7-mips, 8-mipsle, 9-mips64,
- 10-mips64le, 11-s390x`)
+ 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,
@@ -74,9 +75,13 @@ func addFlags(fs *pflag.FlagSet) {
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 constraint [default: aligned on page size]")
+ 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]")
}
@@ -101,6 +106,8 @@ func getDataType() (sif.DataType, error) {
return sif.DataGeneric, nil
case 8:
return sif.DataCryptoMessage, nil
+ case 9:
+ return sif.DataSBOM, nil
default:
return 0, errDataTypeRequired
}
@@ -130,6 +137,8 @@ func getArch() string {
return "mips64le"
case 11:
return "s390x"
+ case 12:
+ return "riscv64"
default:
return "unknown"
}
@@ -154,9 +163,35 @@ func getHashType() (crypto.Hash, error) {
}
}
+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) {
@@ -180,7 +215,8 @@ func getOptions(dt sif.DataType, fs *pflag.FlagSet) ([]sif.DescriptorInputOpt, e
opts = append(opts, sif.OptObjectName(*name))
}
- if dt == sif.DataPartition {
+ switch dt {
+ case sif.DataPartition:
if *partType == 0 || *partFS == 0 || *partArch == 0 {
return nil, errPartitionArgs
}
@@ -188,9 +224,8 @@ func getOptions(dt sif.DataType, fs *pflag.FlagSet) ([]sif.DescriptorInputOpt, e
opts = append(opts,
sif.OptPartitionMetadata(sif.FSType(*partFS), sif.PartType(*partType), getArch()),
)
- }
- if dt == sif.DataSignature {
+ case sif.DataSignature:
b, err := hex.DecodeString(*signEntity)
if err != nil {
return nil, fmt.Errorf("failed to decode signing entity fingerprint: %w", err)
@@ -208,6 +243,18 @@ func getOptions(dt sif.DataType, fs *pflag.FlagSet) ([]sif.DescriptorInputOpt, e
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
diff --git a/pkg/siftool/add_test.go b/pkg/siftool/add_test.go
index 6d51a33..ccbb5b3 100644
--- a/pkg/siftool/add_test.go
+++ b/pkg/siftool/add_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021, Sylabs Inc. All rights reserved.
+// 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.
@@ -46,7 +46,7 @@ func Test_command_getAdd(t *testing.T) {
}
args = append(args, tt.flags...)
- runCommand(t, cmd, args)
+ runCommand(t, cmd, args, nil)
})
}
}
diff --git a/pkg/siftool/del_test.go b/pkg/siftool/del_test.go
index 13fad07..070d609 100644
--- a/pkg/siftool/del_test.go
+++ b/pkg/siftool/del_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021, Sylabs Inc. All rights reserved.
+// 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.
@@ -24,7 +24,7 @@ func Test_command_getDel(t *testing.T) {
cmd := c.getDel()
- runCommand(t, cmd, []string{"1", makeTestSIF(t, true)})
+ runCommand(t, cmd, []string{"1", makeTestSIF(t, true)}, nil)
})
}
}
diff --git a/pkg/siftool/dump_test.go b/pkg/siftool/dump_test.go
index 20362b8..fb13eb4 100644
--- a/pkg/siftool/dump_test.go
+++ b/pkg/siftool/dump_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021, Sylabs Inc. All rights reserved.
+// 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.
@@ -39,7 +39,7 @@ func Test_command_getDump(t *testing.T) {
cmd := c.getDump()
- runCommand(t, cmd, []string{tt.id, tt.path})
+ runCommand(t, cmd, []string{tt.id, tt.path}, nil)
})
}
}
diff --git a/pkg/siftool/header_test.go b/pkg/siftool/header_test.go
index 5c19498..7aeb615 100644
--- a/pkg/siftool/header_test.go
+++ b/pkg/siftool/header_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021, Sylabs Inc. All rights reserved.
+// 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.
@@ -68,7 +68,7 @@ func Test_command_getHeader(t *testing.T) {
cmd := c.getHeader()
- runCommand(t, cmd, []string{tt.path})
+ runCommand(t, cmd, []string{tt.path}, nil)
})
}
}
diff --git a/pkg/siftool/info_test.go b/pkg/siftool/info_test.go
index 87f3a46..963b00a 100644
--- a/pkg/siftool/info_test.go
+++ b/pkg/siftool/info_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021, Sylabs Inc. All rights reserved.
+// 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.
@@ -39,7 +39,7 @@ func Test_command_getInfo(t *testing.T) {
cmd := c.getInfo()
- runCommand(t, cmd, []string{tt.id, tt.path})
+ runCommand(t, cmd, []string{tt.id, tt.path}, nil)
})
}
}
diff --git a/pkg/siftool/list_test.go b/pkg/siftool/list_test.go
index b7c1d32..5a4a47b 100644
--- a/pkg/siftool/list_test.go
+++ b/pkg/siftool/list_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021, Sylabs Inc. All rights reserved.
+// 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.
@@ -68,7 +68,7 @@ func Test_command_getList(t *testing.T) {
cmd := c.getList()
- runCommand(t, cmd, []string{tt.path})
+ runCommand(t, cmd, []string{tt.path}, nil)
})
}
}
diff --git a/pkg/siftool/mount.go b/pkg/siftool/mount.go
new file mode 100644
index 0000000..6b59557
--- /dev/null
+++ b/pkg/siftool/mount.go
@@ -0,0 +1,27 @@
+// 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 <sif_path> <mount_path>",
+ 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
+ }
+}
diff --git a/pkg/siftool/mount_test.go b/pkg/siftool/mount_test.go
new file mode 100644
index 0000000..3b2848e
--- /dev/null
+++ b/pkg/siftool/mount_test.go
@@ -0,0 +1,61 @@
+// 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)
+ })
+ }
+}
diff --git a/pkg/siftool/new_test.go b/pkg/siftool/new_test.go
index af4cd75..81bcd58 100644
--- a/pkg/siftool/new_test.go
+++ b/pkg/siftool/new_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021, Sylabs Inc. All rights reserved.
+// 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.
@@ -32,7 +32,7 @@ func Test_command_getNew(t *testing.T) {
cmd := c.getNew()
- runCommand(t, cmd, []string{tf.Name()})
+ runCommand(t, cmd, []string{tf.Name()}, nil)
})
}
}
diff --git a/pkg/siftool/setprim_test.go b/pkg/siftool/setprim_test.go
index e37b240..13cd0e3 100644
--- a/pkg/siftool/setprim_test.go
+++ b/pkg/siftool/setprim_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021, Sylabs Inc. All rights reserved.
+// 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.
@@ -24,7 +24,7 @@ func Test_command_getSetPrim(t *testing.T) {
cmd := c.getSetPrim()
- runCommand(t, cmd, []string{"1", makeTestSIF(t, true)})
+ runCommand(t, cmd, []string{"1", makeTestSIF(t, true)}, nil)
})
}
}
diff --git a/pkg/siftool/siftool.go b/pkg/siftool/siftool.go
index 4e624f1..aa59f61 100644
--- a/pkg/siftool/siftool.go
+++ b/pkg/siftool/siftool.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2018-2021, Sylabs Inc. All rights reserved.
+// Copyright (c) 2018-2022, Sylabs Inc. All rights reserved.
// Copyright (c) 2017, SingularityWare, LLC. All rights reserved.
// Copyright (c) 2017, Yannick Cote <yhcote@gmail.com> All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the
@@ -23,6 +23,7 @@ type command struct {
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
@@ -31,12 +32,21 @@ func (c *command) initApp(cmd *cobra.Command, args []string) error {
// commandOpts contains configured options.
type commandOpts struct {
- rootPath string
+ 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
@@ -66,5 +76,10 @@ func AddCommands(cmd *cobra.Command, opts ...CommandOpt) error {
c.getSetPrim(),
)
+ if c.opts.experimental {
+ cmd.AddCommand(c.getMount())
+ cmd.AddCommand(c.getUnmount())
+ }
+
return nil
}
diff --git a/pkg/siftool/siftool_test.go b/pkg/siftool/siftool_test.go
index 5236254..606d6eb 100644
--- a/pkg/siftool/siftool_test.go
+++ b/pkg/siftool/siftool_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021, Sylabs Inc. All rights reserved.
+// 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.
@@ -6,6 +6,7 @@ package siftool
import (
"bytes"
+ "errors"
"os"
"path/filepath"
"testing"
@@ -47,7 +48,7 @@ func makeTestSIF(t *testing.T, withDataObject bool) string {
return tf.Name()
}
-func runCommand(t *testing.T, cmd *cobra.Command, args []string) {
+func runCommand(t *testing.T, cmd *cobra.Command, args []string, wantErr error) {
t.Helper()
var out, err bytes.Buffer
@@ -56,8 +57,8 @@ func runCommand(t *testing.T, cmd *cobra.Command, args []string) {
cmd.SetArgs(args)
- if err := cmd.Execute(); err != nil {
- t.Fatal(err)
+ if got, want := cmd.Execute(), wantErr; !errors.Is(got, want) {
+ t.Fatalf("got error %v, want %v", got, want)
}
g := goldie.New(t,
@@ -79,6 +80,11 @@ func TestAddCommands(t *testing.T) {
args: []string{"help"},
},
{
+ name: "SifToolExperimental",
+ opts: []CommandOpt{OptWithExperimental(true)},
+ args: []string{"help"},
+ },
+ {
name: "Add",
args: []string{"help", "add"},
},
@@ -110,6 +116,11 @@ func TestAddCommands(t *testing.T) {
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) {
@@ -121,7 +132,7 @@ func TestAddCommands(t *testing.T) {
t.Fatal(err)
}
- runCommand(t, cmd, tt.args)
+ runCommand(t, cmd, tt.args, nil)
})
}
}
diff --git a/pkg/siftool/testdata/TestAddCommands/Add/out.golden b/pkg/siftool/testdata/TestAddCommands/Add/out.golden
index 16c47e5..3f39697 100644
--- a/pkg/siftool/testdata/TestAddCommands/Add/out.golden
+++ b/pkg/siftool/testdata/TestAddCommands/Add/out.golden
@@ -9,12 +9,12 @@ siftool add image.sif rootfs.squashfs --datatype 4 --parttype 1 --partfs 1 ----p
siftool add image.sif signature.bin -datatype 5 --signentity 433FE984155206BD962725E20E8713472A879943 --signhash 1
Flags:
- --alignment int set alignment constraint [default: aligned on page size]
+ --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
+ 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
@@ -24,7 +24,7 @@ Flags:
1-386, 2-amd64, 3-arm,
4-arm64, 5-ppc64, 6-ppc64le,
7-mips, 8-mipsle, 9-mips64,
- 10-mips64le, 11-s390x
+ 10-mips64le, 11-s390x, 12-riscv64
--partfs int32 the filesystem used (with -datatype 4-Partition)
[NEEDED, no default]:
1-Squash, 2-Ext3, 3-ImmuObj,
@@ -33,6 +33,10 @@ Flags:
[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
diff --git a/pkg/siftool/testdata/TestAddCommands/Mount/err.golden b/pkg/siftool/testdata/TestAddCommands/Mount/err.golden
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/pkg/siftool/testdata/TestAddCommands/Mount/err.golden
diff --git a/pkg/siftool/testdata/TestAddCommands/Mount/out.golden b/pkg/siftool/testdata/TestAddCommands/Mount/out.golden
new file mode 100644
index 0000000..3df015d
--- /dev/null
+++ b/pkg/siftool/testdata/TestAddCommands/Mount/out.golden
@@ -0,0 +1,10 @@
+Mount the primary system partition of a SIF image
+
+Usage:
+ siftool mount <sif_path> <mount_path>
+
+Examples:
+siftool mount image.sif path/
+
+Flags:
+ -h, --help help for mount
diff --git a/pkg/siftool/testdata/TestAddCommands/SifToolExperimental/err.golden b/pkg/siftool/testdata/TestAddCommands/SifToolExperimental/err.golden
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/pkg/siftool/testdata/TestAddCommands/SifToolExperimental/err.golden
diff --git a/pkg/siftool/testdata/TestAddCommands/SifToolExperimental/out.golden b/pkg/siftool/testdata/TestAddCommands/SifToolExperimental/out.golden
new file mode 100644
index 0000000..2d8532f
--- /dev/null
+++ b/pkg/siftool/testdata/TestAddCommands/SifToolExperimental/out.golden
@@ -0,0 +1,19 @@
+Usage:
+ 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.
diff --git a/pkg/siftool/testdata/Test_command_getDump/Three/out.golden b/pkg/siftool/testdata/Test_command_getDump/Three/out.golden
index 0299536..4e354d9 100644
--- a/pkg/siftool/testdata/Test_command_getDump/Three/out.golden
+++ b/pkg/siftool/testdata/Test_command_getDump/Three/out.golden
@@ -1,15 +1,15 @@
-----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:db74cb63348414def73535c9f0f83e8ad7df61229ed2806f4da8b69d6d7464d6","objectDigest":"sha256:5f78c33274e43fa9de5659265c1d917e25c03722dcb0b8d27db8d5feaa813953"}]}
+{"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/e6hBahBBIEXIwLEATQWN5L7aIMJ+5/97qE
-AABQ4QgAkWcLNLcghZ96VnJ9+67qbsdwp51rfERCKN0dZLBTHKN5Qjn1BWM/XbPj
-Qnl0F6D6YBId7c/KO0sbb3EHUdpmMEQlouQYFOTHWtdyvwO6spRLBx5EQA7Iv0rF
-jREz/jC7GaREK94u+hXRr94+FH5gEnHUL+Vg7pW/+cGiwLY1ddoL8ELgYhxqxd9J
-sET+vU1E4GJ3TyYFhVFsMsNeW7dQauqjQSJxMLTwXNphxTH19ePbJ2uDE2UJ3fn7
-up5ruugRyEe5qgRICGxRSDp8/INGRvoDUi32T9uLORzS+umRX5YW0b6RWD+5R72V
-0ewbMTJIx2lpfQGPMWROwcF7nkLdWQ==
-=WWGX
+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----- \ No newline at end of file
diff --git a/pkg/siftool/testdata/Test_command_getDump/Two/out.golden b/pkg/siftool/testdata/Test_command_getDump/Two/out.golden
index 7d174b1..cf6539a 100644
--- a/pkg/siftool/testdata/Test_command_getDump/Two/out.golden
+++ b/pkg/siftool/testdata/Test_command_getDump/Two/out.golden
Binary files differ
diff --git a/pkg/siftool/testdata/Test_command_getHeader/OneGroup/out.golden b/pkg/siftool/testdata/Test_command_getHeader/OneGroup/out.golden
index bc631d6..a2941c1 100644
--- a/pkg/siftool/testdata/Test_command_getHeader/OneGroup/out.golden
+++ b/pkg/siftool/testdata/Test_command_getHeader/OneGroup/out.golden
@@ -5,4 +5,4 @@ Descriptors Total: 48
Descriptors Offset: 4096
Descriptors Size: 27 KiB
Data Offset: 32176
-Data Size: 5 KiB
+Data Size: 9 KiB
diff --git a/pkg/siftool/testdata/Test_command_getHeader/TwoGroups/out.golden b/pkg/siftool/testdata/Test_command_getHeader/TwoGroups/out.golden
index 0db18f0..c16ef18 100644
--- a/pkg/siftool/testdata/Test_command_getHeader/TwoGroups/out.golden
+++ b/pkg/siftool/testdata/Test_command_getHeader/TwoGroups/out.golden
@@ -5,4 +5,4 @@ Descriptors Total: 48
Descriptors Offset: 4096
Descriptors Size: 27 KiB
Data Offset: 32176
-Data Size: 9 KiB
+Data Size: 265 KiB
diff --git a/pkg/siftool/testdata/Test_command_getHeader/TwoGroupsSigned/out.golden b/pkg/siftool/testdata/Test_command_getHeader/TwoGroupsSigned/out.golden
index acf8b17..44854c9 100644
--- a/pkg/siftool/testdata/Test_command_getHeader/TwoGroupsSigned/out.golden
+++ b/pkg/siftool/testdata/Test_command_getHeader/TwoGroupsSigned/out.golden
@@ -5,4 +5,4 @@ Descriptors Total: 48
Descriptors Offset: 4096
Descriptors Size: 27 KiB
Data Offset: 32176
-Data Size: 17 KiB
+Data Size: 266 KiB
diff --git a/pkg/siftool/testdata/Test_command_getInfo/Two/out.golden b/pkg/siftool/testdata/Test_command_getInfo/Two/out.golden
index f34651f..f49a554 100644
--- a/pkg/siftool/testdata/Test_command_getInfo/Two/out.golden
+++ b/pkg/siftool/testdata/Test_command_getInfo/Two/out.golden
@@ -3,7 +3,7 @@
Group ID: 1
Linked ID: NONE
Offset: 36864
- Size: 4
+ Size: 4096
Filesystem Type: Squashfs
Partition Type: *System
Architecture: 386
diff --git a/pkg/siftool/testdata/Test_command_getList/OneGroup/out.golden b/pkg/siftool/testdata/Test_command_getList/OneGroup/out.golden
index fe78f62..01400f9 100644
--- a/pkg/siftool/testdata/Test_command_getList/OneGroup/out.golden
+++ b/pkg/siftool/testdata/Test_command_getList/OneGroup/out.golden
@@ -2,4 +2,4 @@
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)
+2 |1 |NONE |36864-40960 |FS (Squashfs/*System/386)
diff --git a/pkg/siftool/testdata/Test_command_getList/OneGroupSigned/out.golden b/pkg/siftool/testdata/Test_command_getList/OneGroupSigned/out.golden
index 98030e3..5b663d3 100644
--- a/pkg/siftool/testdata/Test_command_getList/OneGroupSigned/out.golden
+++ b/pkg/siftool/testdata/Test_command_getList/OneGroupSigned/out.golden
@@ -2,5 +2,5 @@
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)
+2 |1 |NONE |36864-40960 |FS (Squashfs/*System/386)
3 |NONE |1 (G) |40960-42014 |Signature (SHA-256)
diff --git a/pkg/siftool/testdata/Test_command_getList/TwoGroups/out.golden b/pkg/siftool/testdata/Test_command_getList/TwoGroups/out.golden
index 648c280..1eca2ab 100644
--- a/pkg/siftool/testdata/Test_command_getList/TwoGroups/out.golden
+++ b/pkg/siftool/testdata/Test_command_getList/TwoGroups/out.golden
@@ -2,5 +2,5 @@
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)
+2 |1 |NONE |36864-40960 |FS (Squashfs/*System/386)
+3 |2 |NONE |40960-303104 |FS (Ext3/System/amd64)
diff --git a/pkg/siftool/testdata/Test_command_getList/TwoGroupsSigned/out.golden b/pkg/siftool/testdata/Test_command_getList/TwoGroupsSigned/out.golden
index f21bf6d..17240ba 100644
--- a/pkg/siftool/testdata/Test_command_getList/TwoGroupsSigned/out.golden
+++ b/pkg/siftool/testdata/Test_command_getList/TwoGroupsSigned/out.golden
@@ -2,7 +2,7 @@
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-46110 |Signature (SHA-256)
-5 |NONE |2 (G) |49152-50007 |Signature (SHA-256)
+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)
diff --git a/pkg/siftool/testdata/Test_command_getMount/Empty/err.golden b/pkg/siftool/testdata/Test_command_getMount/Empty/err.golden
new file mode 100644
index 0000000..cd860b8
--- /dev/null
+++ b/pkg/siftool/testdata/Test_command_getMount/Empty/err.golden
@@ -0,0 +1 @@
+Error: failed to get partition descriptor: no objects in image
diff --git a/pkg/siftool/testdata/Test_command_getMount/Empty/out.golden b/pkg/siftool/testdata/Test_command_getMount/Empty/out.golden
new file mode 100644
index 0000000..f22522a
--- /dev/null
+++ b/pkg/siftool/testdata/Test_command_getMount/Empty/out.golden
@@ -0,0 +1,9 @@
+Usage:
+ mount <sif_path> <mount_path>
+
+Examples:
+ mount image.sif path/
+
+Flags:
+ -h, --help help for mount
+
diff --git a/pkg/siftool/testdata/Test_command_getMount/OneGroup/err.golden b/pkg/siftool/testdata/Test_command_getMount/OneGroup/err.golden
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/pkg/siftool/testdata/Test_command_getMount/OneGroup/err.golden
diff --git a/pkg/siftool/testdata/Test_command_getMount/OneGroup/out.golden b/pkg/siftool/testdata/Test_command_getMount/OneGroup/out.golden
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/pkg/siftool/testdata/Test_command_getMount/OneGroup/out.golden
diff --git a/pkg/siftool/testdata/Test_command_getUnmount/err.golden b/pkg/siftool/testdata/Test_command_getUnmount/err.golden
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/pkg/siftool/testdata/Test_command_getUnmount/err.golden
diff --git a/pkg/siftool/testdata/Test_command_getUnmount/out.golden b/pkg/siftool/testdata/Test_command_getUnmount/out.golden
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/pkg/siftool/testdata/Test_command_getUnmount/out.golden
diff --git a/pkg/siftool/unmount.go b/pkg/siftool/unmount.go
new file mode 100644
index 0000000..8814182
--- /dev/null
+++ b/pkg/siftool/unmount.go
@@ -0,0 +1,27 @@
+// 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 <mount_path>",
+ 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
+ }
+}
diff --git a/pkg/siftool/unmount_test.go b/pkg/siftool/unmount_test.go
new file mode 100644
index 0000000..2cf8160
--- /dev/null
+++ b/pkg/siftool/unmount_test.go
@@ -0,0 +1,42 @@
+// 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)
+}
diff --git a/pkg/user/mount.go b/pkg/user/mount.go
new file mode 100644
index 0000000..c96e414
--- /dev/null
+++ b/pkg/user/mount.go
@@ -0,0 +1,122 @@
+// 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
+ }
+}
diff --git a/pkg/user/unmount.go b/pkg/user/unmount.go
new file mode 100644
index 0000000..609abf5
--- /dev/null
+++ b/pkg/user/unmount.go
@@ -0,0 +1,93 @@
+// 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)
+}
diff --git a/pkg/user/unmount_test.go b/pkg/user/unmount_test.go
new file mode 100644
index 0000000..e24baf0
--- /dev/null
+++ b/pkg/user/unmount_test.go
@@ -0,0 +1,139 @@
+// 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
+}
diff --git a/test/gen_sifs.go b/test/gen_sifs.go
index 487ea5b..7395951 100755
--- a/test/gen_sifs.go
+++ b/test/gen_sifs.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2020-2021, Sylabs Inc. All rights reserved.
+// 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.
@@ -58,6 +58,17 @@ func generateImages() error {
)
}
+ 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}),
@@ -66,15 +77,23 @@ func generateImages() error {
}
partPrimSys := func() (sif.DescriptorInput, error) {
- return sif.NewDescriptorInput(sif.DataPartition,
- bytes.NewReader([]byte{0xde, 0xad, 0xbe, 0xef}),
+ 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) {
- return sif.NewDescriptorInput(sif.DataPartition,
- bytes.NewReader([]byte{0xba, 0xdd, 0xca, 0xfe}),
+ 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),
)
@@ -125,6 +144,12 @@ func generateImages() error {
objectCryptoMessage,
},
},
+ {
+ path: "one-object-sbom.sif",
+ diFns: []func() (sif.DescriptorInput, error){
+ objectSBOM,
+ },
+ },
// Images with two partitions in one group.
{
diff --git a/test/images/one-group-signed.sif b/test/images/one-group-signed.sif
index 83e9667..f7c6894 100755
--- a/test/images/one-group-signed.sif
+++ b/test/images/one-group-signed.sif
Binary files differ
diff --git a/test/images/one-group.sif b/test/images/one-group.sif
index ea68b45..fb72e4a 100755
--- a/test/images/one-group.sif
+++ b/test/images/one-group.sif
Binary files differ
diff --git a/test/images/one-object-crypt-message.sif b/test/images/one-object-crypt-message.sif
index 21f0670..27e2aed 100755
--- a/test/images/one-object-crypt-message.sif
+++ b/test/images/one-object-crypt-message.sif
Binary files differ
diff --git a/test/images/one-object-generic-json.sif b/test/images/one-object-generic-json.sif
index 5ef1418..2f08067 100755
--- a/test/images/one-object-generic-json.sif
+++ b/test/images/one-object-generic-json.sif
Binary files differ
diff --git a/test/images/one-object-sbom.sif b/test/images/one-object-sbom.sif
new file mode 100755
index 0000000..339cde2
--- /dev/null
+++ b/test/images/one-object-sbom.sif
Binary files differ
diff --git a/test/images/one-object-time.sif b/test/images/one-object-time.sif
index e7d8205..a651be6 100755
--- a/test/images/one-object-time.sif
+++ b/test/images/one-object-time.sif
Binary files differ
diff --git a/test/images/two-groups-signed.sif b/test/images/two-groups-signed.sif
index 7ee18b3..590c44a 100755
--- a/test/images/two-groups-signed.sif
+++ b/test/images/two-groups-signed.sif
Binary files differ
diff --git a/test/images/two-groups.sif b/test/images/two-groups.sif
index 0555d09..36701ad 100755
--- a/test/images/two-groups.sif
+++ b/test/images/two-groups.sif
Binary files differ
diff --git a/test/input/root.ext3 b/test/input/root.ext3
new file mode 100644
index 0000000..6163e60
--- /dev/null
+++ b/test/input/root.ext3
Binary files differ
diff --git a/test/input/root.squashfs b/test/input/root.squashfs
new file mode 100644
index 0000000..cf6539a
--- /dev/null
+++ b/test/input/root.squashfs
Binary files differ
diff --git a/test/input/sbom.cdx.json b/test/input/sbom.cdx.json
new file mode 100644
index 0000000..e4e722a
--- /dev/null
+++ b/test/input/sbom.cdx.json
@@ -0,0 +1,22 @@
+{
+ "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": []
+}
diff --git a/test/keys/private.asc b/test/keys/private.asc
index 0cb933f..f7a8d4a 100644
--- a/test/keys/private.asc
+++ b/test/keys/private.asc
@@ -19,39 +19,39 @@ r4kaytnGsMJ+iKbZ8WHI52aCcX6cuRBeOget2EbwicU1NOnFpP7YiE+G8SXq73LZ
1es1dK4jk2oBwHUBMLOz+ZVkPvnrXSkD/2xo+U441tM1+w+9njSsS4huaobqKgvx
OKy4PpWh25IjOd3ODdrKpLeR5DHdtvl0b2ph1tOTgnOrDF3lCQ9y50f46JDY+Usm
/0hV9Vg7bXS6hpW36M+StuxbxNFoIcJOaStkWnOaysKQQfQ7qKu1v3yXu5woGlmM
-q8fAGOQ9zkOhStS0GVVuaXQgVGVzdCA8dW5pdEB0ZXN0LmNvbT6JAVQEEwEIAD4W
-IQQSBFyMCxAE0FjeS+2iDCfuf/e6hAUCXqdQ8AIbAwUJA8JnAAULCQgHAgYVCgkI
-CwIEFgIDAQIeAQIXgAAKCRCiDCfuf/e6hCl+CACALh9bNfdpmyvq8cm1/wayb9fC
-VVPuJ6Hi+5FGhSwxPyYJZmA2QTSu0yaXXUiRKoNuRJ89WzPTsK2zY5c0YSZH3dSj
-Ggzg5VpQn56RgemeZ0Fn+sPPbob57lOiiThx66yRg5AvYazpBwacowai6asiwTpR
-oO242zxLKodqnhisJUZC3OC0/Mm4Fu7+R3J90qWY45Ti1YJd892JKJAsSOPU+Yb7
-jyYyYcg2B6xkkHdai6z4EQBbSpGK5nBrTxJY7FIY5baY+FCDbygOahkj10y1xNjt
-h9gt8w9MnZL5u0Zdp2+kb1aow+bAVcYzYJVjBUV2e/+esoIXvpLIcBHvRy8FnQOY
-BF6nUPABCAC/yLh6jYYFrWwQp0NQJtBXsw2iK2TJ42mZdtCUeRmr82eBui+JoiCJ
-VleQNr5Oe+JFbIeI6VwxR+n8ct5jDHOP5skjVAhzPNZ7jwrrVlZbeW/BVnILEUuo
-6CiqJY3FCIuOncX5IAH/0jyDRkz50rFqPAAODyV5TTFCViBdtAYZZ3r4pqg5z7a4
-CRZmn/+Ao3/27opAgt96VUkIqIQLIukiquS7ZSLcJrJxxS6QjDcy0gswdLbenG9F
-XtwEcUK2Jdc8IAq5WVkzE4xOcgE9JeV9L2/449MStZm/nkzFteutPWc9PpTXSDWu
-+H4U9+WoZW5OwINRe9VpNVv7UlxW80VpABEBAAEAB/wJ5Hwjki5CF7Z1y3Ls7Pud
-Mna3ET7zLQBS8q6CohaBaJ5DsktmcY71FpeQsEozuS8sPpNlLAhd4GRA6dnvyQIi
-/5gLcve2ngJAQFojVoJA2Kw7kE50pLE+5q7GTAaajbzJH/lIxu5jeEA300YAMu6E
-2NB16TEZJzKtxcyImNMhtz434EXvPp01T8NxukBKYjfJ6cZ1hFIahFoZGZ9GemFG
-x2FRfM0CU/yXIYTwXUBkLaE8U6bx1cU7ujaBuu/uMN0FA+37UHBSQWh+9yiDQ0+I
-q7D3WkjTAgGdQ/GAhTDVtt9Y7h4cozNvlSS/VrEyH0nJbPMCbEDoB65yim0/+N+J
-BADD7u0+ajZtyW5JpE1eyucn2VDg1m34QZU0/h5bBnMy6P1zhRO/8fqIKta00y9i
-Y7PAvYtFivY18zrmNYmaQFAe6Cawm9/Qb2db7sbex5tMXfqKB0pItl05RKUK3BRM
-69iLYg76jtPSZerfSJJVGhpQmFJxa3EdcumzEyiUnm4bPQQA+pQoESGvSZ6GJPu/
-fGz/duOtw4TBNtMWlt2dtSVtWru6r/aVWlkAXMdvNWdSGKrEXqvxo39xIhKIW742
-BlqWlZ2fsJQb/9UmBFjhDg5poj+jpATQ2MFhOlSc4un+0KE7R3sz6n98S8AN76PP
-lPZOwgbzBCAub/+kMCQcnog75Z0EAJdYFYs/gUnvOmfoKZisIn8OZ6aa6LWqWhFC
-SgNU03I6+wWyEjJsbcBRBXEpuGfeVfAUDJSVSv5lJg2fOU0p3ephoiZSORaCZovX
-TPQgTgO0afcSUP5g8o/Tjp1QPETBd/Rd/Br5WBdfoF91ZUhblvs04qb+lswvJXYZ
-6caeot1EPByJATwEGAEIACYWIQQSBFyMCxAE0FjeS+2iDCfuf/e6hAUCXqdQ8AIb
-DAUJA8JnAAAKCRCiDCfuf/e6hO8qB/98f5Lb7FZY+g9LNE3BVpcc2tXPz7p7rVP7
-Kp6Om0r5aHRANl86E/oEKy/dBg/PgOoZ3WBidvhgldohKnwgLNJuzD7rAWgtWGIA
-O/PJHUEZ7KFlSe2Dh/p02s9+rU6fo8FsMRDXO34ttZLs6mTltFl9hsRO4BJr6JXE
-vWVPqRiFh1FguCrOoR15kS/FCKTSVVgg9OyTYql184vKmq266//lrPSH200/7f1d
-rhwQo7Rrnee5dPNefAsruppLqAt6XyI7k7NBl4XCXP4TA8sSbrtnArlxV1Z+F5/o
-zBMHk8OzwVlDXpW5DRXuRs8+F0z9qSsfBg8RNELESuys0UR+KKNa
-=zxMk
+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-----