diff --git a/cmd/skopeo/main.go b/cmd/skopeo/main.go index 75874054..51f918de 100644 --- a/cmd/skopeo/main.go +++ b/cmd/skopeo/main.go @@ -64,6 +64,7 @@ func createApp() *cli.App { manifestDigestCmd, standaloneSignCmd, standaloneVerifyCmd, + untrustedSignatureDumpCmd, } return app } diff --git a/cmd/skopeo/signing.go b/cmd/skopeo/signing.go index 3131e01f..1cd00439 100644 --- a/cmd/skopeo/signing.go +++ b/cmd/skopeo/signing.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "errors" "fmt" "io/ioutil" @@ -88,3 +89,40 @@ var standaloneVerifyCmd = cli.Command{ ArgsUsage: "MANIFEST DOCKER-REFERENCE KEY-FINGERPRINT SIGNATURE", Action: standaloneVerify, } + +func untrustedSignatureDump(context *cli.Context) error { + if len(context.Args()) != 1 { + return errors.New("Usage: skopeo untrusted-signature-dump-without-verification signature") + } + untrustedSignaturePath := context.Args()[0] + + untrustedSignature, err := ioutil.ReadFile(untrustedSignaturePath) + if err != nil { + return fmt.Errorf("Error reading untrusted signature from %s: %v", untrustedSignaturePath, err) + } + + untrustedInfo, err := signature.GetUntrustedSignatureInformationWithoutVerifying(untrustedSignature) + if err != nil { + return fmt.Errorf("Error decoding untrusted signature: %v", err) + } + untrustedOut, err := json.MarshalIndent(untrustedInfo, "", " ") + if err != nil { + return err + } + fmt.Fprintln(context.App.Writer, string(untrustedOut)) + return nil +} + +// WARNING: Do not use the contents of this for ANY security decisions, +// and be VERY CAREFUL about showing this information to humans in any way which suggest that these values “are probably” reliable. +// There is NO REASON to expect the values to be correct, or not intentionally misleading +// (including things like “✅ Verified by $authority”) +// +// The subcommand is undocumented, and it may be renamed or entirely disappear in the future. +var untrustedSignatureDumpCmd = cli.Command{ + Name: "untrusted-signature-dump-without-verification", + Usage: "Dump contents of a signature WITHOUT VERIFYING IT", + ArgsUsage: "SIGNATURE", + Hidden: true, + Action: untrustedSignatureDump, +} diff --git a/cmd/skopeo/signing_test.go b/cmd/skopeo/signing_test.go index fee0f6a5..814dec4a 100644 --- a/cmd/skopeo/signing_test.go +++ b/cmd/skopeo/signing_test.go @@ -1,9 +1,11 @@ package main import ( + "encoding/json" "io/ioutil" "os" "testing" + "time" "github.com/containers/image/signature" "github.com/opencontainers/go-digest" @@ -16,6 +18,8 @@ const ( fixturesTestImageManifestDigest = digest.Digest("sha256:20bf21ed457b390829cdbeec8795a7bea1626991fda603e0d01b4e7f60427e55") // fixturesTestKeyFingerprint is the fingerprint of the private key. fixturesTestKeyFingerprint = "1D8230F6CDB6A06716E414C1DB72F2188BB46CC8" + // fixturesTestKeyFingerprint is the key ID of the private key. + fixturesTestKeyShortID = "DB72F2188BB46CC8" ) // Test that results of runSkopeo failed with nothing on stdout, and substring @@ -125,3 +129,42 @@ func TestStandaloneVerify(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "Signature verified, digest "+fixturesTestImageManifestDigest.String()+"\n", out) } + +func TestUntrustedSignatureDump(t *testing.T) { + // Invalid command-line arguments + for _, args := range [][]string{ + {}, + {"a1", "a2"}, + {"a1", "a2", "a3", "a4"}, + } { + out, err := runSkopeo(append([]string{"untrusted-signature-dump-without-verification"}, args...)...) + assertTestFailed(t, out, err, "Usage") + } + + // Error reading manifest + out, err := runSkopeo("untrusted-signature-dump-without-verification", + "/this/doesnt/exist") + assertTestFailed(t, out, err, "/this/doesnt/exist") + + // Error reading signature (input is not a signature) + out, err = runSkopeo("untrusted-signature-dump-without-verification", "fixtures/image.manifest.json") + assertTestFailed(t, out, err, "Error decoding untrusted signature") + + // Success + for _, path := range []string{"fixtures/image.signature", "fixtures/corrupt.signature"} { + // Success + out, err = runSkopeo("untrusted-signature-dump-without-verification", path) + require.NoError(t, err) + + var info signature.UntrustedSignatureInformation + err := json.Unmarshal([]byte(out), &info) + require.NoError(t, err) + assert.Equal(t, fixturesTestImageManifestDigest, info.UntrustedDockerManifestDigest) + assert.Equal(t, "testing/manifest", info.UntrustedDockerReference) + assert.NotNil(t, info.UntrustedCreatorID) + assert.Equal(t, "atomic ", *info.UntrustedCreatorID) + assert.NotNil(t, info.UntrustedTimestamp) + assert.True(t, time.Unix(1458239713, 0).Equal(*info.UntrustedTimestamp)) + assert.Equal(t, fixturesTestKeyShortID, info.UntrustedShortKeyIdentifier) + } +}