Skip to content

Commit

Permalink
feat: support cose (notaryproject#365)
Browse files Browse the repository at this point in the history
Add cose envelope support
Signed-off-by: zaihaoyin <[email protected]>
  • Loading branch information
chloeyin authored Oct 12, 2022
1 parent 4666267 commit 947e55c
Show file tree
Hide file tree
Showing 11 changed files with 209 additions and 42 deletions.
13 changes: 11 additions & 2 deletions cmd/notation/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/notaryproject/notation-go"
"github.com/notaryproject/notation-go/dir"
"github.com/notaryproject/notation/internal/envelope"
"github.com/notaryproject/notation/pkg/cache"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -70,7 +71,11 @@ func runPush(command *cobra.Command, opts *pushOpts) error {
return err
}
// pass in nonempty annotations if needed
sigDesc, _, err := sigRepo.PutSignatureManifest(command.Context(), sig, manifestDesc, make(map[string]string))
sigMediaType, err := envelope.SpeculateSignatureEnvelopeFormat(sig)
if err != nil {
return err
}
sigDesc, _, err := sigRepo.PutSignatureManifest(command.Context(), sig, sigMediaType, manifestDesc, make(map[string]string))
if err != nil {
return fmt.Errorf("put signature manifest failure: %v", err)
}
Expand All @@ -95,7 +100,11 @@ func pushSignature(ctx context.Context, opts *SecureFlagOpts, ref string, sig []

// core process
// pass in nonempty annotations if needed
sigDesc, _, err := sigRepo.PutSignatureManifest(ctx, sig, manifestDesc, make(map[string]string))
sigMediaType, err := envelope.SpeculateSignatureEnvelopeFormat(sig)
if err != nil {
return notation.Descriptor{}, err
}
sigDesc, _, err := sigRepo.PutSignatureManifest(ctx, sig, sigMediaType, manifestDesc, make(map[string]string))
if err != nil {
return notation.Descriptor{}, fmt.Errorf("put signature manifest failure: %v", err)
}
Expand Down
24 changes: 15 additions & 9 deletions cmd/notation/sign_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"time"

"github.com/notaryproject/notation/internal/cmd"
"github.com/notaryproject/notation/internal/envelope"
)

func TestSignCommand_BasicArgs(t *testing.T) {
Expand All @@ -23,9 +24,10 @@ func TestSignCommand_BasicArgs(t *testing.T) {
},
},
SignerFlagOpts: cmd.SignerFlagOpts{
Key: "key",
KeyFile: "keyfile",
CertFile: "certfile",
Key: "key",
KeyFile: "keyfile",
CertFile: "certfile",
EnvelopeType: envelope.JWS,
},
push: true,
}
Expand Down Expand Up @@ -63,9 +65,10 @@ func TestSignCommand_MoreArgs(t *testing.T) {
},
},
SignerFlagOpts: cmd.SignerFlagOpts{
Key: "key",
KeyFile: "keyfile",
CertFile: "certfile",
Key: "key",
KeyFile: "keyfile",
CertFile: "certfile",
EnvelopeType: envelope.COSE,
},
output: "outputfile",
push: false,
Expand All @@ -82,6 +85,7 @@ func TestSignCommand_MoreArgs(t *testing.T) {
"--push=false",
"--media-type", expected.MediaType,
"-l",
"--envelope-type", expected.SignerFlagOpts.EnvelopeType,
"--output", expected.output,
"--expiry", expected.expiry.String()}); err != nil {
t.Fatalf("Parse Flag failed: %v", err)
Expand All @@ -106,9 +110,10 @@ func TestSignCommand_CorrectConfig(t *testing.T) {
},
},
SignerFlagOpts: cmd.SignerFlagOpts{
Key: "key",
KeyFile: "keyfile",
CertFile: "certfile",
Key: "key",
KeyFile: "keyfile",
CertFile: "certfile",
EnvelopeType: envelope.JWS,
},
push: true,
expiry: 365 * 24 * time.Hour,
Expand All @@ -126,6 +131,7 @@ func TestSignCommand_CorrectConfig(t *testing.T) {
"--push-reference", expected.pushReference,
"-r", expected.originReference,
"--local",
"--envelope-type", expected.SignerFlagOpts.EnvelopeType,
"--expiry", expected.expiry.String(),
"--pluginConfig", expected.pluginConfig}); err != nil {
t.Fatalf("Parse Flag failed: %v", err)
Expand Down
19 changes: 13 additions & 6 deletions cmd/notation/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@ import (
"fmt"
"os"

"github.com/notaryproject/notation-core-go/signature/jws"
"github.com/notaryproject/notation-go"
"github.com/notaryproject/notation-go/dir"
"github.com/notaryproject/notation-go/signature"
"github.com/notaryproject/notation/internal/cmd"
"github.com/notaryproject/notation/internal/envelope"
"github.com/notaryproject/notation/internal/slices"
"github.com/notaryproject/notation/pkg/cache"
"github.com/notaryproject/notation/pkg/configutil"
"github.com/opencontainers/go-digest"

"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -96,15 +97,21 @@ func verifySignatures(ctx context.Context, verifier notation.Verifier, manifestD
return errors.New("verification failure: no signatures found")
}

// TODO: support cose media type
opts := notation.VerifyOptions{
SignatureMediaType: jws.MediaTypeEnvelope,
}
var lastErr error
for _, path := range sigPaths {
sig, err := os.ReadFile(path)
if err != nil {
return err
lastErr = fmt.Errorf("verification failure: %v", err)
continue
}
// pass in nonempty annotations if needed
sigMediaType, err := envelope.SpeculateSignatureEnvelopeFormat(sig)
if err != nil {
lastErr = fmt.Errorf("verification failure: %v", err)
continue
}
opts := notation.VerifyOptions{
SignatureMediaType: sigMediaType,
}
desc, err := verifier.Verify(ctx, sig, opts)
if err != nil {
Expand Down
27 changes: 16 additions & 11 deletions docs/hello-signing.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,48 +72,53 @@ ACME Rockets will only deploy software that's been scanned and approved by the A
- List the image, and any associated signatures

```bash
notation list --plain-http $IMAGE
notation list $IMAGE
```

At this point, the results are empty, as there are no existing signatures

## Signing a Container Image

To get things started quickly, the Notation cli supports generating self signed certificates. As you automate the signing of content, you will most likely want to create and store the private keys in a key vault. (Detailed production steps will be covered later)
To get things started quickly, the Notation cli supports self-generated signing certificate. As you automate the signing of content, you will most likely want to create and store the private keys in a key vault. (Detailed production steps will be covered later)

- Generate a self-signed test certificate for signing artifacts
The following will generate a self-signed X.509 certificate under the `~/config/notation/` directory
- Create a self-signed test certificate for signing artifacts
The following will create a self-signed X.509 certificate under the `~/.config/notation/localkeys` directory along with a private key.

```bash
notation cert generate-test --default "wabbit-networks.io"
```

- Sign the container image
- Sign the container image. By default, JWS envelope is used.

```bash
notation sign --plain-http $IMAGE
notation sign $IMAGE
```
To sign with COSE envelope
```bash
notation sign --envelope-type cose $IMAGE
```

- List the image, and any associated signatures

```bash
notation list --plain-http $IMAGE
notation list $IMAGE
```

## Verify a Container Image Using Notation Signatures

To avoid a Trojan Horse attack, and before pulling an artifact into an environment, it is important to verify that the artifact was unmodified after it was created (integrity), and from an trusted entity (authenticity). Notation uses a set of configured public keys that represent trusted entities, to verify the content. The `notation cert generate-test` command created the public key, however it must be implicitly added for verification to succeed.
To avoid a Trojan Horse attack, and before pulling an artifact into an environment, it is important to verify that the artifact was unmodified after it was created (integrity), and from an trusted entity (authenticity). Notation uses a set of configured public keys that represent trusted entities, to verify the content. The `notation cert generate-test` command created the public key, however it must be explicitly added for verification to succeed.

- Attempt to verify the $IMAGE notation signature

```bash
notation verify --plain-http $IMAGE
notation verify $IMAGE
```

*The above verification should fail, as you haven't yet configured the keys to trust.*

```bash
2021/09/07 11:40:51 trust certificate not specified
Error: trust certificate not specified
2022/08/31 10:24:19 trust certificate not specified
```

- To assure users opt-into the public keys they trust, add the key to the trusted store
Expand All @@ -125,7 +130,7 @@ To avoid a Trojan Horse attack, and before pulling an artifact into an environme
- Verify the `net-monitor:v1` notation signature

```bash
notation verify --plain-http $IMAGE
notation verify $IMAGE
```

This should now succeed because the image is signed with a trusted public key
Expand Down
7 changes: 5 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,23 @@ go 1.19
require (
github.com/distribution/distribution/v3 v3.0.0-20220729163034-26163d82560f
github.com/docker/docker-credential-helpers v0.7.0
github.com/notaryproject/notation-core-go v0.1.0-alpha.3.0.20220921054535-009c09a9628e
github.com/notaryproject/notation-go v0.10.0-alpha.3.0.20220927020950-2bcfd343f974
github.com/notaryproject/notation-core-go v0.1.0-alpha.4
github.com/notaryproject/notation-go v0.10.0-alpha.3.0.20221009143310-e2ae1fecf077
github.com/opencontainers/go-digest v1.0.0
github.com/spf13/cobra v1.5.0
github.com/spf13/pflag v1.0.5
github.com/veraison/go-cose v1.0.0-rc.1.0.20220824135457-9d2fab636b83
oras.land/oras-go/v2 v2.0.0-rc.3
)

require (
github.com/fxamacker/cbor/v2 v2.4.0 // indirect
github.com/golang-jwt/jwt/v4 v4.4.2 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/opencontainers/distribution-spec/specs-go v0.0.0-20220620172159-4ab4752c3b86 // indirect
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect
github.com/oras-project/artifacts-spec v1.0.0-rc.2 // indirect
github.com/x448/float16 v0.8.4 // indirect
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 // indirect
)
14 changes: 10 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ github.com/distribution/distribution/v3 v3.0.0-20220729163034-26163d82560f h1:3N
github.com/distribution/distribution/v3 v3.0.0-20220729163034-26163d82560f/go.mod h1:28YO/VJk9/64+sTGNuYaBjWxrXTPrj0C0XmgTIOjxX4=
github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A=
github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0=
github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88=
github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs=
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/notaryproject/notation-core-go v0.1.0-alpha.3.0.20220921054535-009c09a9628e h1:n3wJRhIVbEGg497rtKV3IMaZJv2hFKYHCOtNIOAyLYw=
github.com/notaryproject/notation-core-go v0.1.0-alpha.3.0.20220921054535-009c09a9628e/go.mod h1:mM4M9wPdu0CGgh8f3wOcu0XMiXwEKWQurjBG4nmqQ4g=
github.com/notaryproject/notation-go v0.10.0-alpha.3.0.20220927020950-2bcfd343f974 h1:aJ5p4zydKHoyXVK62H50fmj5czcxXpSG5a24EgoZH5E=
github.com/notaryproject/notation-go v0.10.0-alpha.3.0.20220927020950-2bcfd343f974/go.mod h1:TeQIoxMetPFqJSDzcHnGZ6x9kKzglfSmgxYrWt9/viA=
github.com/notaryproject/notation-core-go v0.1.0-alpha.4 h1:0OhA2PjwT0TAouHOrU4K+8H9YM6E/e4/ocoq+JiHeOw=
github.com/notaryproject/notation-core-go v0.1.0-alpha.4/go.mod h1:s8DZptmN1rZS0tBLTPt/w+d4o6eAcGWTYYJlXaJhQ4U=
github.com/notaryproject/notation-go v0.10.0-alpha.3.0.20221009143310-e2ae1fecf077 h1:LshczxRKk5d/5ZOVr/qG0aZr9bu6CVYmdLgSTzTGG8w=
github.com/notaryproject/notation-go v0.10.0-alpha.3.0.20221009143310-e2ae1fecf077/go.mod h1:26t4uYh4bkLWMjtG/eakOWIRpqZiF7fMCs55q0WGwTE=
github.com/opencontainers/distribution-spec/specs-go v0.0.0-20220620172159-4ab4752c3b86 h1:Oumw+lPnO8qNLTY2mrqPJZMoGExLi/0h/DdikoLTXVU=
github.com/opencontainers/distribution-spec/specs-go v0.0.0-20220620172159-4ab4752c3b86/go.mod h1:aA4vdXRS8E1TG7pLZOz85InHi3BiPdErh8IpJN6E0x4=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
Expand All @@ -24,6 +26,10 @@ github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
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/veraison/go-cose v1.0.0-rc.1.0.20220824135457-9d2fab636b83 h1:g8vDfnNOPcGzg6mnlBGc0J5t5lAJkaepXqbc9qFRnFs=
github.com/veraison/go-cose v1.0.0-rc.1.0.20220824135457-9d2fab636b83/go.mod h1:7ziE85vSq4ScFTg6wyoMXjucIGOf4JkFEZi/an96Ct4=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 h1:UiNENfZ8gDvpiWw7IpOMQ27spWmThO1RwwdQVbJahJM=
Expand Down
9 changes: 9 additions & 0 deletions internal/cmd/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"strings"
"time"

"github.com/notaryproject/notation/internal/envelope"
"github.com/spf13/pflag"
)

Expand Down Expand Up @@ -36,6 +37,14 @@ var (
fs.StringVar(p, PflagCertFile.Name, "", PflagCertFile.Usage)
}

PflagEnvelopeType = &pflag.Flag{
Name: "envelope-type",
Usage: "signature envelope format, options: 'jws', 'cose'",
}
SetPflagSignatureFormat = func(fs *pflag.FlagSet, p *string) {
fs.StringVar(p, PflagEnvelopeType.Name, envelope.JWS, PflagEnvelopeType.Usage)
}

PflagTimestamp = &pflag.Flag{
Name: "timestamp",
Shorthand: "t",
Expand Down
8 changes: 5 additions & 3 deletions internal/cmd/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@ import (

// SignerFlagOpts cmd opts for using cmd.GetSigner
type SignerFlagOpts struct {
Key string
KeyFile string
CertFile string
Key string
KeyFile string
CertFile string
EnvelopeType string
}

// ApplyFlags set flags and their default values for the FlagSet
func (opts *SignerFlagOpts) ApplyFlags(fs *pflag.FlagSet) {
SetPflagKey(fs, &opts.Key)
SetPflagKeyFile(fs, &opts.KeyFile)
SetPflagCertFile(fs, &opts.CertFile)
SetPflagSignatureFormat(fs, &opts.EnvelopeType)
}
25 changes: 20 additions & 5 deletions internal/cmd/signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,28 @@ package cmd

import (
"errors"
"fmt"
"time"

"github.com/notaryproject/notation-core-go/signature/cose"
"github.com/notaryproject/notation-core-go/signature/jws"
"github.com/notaryproject/notation-go"
"github.com/notaryproject/notation-go/plugin/manager"
"github.com/notaryproject/notation-go/signature"
"github.com/notaryproject/notation/internal/envelope"
"github.com/notaryproject/notation/pkg/configutil"
)

// GetSigner returns a signer according to the CLI context.
func GetSigner(opts *SignerFlagOpts) (notation.Signer, error) {
// Construct a signer from key and cert file if provided as CLI arguments
// TODO: support cose media type
envelopeType := jws.MediaTypeEnvelope
mediaType, err := GetEnvelopeMediaType(opts.EnvelopeType)
if err != nil {
return nil, err
}
if keyPath := opts.KeyFile; keyPath != "" {
certPath := opts.CertFile
return signature.NewSignerFromFiles(keyPath, certPath, envelopeType)
return signature.NewSignerFromFiles(keyPath, certPath, mediaType)
}
// Construct a signer from preconfigured key pair in config.json
// if key name is provided as the CLI argument
Expand All @@ -27,7 +32,7 @@ func GetSigner(opts *SignerFlagOpts) (notation.Signer, error) {
return nil, err
}
if key.X509KeyPair != nil {
return signature.NewSignerFromFiles(key.X509KeyPair.KeyPath, key.X509KeyPair.CertificatePath, envelopeType)
return signature.NewSignerFromFiles(key.X509KeyPair.KeyPath, key.X509KeyPair.CertificatePath, mediaType)
}
// Construct a plugin signer if key name provided as the CLI argument
// corresponds to an external key
Expand All @@ -37,7 +42,7 @@ func GetSigner(opts *SignerFlagOpts) (notation.Signer, error) {
if err != nil {
return nil, err
}
return signature.NewSignerPlugin(runner, key.ExternalKey.ID, key.PluginConfig, envelopeType)
return signature.NewSignerPlugin(runner, key.ExternalKey.ID, key.PluginConfig, mediaType)
}
return nil, errors.New("unsupported key, either provide a local key and certificate file paths, or a key name in config.json, check [DOC_PLACEHOLDER] for details")
}
Expand All @@ -49,3 +54,13 @@ func GetExpiry(expiry time.Duration) time.Time {
}
return time.Now().Add(expiry)
}

func GetEnvelopeMediaType(sigFormat string) (string, error) {
switch sigFormat {
case envelope.JWS:
return jws.MediaTypeEnvelope, nil
case envelope.COSE:
return cose.MediaTypeEnvelope, nil
}
return "", fmt.Errorf("signature format %s not supported", sigFormat)
}
Loading

0 comments on commit 947e55c

Please sign in to comment.