From cfd40daa11e79f85bacc94b7c088472e0bdd6c1b Mon Sep 17 00:00:00 2001 From: Michelle Noorali Date: Fri, 5 Apr 2019 09:22:19 -0400 Subject: [PATCH] remove signature package + update loader pkg to support only unsigned loader + update docs --- cmd/duffle/export.go | 9 +- cmd/duffle/import.go | 2 +- cmd/duffle/main.go | 2 +- docs/README.md | 1 - docs/guides/bundle-guide.md | 18 - docs/guides/signing-guide.md | 138 ------- docs/proposal/200-duffle.md | 2 +- pkg/bundle/bundle.go | 4 +- pkg/loader/detecting_loader.go | 46 --- pkg/loader/detecting_loader_test.go | 27 -- pkg/loader/loader.go | 72 +++- ...unsigned_loader_test.go => loader_test.go} | 8 +- pkg/loader/secure_loader.go | 42 -- pkg/loader/secure_loader_test.go | 42 -- pkg/loader/unsigned_loader.go | 70 ---- pkg/packager/export.go | 4 +- pkg/packager/export_test.go | 4 +- pkg/packager/import.go | 6 +- pkg/packager/import_test.go | 4 +- pkg/signature/doc.go | 9 - pkg/signature/keyring.go | 362 ------------------ pkg/signature/keyring_test.go | 279 -------------- pkg/signature/keys.go | 143 ------- pkg/signature/keys_test.go | 112 ------ pkg/signature/signature.go | 154 -------- pkg/signature/signature_test.go | 70 ---- pkg/signature/testdata/README.md | 26 -- pkg/signature/testdata/broken.gpg | Bin 1455 -> 0 bytes pkg/signature/testdata/extra.asc | 58 --- pkg/signature/testdata/extra.gpg | 58 --- pkg/signature/testdata/extra.kbx | Bin 1457 -> 0 bytes pkg/signature/testdata/extra.kbx~ | Bin 32 -> 0 bytes pkg/signature/testdata/extra1-public.key | Bin 1235 -> 0 bytes pkg/signature/testdata/fail-signed.json | 3 - pkg/signature/testdata/fail-signed.json.asc | 18 - pkg/signature/testdata/keyring.asc | 113 ------ pkg/signature/testdata/keyring.gpg | Bin 5147 -> 0 bytes pkg/signature/testdata/keyring.kbx | Bin 2864 -> 0 bytes pkg/signature/testdata/keyring.kbx~ | Bin 1455 -> 0 bytes pkg/signature/testdata/noclobber.empty | 0 pkg/signature/testdata/public.asc | 57 --- pkg/signature/testdata/public.gpg | Bin 2452 -> 0 bytes pkg/signature/testdata/signed.json | 3 - pkg/signature/testdata/signed.json.asc | 18 - pkg/signature/user_id.go | 67 ---- pkg/signature/user_id_test.go | 80 ---- pkg/signature/verifier.go | 68 ---- pkg/signature/verifier_test.go | 74 ---- 48 files changed, 88 insertions(+), 2185 deletions(-) delete mode 100644 docs/guides/signing-guide.md delete mode 100644 pkg/loader/detecting_loader.go delete mode 100644 pkg/loader/detecting_loader_test.go rename pkg/loader/{unsigned_loader_test.go => loader_test.go} (85%) delete mode 100644 pkg/loader/secure_loader.go delete mode 100644 pkg/loader/secure_loader_test.go delete mode 100644 pkg/loader/unsigned_loader.go delete mode 100644 pkg/signature/doc.go delete mode 100644 pkg/signature/keyring.go delete mode 100644 pkg/signature/keyring_test.go delete mode 100644 pkg/signature/keys.go delete mode 100644 pkg/signature/keys_test.go delete mode 100644 pkg/signature/signature.go delete mode 100644 pkg/signature/signature_test.go delete mode 100644 pkg/signature/testdata/README.md delete mode 100644 pkg/signature/testdata/broken.gpg delete mode 100644 pkg/signature/testdata/extra.asc delete mode 100644 pkg/signature/testdata/extra.gpg delete mode 100644 pkg/signature/testdata/extra.kbx delete mode 100644 pkg/signature/testdata/extra.kbx~ delete mode 100644 pkg/signature/testdata/extra1-public.key delete mode 100644 pkg/signature/testdata/fail-signed.json delete mode 100644 pkg/signature/testdata/fail-signed.json.asc delete mode 100644 pkg/signature/testdata/keyring.asc delete mode 100644 pkg/signature/testdata/keyring.gpg delete mode 100644 pkg/signature/testdata/keyring.kbx delete mode 100644 pkg/signature/testdata/keyring.kbx~ delete mode 100644 pkg/signature/testdata/noclobber.empty delete mode 100644 pkg/signature/testdata/public.asc delete mode 100644 pkg/signature/testdata/public.gpg delete mode 100644 pkg/signature/testdata/signed.json delete mode 100644 pkg/signature/testdata/signed.json.asc delete mode 100644 pkg/signature/user_id.go delete mode 100644 pkg/signature/user_id_test.go delete mode 100644 pkg/signature/verifier.go delete mode 100644 pkg/signature/verifier_test.go diff --git a/cmd/duffle/export.go b/cmd/duffle/export.go index 93157649..c6ae7773 100644 --- a/cmd/duffle/export.go +++ b/cmd/duffle/export.go @@ -78,7 +78,7 @@ func (ex *exportCmd) run() error { return nil } -func (ex *exportCmd) Export(bundlefile string, l loader.Loader) error { +func (ex *exportCmd) Export(bundlefile string, l loader.BundleLoader) error { exp, err := packager.NewExporter(bundlefile, ex.dest, ex.home.Logs(), l, ex.thin) if err != nil { return fmt.Errorf("Unable to set up exporter: %s", err) @@ -92,13 +92,14 @@ func (ex *exportCmd) Export(bundlefile string, l loader.Loader) error { return nil } -func (ex *exportCmd) setup() (string, loader.Loader, error) { +func (ex *exportCmd) setup() (string, loader.BundleLoader, error) { + l := loader.New() bundlefile, err := resolveBundleFilePath(ex.bundle, ex.home.String(), ex.bundleIsFile) if err != nil { - return "", nil, err + return "", l, err } - return bundlefile, loader.NewDetectingLoader(), nil + return bundlefile, l, nil } func resolveBundleFilePath(bun, homePath string, bundleIsFile bool) (string, error) { diff --git a/cmd/duffle/import.go b/cmd/duffle/import.go index 51620dce..d50060aa 100644 --- a/cmd/duffle/import.go +++ b/cmd/duffle/import.go @@ -62,7 +62,7 @@ func (im *importCmd) run() error { return err } - l := loader.NewDetectingLoader() + l := loader.NewLoader() imp, err := packager.NewImporter(source, dest, l, im.verbose) if err != nil { return err diff --git a/cmd/duffle/main.go b/cmd/duffle/main.go index 389cf824..6ea70978 100644 --- a/cmd/duffle/main.go +++ b/cmd/duffle/main.go @@ -124,7 +124,7 @@ func prepareDriver(driverName string) (driver.Driver, error) { } func loadBundle(bundleFile string) (*bundle.Bundle, error) { - l := loader.NewDetectingLoader() + l := loader.NewLoader() // Issue #439: Errors that come back from the loader can be // pretty opaque. diff --git a/docs/README.md b/docs/README.md index dd2b466b..02a12d61 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,7 +4,6 @@ The following documentation is available: 1. Guides 1. [Bundle development guide](guides/bundle-guide.md) - 2. [Signing and verifying guide](guides/signing-guide.md) 2. [Duffle Proposals](./proposal/200-duffle.md): The CNAB reference implementation 1. [Credential Sets](./proposal/201-credentialset.md) 2. [Drivers](./proposal/202-drivers.md) diff --git a/docs/guides/bundle-guide.md b/docs/guides/bundle-guide.md index 93a25979..d31167ac 100644 --- a/docs/guides/bundle-guide.md +++ b/docs/guides/bundle-guide.md @@ -95,9 +95,6 @@ After the bundle has been built, we can inspect the bundle: ```console $ duffle show helloworld:0.1.0 -r ------BEGIN PGP SIGNED MESSAGE----- -Hash: SHA256 - { "name": "helloworld", "version": "0.1.0", @@ -112,23 +109,8 @@ Hash: SHA256 "parameters": null, "credentials": null } ------BEGIN PGP SIGNATURE----- - -wsDcBAEBCAAQBQJb9ErXCRA58VPKJbKbxwAARqYMADtWlk3aLj/NVxNpd3GaqlI6 -tUiW/1T5zIFEWYsJgSC3ammN9z266Uf2q+tDC+jt7A5+sZTGHujn/8FCuURLRkp7 -UVU7ot1xJb8nWUyDLeZjX6yG+eI7XbqjIbt17+bp59XYVRlgJtT1/gLxqm1gh8IQ -D2TLeuOdfI3bstupFEN7AoZWPG5XTYbtQCC9TdBLw70LLGl2f7L4Ll7RFDEJEjx+ -NVCjJEWaYAw7DP1kHUpl67vhkFVeptnbr99uC9aEFUo6fImeuczIU0S9K9g+2Vxf -wcs+XgWKDBkAN9hF/tnaIVsIeHrPJZ9oviEbYDeVqIKUlUBBbNblVTVnjC7shfjF -1SQ4AGhkIgf9gFan7KkERlAp3dcjh5XDgZ7/ijVGGItlbIE1p8+KBm2FRwJfox69 -L9aitybWBnt5EIm3w4YIYsMuMZuPM/0taoKH9nzNv4lQsKYqeX6tOD36aDx4fys1 -NSKekvE5KfHYU3t+3rUtJRphoVsSr3cNFldsVCVuzQ== -=iFSF ------END PGP SIGNATURE----- ``` -As shown by the output, `duffle build` cryptographically signs the bundle to ensure that it has not been tampered with. - ## Watch it Work ```console diff --git a/docs/guides/signing-guide.md b/docs/guides/signing-guide.md deleted file mode 100644 index 02521301..00000000 --- a/docs/guides/signing-guide.md +++ /dev/null @@ -1,138 +0,0 @@ -# Signing and Verifying in Duffle - -CNAB describes a mechanism for signing and verifying bundles. Duffle supports the CNAB spec. This guide explains how Duffle's signing and verifying works. - -## The Basic Idea - -CNAB uses what is called a "web of trust" model for ensuring that bundles are trustworthy. In the "web of trust" model, you decide whom you trust. This is not established by a central authority (which is the way SSL works). Instead, you decide based on whether you believe the other person is trustworthy. You might decide to only trust people you personally know. Or perhaps you trust not only those people, but the people they suggest are trustworthy. Or maybe you only want to trust large corporations. The point is, in the web of trust model _it's up to you_. - -The web of trust model works like this: - -- You have (or create) a _key pair_. This has two keys: a _signing key_ which you keep private, and a _verifying key_ that you can share. - - With a singing key, you can _sign a bundle_, effectively stating, "I, [insert your name], hereby claim that I trust this bundle." - - You can go ahead and distribute your _verifying key_ to other people. There's nothing private about this key. It's primary use is for other people to be able to use it to verify that you indeed signed something. -- When you distribute your signed bundle, other people can _use your verifying key to check your signature_. - - Your friends can then _import your verifying key_. - - If the bundle was signed with your private key, Duffle will let your friends know that the bundle is trusted. - - If it fails to verify, Duffle will not let your friends install the bundle. It will tell them that either they are missing the right key or someone forged a signature. -- You will want to keep your signing key secret and safe, and use it consistently when signing your bundles. So if you have two computers that you use for creating bundles, that _usually_ means you want both to have the same signing key. - -That's the basic idea of the way Duffle does web of trust. There's more to web of trust, though. You can, for example, sign other people's public keys in order to say, "these are people I trust." In this case, you can share those signed keys with your friends, who can say, "well, if [insert your name] trusts them, so will I." Duffle does not yet have a lot of built-in support for this, but there are other tools that you can use with Duffle (like Gnu Privacy Guard, or `gpg`) to do that part. We also really like [Keybase](https://keybase.io), which provides a really cool way of building a trust model for your OpenPGP keys. - -## Cryptographic Batteries Included - -Now we can get started with the practical stuff. Duffle tries to make this as painless as possible for you. - -The first time you run Duffle, it will create a a signing key for you. It does this automatically, and it places the key in `$HOME/.duffle/secret.ring`. You can look at your secret keyring like this: - -```console -$ duffle key list --signing -Test One (Signer) -``` - -As you can see, the keyring above has one key. My personal keyring has 4 (a personal, a work key, and a few for testing). - -Since you already have a signing key, whenever you create new bundles, they will automatically be signed with this key: - -```console -$ duffle create mybundle -$ # work on your bundle -$ duffle build -``` - -The `duffle build` command will automatically sign your bundle with the first key it finds in your secret ring. If you have multiple keys, you can use the `--user`/`-u` flag to choose one: - -``` -$ duffle build -u test1@example.com -``` - -NOTE: In the future, `duffle build` will also calculate and write out each Docker image's digest for the bundle, which is important to ensure that the docker image referenced in a bundle has not been tampered with. [Issue #563](https://github.com/deislabs/duffle/issues/563) discusses a solution for computing the digest when building or signing a bundle. - -## Verifying Bundles - -Just as with signing bundles, Duffle does its best to automatically verify a bundle as well. - -NOTE: In the future, `duffle install` or `duffle upgrade` will also validate each Docker image's digest in a bundle, which is important to ensure that the docker images have not been tampered with. [Issue #392](https://github.com/deislabs/duffle/issues/392) discusses a solution for enforcing digest validation when installing or upgrading a bundle. - -Above, we used `duffle build` to create a bundle. Since we signed it with our own key, we can also easily verify it. And this happens automatically when we do things like `duffle install` or `duffle upgrade`: - -```console -$ duffle install myrelease mybundle -``` - -However, when it comes to working with bundles that were created by other people you trust, you need to first get their public key. - -Say they give you a key named `friend.key` (the extension, by the way, makes no difference to Duffle). You can import it into your _verifying keys_ like this: - -```console -$ duffle key import friend.key -``` - -Then you can verify that it imported by listing the _verifying keys_ that are in your keyring: - -```console -$ duffle key list -l -NAME TYPE FINGERPRINT -Extra Key (Signer) signing D113 40F5 336 A06C BF0 7F14 F8A4 26C5 52CB 806 -Test One (Signer) signing FAB9 6672 1CFB 1C25 164D BB4E 281 57BA FDA1 54FA -friend@example.com signing 5D76 712C E625 98A8 27A2 7E28 9B79 91DD 4037 8340 -``` - -In the example above, we have three keys that can be used for verifying bundles. - -## Sharing Your Verifying Key with Others - -When your signing key was generated, Duffle also generated a verifying key for you to share with your friends. You can _export_ this key to a file, and then share that file: - -```console -$ duffle key export my_signer.key -``` - -That will export _just the first key_ that you generated. If you want to export particular keys, you can use the `--user`/`-u` flag: - -```console -$ duffle key export my_signer.key -u test1@example.com -``` - -Now, any of your friends can use `duffle key import` to add your key to their keyring. - -## But I Want to Use My Existing Key! - -Say you already have a key (from, perhaps, Keybase or GnuPG). In most cases, you can use that key with Duffle. There are two ways of doing this: - -- You can use `duffle key import --secret mykey.gpg` and import it to the existing keyring. However, this will place the key at the _end_ of your keyring, and you will need to use the `--user`/`-u` flag whenever signing. -- You can remove your existing `secret.ring` and then run `duffle init -s mykey.gpg` or run `duffle key import --secret mykey.gpg`. Either of these will create a new keyring for you. - -As always, you can check on your keyring by using `duffle key list --secret` (or see your verifying keys with `duffle key list`). - -## Manually Signing and Verifying Bundles - -Most of the time Duffle will deal with signing and verifying for you. But it also provides a few extra tools you can use to sign and verify on your own. These are useful if you are using other tools to build or install your CNAB bundles, but just need to do the signing or verifying yourself. - -Verifying a bundle is done with `duffle bundle verify`: - -``` -$ duffle bundle verify -f ./bundle.cnab -Signed by "Extra Key (Signer) " (D113 40F5 336 A06C BF0 7F14 F8A4 26C5 52CB 806) -``` - -A verification works like this: - -- it reads the signature on a `bundle.cnab` file -- it tests the signature against every key in your keyring until it finds one that works -- it then returns the key whose verification passed -- and it exits with code 0 - -If it cannot find a key that will verify the bundle, it will print an error message and exit with the error code 1. - -You can sign a bundle like this: - -``` -$ duffle bundle sign -f bundle.json -o bundle.cnab -``` - -The above will use your default signing key to sign the file `bundle.json` and produce the file `bundle.cnab`. As usual, you can use `--user`/`-u` to specify a particular user ID. - -## Conclusion - -The purpose of this guide has been to explain how signing and verifying work in Duffle, and which commands are available to you. If you are deeply interested in the theory and implementation behind OpenPGP-style signing, you may want to read the [OpenPGP spec](https://tools.ietf.org/html/rfc4880). diff --git a/docs/proposal/200-duffle.md b/docs/proposal/200-duffle.md index ace0c45f..156a374a 100644 --- a/docs/proposal/200-duffle.md +++ b/docs/proposal/200-duffle.md @@ -74,7 +74,7 @@ For operations that execute this installation image (install, upgrade, etc.), th - Load the parameters and credential set definitions - Load the claim (if necessary) -- Load the `bundle.json`. If signed, verify it before parsing the body +- Load the `bundle.json`. - Locate the invocation image - Validate the parameters and credentials - Prepare the invocation image, mounting the parameters and credentials, as well as the claim data diff --git a/pkg/bundle/bundle.go b/pkg/bundle/bundle.go index 9f457e52..edcd5778 100644 --- a/pkg/bundle/bundle.go +++ b/pkg/bundle/bundle.go @@ -25,7 +25,6 @@ type Bundle struct { Credentials map[string]Location `json:"credentials" mapstructure:"credentials"` } -//Unmarshal unmarshals a Bundle that was not signed. func Unmarshal(data []byte) (*Bundle, error) { b := &Bundle{} return b, json.Unmarshal(data, b) @@ -40,7 +39,6 @@ func ParseReader(r io.Reader) (Bundle, error) { // WriteFile serializes the bundle and writes it to a file as JSON. func (b Bundle) WriteFile(dest string, mode os.FileMode) error { - // FIXME: The marshal here should exactly match the Marshal in the signature code. d, err := json.MarshalCanonical(b) if err != nil { return err @@ -48,7 +46,7 @@ func (b Bundle) WriteFile(dest string, mode os.FileMode) error { return ioutil.WriteFile(dest, d, mode) } -// WriteTo writes unsigned JSON to an io.Writer using the standard formatting. +// WriteTo writes JSON to an io.Writer using the standard formatting. func (b Bundle) WriteTo(w io.Writer) (int64, error) { d, err := json.MarshalCanonical(b) if err != nil { diff --git a/pkg/loader/detecting_loader.go b/pkg/loader/detecting_loader.go deleted file mode 100644 index 0c4ccf2d..00000000 --- a/pkg/loader/detecting_loader.go +++ /dev/null @@ -1,46 +0,0 @@ -package loader - -import ( - "github.com/deislabs/duffle/pkg/bundle" - - "golang.org/x/crypto/openpgp/clearsign" -) - -// DetectingLoader loads a file or data, and then determines the content. -// -// A DetectingLoader NEVER verifies a signature. If the bundle is signed, it will extract -// the body, and then parse. If it is raw JSON, it will parse. -// -// DetectingLoader is INSECURE, as it does not verify the bundle. -type DetectingLoader struct{} - -// NewDetectingLoader creates a new loader that can detect the file type -// -// It will only detect between the '.cnab' format (signed bundles) and -// the '.json' (JSON) format. -func NewDetectingLoader() *DetectingLoader { - return &DetectingLoader{} -} - -// Load loads a file from the filesystem and parses it. -func (l *DetectingLoader) Load(filename string) (*bundle.Bundle, error) { - data, err := loadData(filename) - if err != nil { - return nil, err - } - return l.LoadData(data) -} - -// LoadData loads file from a byte slice and parses it. -func (l *DetectingLoader) LoadData(data []byte) (*bundle.Bundle, error) { - // clearsign.Decode provides a safe way of dealing with clearsigned - // blocks. It will return a block ONLY IF it finds a clearsign header - // at the beginning of a line, which is not valid JSON (and therefore) - // can't occur in a legitimate bundle.json). - block, _ := clearsign.Decode(data) - if block != nil { - data = block.Bytes - } - // Delegate parsing to an unsigned_loader - return NewUnsignedLoader().LoadData(data) -} diff --git a/pkg/loader/detecting_loader_test.go b/pkg/loader/detecting_loader_test.go deleted file mode 100644 index 45e6e46f..00000000 --- a/pkg/loader/detecting_loader_test.go +++ /dev/null @@ -1,27 +0,0 @@ -package loader - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestDetectingLoader_Signed(t *testing.T) { - is := assert.New(t) - - b, err := NewDetectingLoader().Load(testSigned) - is.NoError(err) - is.Equal(b.Name, "example_bundle") -} - -func TestDetectingLoader_Unsigned(t *testing.T) { - is := assert.New(t) - - bundle, err := NewDetectingLoader().Load(testFooJSON) - if err != nil { - t.Fatalf("cannot load bundle: %v", err) - } - - is.Equal("foo", bundle.Name) - is.Equal("1.0", bundle.Version) -} diff --git a/pkg/loader/loader.go b/pkg/loader/loader.go index b6f79225..37c85287 100644 --- a/pkg/loader/loader.go +++ b/pkg/loader/loader.go @@ -1,21 +1,81 @@ package loader import ( + "fmt" + "io/ioutil" + "net/http" + "net/url" + "os" + "github.com/deislabs/duffle/pkg/bundle" - "github.com/deislabs/duffle/pkg/signature" ) // Loader provides an interface for loading a bundle -type Loader interface { +type BundleLoader interface { // Load a bundle from a local file Load(source string) (*bundle.Bundle, error) // Load a bundle from raw data LoadData(data []byte) (*bundle.Bundle, error) } -// New creates a loader for signed bundle files. -func New(keyring *signature.KeyRing) Loader { - return &SecureLoader{ - keyring: keyring, +// Loader loads a bundle manifest (bundle.json) +type Loader struct{} + +// New creates a loader for bundle files. +//TODO: remove if unnecessary +func New() BundleLoader { + return &Loader{} +} + +func NewLoader() *Loader { + return &Loader{} +} + +// Load loads the given bundle. +func (l *Loader) Load(filename string) (*bundle.Bundle, error) { + b := &bundle.Bundle{} + data, err := loadData(filename) + if err != nil { + return b, err + } + return l.LoadData(data) +} + +// LoadData loads a Bundle from the given data. +// +// This loads a JSON bundle file into a *bundle.Bundle. +func (l *Loader) LoadData(data []byte) (*bundle.Bundle, error) { + return bundle.Unmarshal(data) +} + +// loadData is a utility method that loads a file either off of the FS (if it exists) or via a remote HTTP GET. +// +// If bundleFile exists on disk, this will return that file. Otherwise, it will attempt to parse the +// file name as a URL and request it as an HTTP GET request. +func loadData(bundleFile string) ([]byte, error) { + if isLocalReference(bundleFile) { + return ioutil.ReadFile(bundleFile) } + + if u, err := url.ParseRequestURI(bundleFile); err != nil { + // The error emitted by ParseRequestURI is icky. + return []byte{}, fmt.Errorf("bundle %q not found", bundleFile) + } else if u.Scheme == "file" { + // What do we do if someone passes a `file:///` URL in? Is `file` inferred + // if no protocol is specified? + return []byte{}, fmt.Errorf("bundle %q not found", bundleFile) + } + + response, err := http.Get(bundleFile) + if err != nil { + return []byte{}, fmt.Errorf("cannot download bundle file: %v", err) + } + defer response.Body.Close() + + return ioutil.ReadAll(response.Body) +} + +func isLocalReference(file string) bool { + _, err := os.Stat(file) + return err == nil } diff --git a/pkg/loader/unsigned_loader_test.go b/pkg/loader/loader_test.go similarity index 85% rename from pkg/loader/unsigned_loader_test.go rename to pkg/loader/loader_test.go index 64a82376..0a3225f9 100644 --- a/pkg/loader/unsigned_loader_test.go +++ b/pkg/loader/loader_test.go @@ -14,10 +14,10 @@ import ( var testFooJSON = filepath.Join("..", "..", "tests", "testdata", "bundles", "foo.json") -func TestUnsignedLoader(t *testing.T) { +func TestLoader(t *testing.T) { is := assert.New(t) - l := NewUnsignedLoader() + l := NewLoader() bundle, err := l.Load(testFooJSON) if err != nil { t.Fatalf("cannot load bundle: %v", err) @@ -26,7 +26,7 @@ func TestUnsignedLoader(t *testing.T) { is.Equal("foo", bundle.Name) is.Equal("1.0", bundle.Version) } -func TestUnsignedLoader_Remote(t *testing.T) { +func TestLoader_Remote(t *testing.T) { data, err := ioutil.ReadFile(testFooJSON) if err != nil { @@ -38,7 +38,7 @@ func TestUnsignedLoader_Remote(t *testing.T) { })) defer ts.Close() - l := NewUnsignedLoader() + l := NewLoader() bundle, err := l.Load(ts.URL) if err != nil { t.Fatal(err) diff --git a/pkg/loader/secure_loader.go b/pkg/loader/secure_loader.go deleted file mode 100644 index 4db93047..00000000 --- a/pkg/loader/secure_loader.go +++ /dev/null @@ -1,42 +0,0 @@ -package loader - -import ( - "github.com/deislabs/duffle/pkg/bundle" - "github.com/deislabs/duffle/pkg/signature" -) - -// SecureLoader loads signed bundles. -// -// A signed bundle is a bundle.json file that has been cryptographically signed. -// This loader will load such a file, and test the validity of the signature. -type SecureLoader struct { - keyring *signature.KeyRing -} - -// NewSecureLoader creates a new SecureLoader.NewSecureLoader. -// A SecureLoader uses a keyring to validate the signature on a signed bundle. -func NewSecureLoader(keyring *signature.KeyRing) *SecureLoader { - return &SecureLoader{ - keyring: keyring, - } -} - -// Load will load a file, verify the signature, and return the parsed *Bundle. -func (s *SecureLoader) Load(filename string) (*bundle.Bundle, error) { - b := &bundle.Bundle{} - data, err := loadData(filename) - if err != nil { - return b, err - } - return s.LoadData(data) -} - -// LoadData loads a bundle from data. -// -// This will perform verification of the bundle, extract the JSON, and then -// parse the JSON into a *Bundle. -func (s *SecureLoader) LoadData(data []byte) (*bundle.Bundle, error) { - verifier := signature.NewVerifier(s.keyring) - b, _, err := verifier.Extract(data) - return b, err -} diff --git a/pkg/loader/secure_loader_test.go b/pkg/loader/secure_loader_test.go deleted file mode 100644 index 95b10072..00000000 --- a/pkg/loader/secure_loader_test.go +++ /dev/null @@ -1,42 +0,0 @@ -package loader - -import ( - "path/filepath" - "testing" - - "github.com/deislabs/duffle/pkg/signature" - - "github.com/stretchr/testify/assert" -) - -var ( - testFixtures = filepath.Join("..", "signature", "testdata") - testPublicRing = filepath.Join(testFixtures, "public.gpg") - testSigned = filepath.Join(testFixtures, "signed.json.asc") - testFailedSigned = filepath.Join(testFixtures, "fail-signed.json.asc") -) - -func TestSecureLoader(t *testing.T) { - is := assert.New(t) - kr, err := signature.LoadKeyRing(testPublicRing) - if err != nil { - t.Fatal(err) - } - - loader := NewSecureLoader(kr) - b, err := loader.Load(testSigned) - is.NoError(err) - is.Equal(b.Name, "example_bundle") -} - -func TestSecureLoader_FailSignature(t *testing.T) { - is := assert.New(t) - kr, err := signature.LoadKeyRing(testPublicRing) - if err != nil { - t.Fatal(err) - } - - loader := NewSecureLoader(kr) - _, err = loader.Load(testFailedSigned) - is.Error(err) -} diff --git a/pkg/loader/unsigned_loader.go b/pkg/loader/unsigned_loader.go deleted file mode 100644 index c02d7918..00000000 --- a/pkg/loader/unsigned_loader.go +++ /dev/null @@ -1,70 +0,0 @@ -package loader - -import ( - "fmt" - "io/ioutil" - "net/http" - "net/url" - "os" - - "github.com/deislabs/duffle/pkg/bundle" -) - -// UnsignedLoader loads a bundle.json that is not signed. -type UnsignedLoader struct{} - -// NewUnsignedLoader creates a new *UnsignedLoader -// -// An UnsignedLoader can load an unsigned bundle, which is represented as a plain JSON file. -func NewUnsignedLoader() *UnsignedLoader { - return &UnsignedLoader{} -} - -// Load loads the given unsigned bundle. -func (l *UnsignedLoader) Load(filename string) (*bundle.Bundle, error) { - b := &bundle.Bundle{} - data, err := loadData(filename) - if err != nil { - return b, err - } - return l.LoadData(data) -} - -// LoadData loads a Bundle from the given data. -// -// This loads an unsigned JSON bundle file into a *Bundle. -func (l *UnsignedLoader) LoadData(data []byte) (*bundle.Bundle, error) { - return bundle.Unmarshal(data) -} - -// loadData is a utility method that loads a file either off of the FS (if it exists) or via a remote HTTP GET. -// -// If bundleFile exists on disk, this will return that file. Otherwise, it will attempt to parse the -// file name as a URL and request it as an HTTP GET request. -func loadData(bundleFile string) ([]byte, error) { - if isLocalReference(bundleFile) { - return ioutil.ReadFile(bundleFile) - } - - if u, err := url.ParseRequestURI(bundleFile); err != nil { - // The error emitted by ParseRequestURI is icky. - return []byte{}, fmt.Errorf("bundle %q not found", bundleFile) - } else if u.Scheme == "file" { - // What do we do if someone passes a `file:///` URL in? Is `file` inferred - // if no protocol is specified? - return []byte{}, fmt.Errorf("bundle %q not found", bundleFile) - } - - response, err := http.Get(bundleFile) - if err != nil { - return []byte{}, fmt.Errorf("cannot download bundle file: %v", err) - } - defer response.Body.Close() - - return ioutil.ReadAll(response.Body) -} - -func isLocalReference(file string) bool { - _, err := os.Stat(file) - return err == nil -} diff --git a/pkg/packager/export.go b/pkg/packager/export.go index 88aba168..c5916840 100644 --- a/pkg/packager/export.go +++ b/pkg/packager/export.go @@ -24,14 +24,14 @@ type Exporter struct { Client *client.Client Context context.Context Logs string - Loader loader.Loader + Loader loader.BundleLoader } // NewExporter returns an *Exporter given information about where a bundle // lives, where the compressed bundle should be exported to, // and what form a bundle should be exported in (thin or thick/full). It also // sets up a docker client to work with images. -func NewExporter(source, dest, logsDir string, l loader.Loader, thin bool) (*Exporter, error) { +func NewExporter(source, dest, logsDir string, l loader.BundleLoader, thin bool) (*Exporter, error) { cli, err := client.NewClientWithOpts(client.FromEnv) if err != nil { return nil, err diff --git a/pkg/packager/export_test.go b/pkg/packager/export_test.go index 9ffea75d..616ace45 100644 --- a/pkg/packager/export_test.go +++ b/pkg/packager/export_test.go @@ -28,7 +28,7 @@ func TestExport(t *testing.T) { Source: source, Thin: true, Logs: filepath.Join(tempDir, "export-logs"), - Loader: loader.NewDetectingLoader(), + Loader: loader.NewLoader(), } if err := ex.Export(); err != nil { @@ -56,7 +56,7 @@ func TestExportCreatesFileProperly(t *testing.T) { Destination: filepath.Join(tempDir, "random-directory", "examplebun-whatev.tgz"), Thin: true, Logs: filepath.Join(tempDir, "export-logs"), - Loader: loader.NewDetectingLoader(), + Loader: loader.NewLoader(), } if err := ex.Export(); err == nil { diff --git a/pkg/packager/import.go b/pkg/packager/import.go index 96d11f35..d9bae836 100644 --- a/pkg/packager/import.go +++ b/pkg/packager/import.go @@ -25,7 +25,7 @@ type Importer struct { Source string Destination string Client *client.Client - Loader loader.Loader + Loader loader.BundleLoader Verbose bool } @@ -33,8 +33,8 @@ type Importer struct { // // source is the filesystem path to the archive. // destination is the directory to unpack the contents. -// load is a loader.Loader preconfigured for loading secure or insecure bundles. -func NewImporter(source, destination string, load loader.Loader, verbose bool) (*Importer, error) { +// load is a loader.BundleLoader preconfigured for loading secure or insecure bundles. +func NewImporter(source, destination string, load loader.BundleLoader, verbose bool) (*Importer, error) { cli, err := client.NewClientWithOpts(client.FromEnv) if err != nil { return nil, err diff --git a/pkg/packager/import_test.go b/pkg/packager/import_test.go index 9c10cbf2..445584f5 100644 --- a/pkg/packager/import_test.go +++ b/pkg/packager/import_test.go @@ -23,7 +23,7 @@ func TestImport(t *testing.T) { im := Importer{ Source: "testdata/examplebun-0.1.0.tgz", Destination: tempDir, - Loader: loader.NewDetectingLoader(), + Loader: loader.NewLoader(), } if err := im.Import(); err != nil { @@ -44,7 +44,7 @@ func TestMalformedImport(t *testing.T) { im := Importer{ Source: "testdata/malformed-0.1.0.tgz", Destination: tempDir, - Loader: loader.NewDetectingLoader(), + Loader: loader.NewLoader(), } if err = im.Import(); err == nil { diff --git a/pkg/signature/doc.go b/pkg/signature/doc.go deleted file mode 100644 index 629018fc..00000000 --- a/pkg/signature/doc.go +++ /dev/null @@ -1,9 +0,0 @@ -// Package signature provides signing tools for cryptographically signing bundles. -// -// These tools provide methods for marking authority, verifying authority, and extracting -// ciphertext. -// -// A Signer is used for signing things. A Verifier is used for taking a signed block and -// verifying it against a keyring. A Key represents a single key, while a KeyRing represents -// a collection of keys. -package signature diff --git a/pkg/signature/keyring.go b/pkg/signature/keyring.go deleted file mode 100644 index 40a85c3a..00000000 --- a/pkg/signature/keyring.go +++ /dev/null @@ -1,362 +0,0 @@ -package signature - -import ( - "bytes" - "errors" - "fmt" - "io" - "os" - "strconv" - "strings" - - "golang.org/x/crypto/openpgp/armor" - - "golang.org/x/crypto/openpgp" - "golang.org/x/crypto/openpgp/packet" -) - -// KeyRing represents a collection of keys as specified by OpenPGP -type KeyRing struct { - entities openpgp.EntityList - PassphraseFetcher PassphraseFetcher -} - -// Len returns the length of this keyring -// -// Length is the number of entitites stored in this ring. -func (r *KeyRing) Len() int { - return len(r.entities) -} - -// Add adds new keys to the keyring. -// -// Add is idempotent. If provided keys already exist, they will be -// silently ignored. This makes it easier to do bulk imports. -func (r *KeyRing) Add(keyReader io.Reader, armored bool) error { - var entities openpgp.EntityList - var err error - if armored { - entities, err = openpgp.ReadArmoredKeyRing(keyReader) - } else { - entities, err = openpgp.ReadKeyRing(keyReader) - } - if err != nil { - return err - } - - r.entities = append(r.entities, r.removeDuplicates(entities)...) - return nil -} - -// AddKey adds a *Key to the keyring. -// -// AddKey is idempotent. If a key exists already, it will be silently ignored. -func (r *KeyRing) AddKey(k *Key) { - if r.isDuplicate(k.entity) { - return - } - r.entities = append(r.entities, k.entity) -} - -// removeDulicates filters out duplicate keys -func (r *KeyRing) removeDuplicates(entities []*openpgp.Entity) []*openpgp.Entity { - remove := map[int]bool{} - for i, e := range entities { - if r.isDuplicate(e) { - remove[i] = true - } - } - - if len(remove) == 0 { - return entities - } - filtered := []*openpgp.Entity{} - for i, e := range entities { - if _, ok := remove[i]; !ok { - filtered = append(filtered, e) - } - } - return filtered -} - -// isDuplicate compares the fingerprint of the given entity to all of the existing fingerprints -// If the fingerprint exists in the keyring, this returns true. Otherwise it returns false. -func (r *KeyRing) isDuplicate(e *openpgp.Entity) bool { - for _, re := range r.entities { - if re.PrimaryKey.Fingerprint == e.PrimaryKey.Fingerprint { - return true - } - } - return false -} - -// Key returns the key with the given ID. -// -// ID is a hex ID or (conventionally) an email address. -// -// If no such key exists, this will return an error. -func (r *KeyRing) Key(id string) (*Key, error) { - // NB: GnuPG allows any of the following to be used: - // - Hex ID (we support) - // - Email (we support) - // - Substring match on OpenPGP User Name (we support if first two fail) - // - Fingerprint - // - OpenPGP User Name ("Name (Comment) ") - // - Partial email - // - Subject DN (x509) - // - Issuer DN (x509) - // - Keygrip (40 hex digits) - - hexID, err := strconv.ParseInt(id, 16, 64) - if err == nil { - k := r.entities.KeysById(uint64(hexID)) - l := len(k) - if l > 1 { - return nil, fmt.Errorf("required one key, got %d", l) - } - if l == 1 { - return &Key{entity: k[0].Entity, PassphraseFetcher: r.PassphraseFetcher}, nil - } - // Else fallthrough and try a string-based lookup - } - - // If we get here, there was no key found when looking by hex ID. - // So we try again by string name in the email field. We also do weak matching - // at the same time. - weak := map[[20]byte]*openpgp.Entity{} - for _, e := range r.entities { - for _, ident := range e.Identities { - // XXX Leave this commented section - // It is not clear whether we should skip identities that were not self-signed - // with the Sign flag on. Since the entity is at a higher level than the identity, - // it seems like we are more interested in the entity's capability than the - // identity the user requested, and we can always walk the subkeys to see if - // any of those are allowed to sign. So I am leaving this commented. - //if !ident.SelfSignature.FlagSign { - // continue - //} - if ident.UserId.Email == id { - return &Key{entity: e, PassphraseFetcher: r.PassphraseFetcher}, nil - } - if strings.Contains(ident.Name, id) { - weak[e.PrimaryKey.Fingerprint] = e - } - } - } - - switch len(weak) { - case 0: - return nil, errors.New("key not found") - case 1: - for _, first := range weak { - return &Key{entity: first, PassphraseFetcher: r.PassphraseFetcher}, nil - } - } - return nil, errors.New("multiple matching keys found") -} - -// PrivateKeys returns all of the private keys on this keyring. -// -// A private key is any key that has material in the private key packet. Note that -// this neither tests that the key is valid, nor decrypts an encrypted key. -func (r *KeyRing) PrivateKeys() []*Key { - pks := []*Key{} - for _, e := range r.entities { - // This is the best test for a private key that I have been able to figure out. - // It tests _if_ there is a PrivateKey on the entity. But that alone is insufficient, - // since public keys sometimes have the public key data tacked on here. So then - // we test for whether the private key has private key material OR whether it is - // an encrypted key (which means it is private, but the data is not in the material - // section until it has been decrypted). - if e.PrivateKey != nil && (e.PrivateKey.PrivateKey != nil || e.PrivateKey.Encrypted) { - pks = append(pks, &Key{ - entity: e, - PassphraseFetcher: r.PassphraseFetcher, - }) - } - } - return pks -} - -// Keys returns all keys (public and private). -func (r *KeyRing) Keys() []*Key { - pks := []*Key{} - for _, e := range r.entities { - pks = append(pks, &Key{ - entity: e, - PassphraseFetcher: r.PassphraseFetcher, - }) - } - return pks -} - -// SavePrivate writes a keyring to disk as a binary entity list. -// -// This is the standard format described by the OpenPGP specification. The file will thus be -// importable to any OpenPGP compliant app that can read entity lists (that is, a list of -// OpenPGP packets). -// -// Note that if the keyring contains encrypted keys, the saving process will need to -// decrypt every single key. Make sure the *KeyRing has a PassphraseFetcher before calling -// Save. -func (r *KeyRing) SavePrivate /*Ryan*/ (filepath string, clobber bool) error { - if !clobber { - if _, err := os.Stat(filepath); err == nil { - return errors.New("keyring file exists") - } - } - - // Write to a buffer so we don't nuke a keychain. - temp := bytes.NewBuffer(nil) - for _, e := range r.entities { - - // The serializer has no decryption, so we have to do this manually before saving. - // Yes, this is a major pain. But apparently encrypted keys cannot be serialized. - if e.PrivateKey.Encrypted { - if err := decryptPassphrase(e.PrimaryKey.KeyIdShortString(), e.PrivateKey, r.PassphraseFetcher); err != nil { - return err - } - } - - for _, sk := range e.Subkeys { - if sk.PrivateKey.Encrypted { - if err := decryptPassphrase(e.PrimaryKey.KeyIdShortString()+" subkey", sk.PrivateKey, r.PassphraseFetcher); err != nil { - return err - } - } - } - - // According to the godocs, when we call this, we lose "signatures from other entities", but preserve public and private keys. - if err := e.SerializePrivate(temp, nil); err != nil { - return err - } - } - - f, err := os.Create(filepath) - if err != nil { - return err - } - defer f.Close() - _, err = io.Copy(f, temp) - return err -} - -// SavePublic saves the public keys into a file. -// -// Private key material is ignored. -func (r *KeyRing) SavePublic(filepath string, clobber, asciiArmor bool) error { - if !clobber { - if _, err := os.Stat(filepath); err == nil { - return errors.New("keyring file exists") - } - } - - f, err := os.Create(filepath) - if err != nil { - return err - } - defer f.Close() - - // Write to a buffer so we don't nuke a keychain. - temp := bytes.NewBuffer(nil) - if err := r.SavePublicTo(temp, asciiArmor); err != nil { - return err - } - _, err = io.Copy(f, temp) - return err -} - -// SavePublicTo saves the keyring to the given writer -// -// It removes private key material as it goes. -func (r *KeyRing) SavePublicTo(writer io.Writer, useArmor bool) error { - w := writer - if useArmor { - headers := map[string]string{"Comment": "Duffle - https://cnab.io"} - var err error - w, err = armor.Encode(writer, openpgp.PublicKeyType, headers) - if err != nil { - return err - } - // Only close the writer if it is an encoder. - defer w.(io.WriteCloser).Close() - } - for _, e := range r.entities { - // According to the godocs, when we call this, we lose private key material, but keep public and signatures. - // However, if I load a secret keyring (generated by GnuPG) and then serialize it, the private key - // seems to still be there. Using `gpg --list-secret-keys`, I can recover secret keys after this method is run. - if err := e.Serialize(w); err != nil { - return err - } - } - return nil -} - -func decryptPassphrase(msg string, pk *packet.PrivateKey, fetcher PassphraseFetcher) error { - if fetcher == nil { - return errors.New("unable to decrypt key") - } - pass, err := fetcher(msg) - if err != nil { - return err - } - - return pk.Decrypt(pass) -} - -// LoadKeyRing loads a keyring from a path. -func LoadKeyRing(path string) (*KeyRing, error) { - // TODO: Should we create a default passphrase fetcher? - return LoadKeyRingFetcher(path, nil) -} - -// LoadKeyRings loads multiple keyring files into one *KeyRing object -// -// This can be used to load both public and private keyrings for verification. -func LoadKeyRings(paths ...string) (*KeyRing, error) { - if len(paths) == 0 { - return &KeyRing{}, errors.New("no keyrings provided") - } - baseRing, err := LoadKeyRing(paths[0]) - if err != nil { - return baseRing, err - } - for i := 1; i < len(paths); i++ { - ring, err := LoadKeyRingFetcher(paths[i], baseRing.PassphraseFetcher) - if err != nil { - return baseRing, err - } - for _, k := range ring.Keys() { - baseRing.AddKey(k) - } - } - return baseRing, nil -} - -// CreateKeyRing creates an empty in-memory keyring. -func CreateKeyRing(fetcher PassphraseFetcher) *KeyRing { - return &KeyRing{ - entities: openpgp.EntityList{}, - PassphraseFetcher: fetcher, - } -} - -// LoadKeyRingFetcher loads a keyring from a path. -// -// If PassphraseFetcher is non-nil, it will be called whenever an encrypted key needs to be decrypted. -// If left nil, this will cause the keyring to emit an error whenever an encrypted key needs to be decrypted. -func LoadKeyRingFetcher(path string, fetcher PassphraseFetcher) (*KeyRing, error) { - f, err := os.Open(path) - if err != nil { - return nil, err - } - defer f.Close() - entities, err := openpgp.ReadKeyRing(f) - if err != nil { - return nil, err - } - return &KeyRing{ - entities: entities, - PassphraseFetcher: fetcher, - }, nil -} diff --git a/pkg/signature/keyring_test.go b/pkg/signature/keyring_test.go deleted file mode 100644 index 0b44dda6..00000000 --- a/pkg/signature/keyring_test.go +++ /dev/null @@ -1,279 +0,0 @@ -package signature - -import ( - "io/ioutil" - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestLoadKeyRing(t *testing.T) { - is := assert.New(t) - k, err := LoadKeyRing(keyringFile) - is.NoError(err) - is.Len(k.entities, 2) - is.Equal(k.entities[0].Identities[fullKeyID].UserId.Email, keyEmail) - is.NotNil(k.entities[0].PrivateKey) -} - -func TestLoadKeyRings(t *testing.T) { - is := assert.New(t) - k, err := LoadKeyRings(keyringFile, "testdata/public.gpg") - is.NoError(err) - is.Len(k.entities, 2) - is.Equal(k.entities[0].Identities[fullKeyID].UserId.Email, keyEmail) - is.NotNil(k.entities[0].PrivateKey) -} - -func TestKeyRing_Len(t *testing.T) { - is := assert.New(t) - k, err := LoadKeyRing(keyringFile) - is.NoError(err) - is.Equal(k.Len(), 2) -} - -func TestKeyring_Key(t *testing.T) { - is := assert.New(t) - k, err := LoadKeyRing(keyringFile) - is.NoError(err) - - key, err := k.Key("test1@example.com") - is.NoError(err) - - is.Equal(key.entity.Identities[fullKeyID].UserId.Email, keyEmail) -} - -func TestKeyring_MultipleKeys(t *testing.T) { - is := assert.New(t) - k, err := LoadKeyRing(keyringFile) - is.NoError(err) - - _, err = k.Key("test") - is.Error(err) - is.Contains(err.Error(), "multiple matching keys found") -} - -func TestKeyring_KeyByID(t *testing.T) { - is := assert.New(t) - k, err := LoadKeyRing(keyringFile) - is.NoError(err) - - key, err := k.Key("6EFB02A2F77D9682") - is.NoError(err) - is.Equal(key.entity.Identities[fullKeyID].UserId.Email, keyEmail) - - key, err = k.Key("123A4002462DC23B") - is.NoError(err) - is.Equal(key.entity.Identities[key2Email].Name, key2Email) -} - -func TestKeyRing_Add_Armored(t *testing.T) { - is := assert.New(t) - extras, err := os.Open("testdata/extra.gpg") - is.NoError(err) - kr, err := LoadKeyRing(keyringFile) - is.NoError(err) - is.NoError(kr.Add(extras, true)) - - k, err := kr.Key("extra1@example.com") - is.NoError(err) - is.Equal(k.entity.Identities[fullExtraID].Name, fullExtraID) - - // Test that we can add the same keys again, and have it silently skip - // duplicates. - l := kr.Len() - is.True(l > 0) - extras, err = os.Open("testdata/extra.gpg") - is.NoError(err) - - // Re-add extras - is.NoError(kr.Add(extras, true)) - k2, err := kr.Key("extra1@example.com") - is.NoError(err) - is.Equal(k2.entity.Identities[fullExtraID].Name, fullExtraID) - is.Equal(l, kr.Len()) -} - -func TestKeyRing_Add_NotArmored(t *testing.T) { - is := assert.New(t) - extras, err := os.Open("testdata/extra1-public.key") - is.NoError(err) - kr, err := LoadKeyRing(keyringFile) - is.NoError(err) - is.NoError(kr.Add(extras, false)) - - k, err := kr.Key("extra1@example.com") - is.NoError(err) - is.Equal(k.entity.Identities[fullExtraID].Name, fullExtraID) - -} - -func TestKeyRing_AddKey(t *testing.T) { - is := assert.New(t) - - kr, err := LoadKeyRing(keyringFile) - is.NoError(err) - - k, err := CreateKey(UserID{Name: "a", Comment: "b", Email: "c@e"}) - is.NoError(err) - is.NotNil(k) - - kr.AddKey(k) - k2, err := kr.Key("c@e") - is.NoError(err) - pk, err := k2.bestPrivateKey() - is.NoError(err) - is.NotNil(pk) - - // Test that if we re-add the same key it will be ignored. - l := kr.Len() - kr.AddKey(k) - is.Equal(l, kr.Len()) -} - -func TestCreateKeyRing(t *testing.T) { - is := assert.New(t) - extras, err := os.Open("testdata/extra.gpg") - is.NoError(err) - - kr := CreateKeyRing(testPassphraseFetch) - is.NoError(kr.Add(extras, true)) - - k, err := kr.Key("extra1@example.com") - is.NoError(err) - is.Equal(k.entity.Identities[fullExtraID].Name, fullExtraID) -} - -func TestKeyRing_SavePrivate(t *testing.T) { - is := assert.New(t) - kr, err := LoadKeyRingFetcher(keyringFile, testPassphraseFetch) - is.NoError(err) - - is.Error(kr.SavePrivate("testdata/noclobber.empty", false)) - - dirname, err := ioutil.TempDir("", "signature-") - if err != nil { - t.Fatal(err) - } - defer func() { - is.NoError(os.RemoveAll(dirname)) - }() - - newfile := filepath.Join(dirname, "save.gpg") - // We do this to verify that the clobber flag is working. - is.NoError(ioutil.WriteFile(newfile, []byte(" "), 0755)) - is.NoError(kr.SavePrivate(newfile, true)) - - // Finally, we test loading the newly saved keyring - kr2, err := LoadKeyRing(newfile) - is.NoError(err) - is.Len(kr2.entities, len(kr.entities)) - - // Test that a known key exists. - kk, err := kr2.Key("123A4002462DC23B") - is.NoError(err) - is.Equal(kk.entity.Identities[key2Email].Name, key2Email) -} - -func TestKeyRing_SavePublic(t *testing.T) { - is := assert.New(t) - kr, err := LoadKeyRingFetcher(keyringFile, testPassphraseFetch) - is.NoError(err) - - is.Error(kr.SavePublic("testdata/noclobber.empty", false, false)) - - dirname, err := ioutil.TempDir("", "signature-") - if err != nil { - t.Fatal(err) - } - defer func() { - is.NoError(os.RemoveAll(dirname)) - }() - - newfile := filepath.Join(dirname, "save.gpg") - // We do this to verify that the clobber flag is working. - is.NoError(ioutil.WriteFile(newfile, []byte(" "), 0755)) - is.NoError(kr.SavePublic(newfile, true, false)) - - // Finally, we test loading the newly saved keyring - kr2, err := LoadKeyRing(newfile) - is.NoError(err) - is.Len(kr2.entities, len(kr.entities)) - - // Test that a known key exists. - kk, err := kr2.Key("123A4002462DC23B") - is.NoError(err) - is.Equal(kk.entity.Identities[key2Email].Name, key2Email) - - // Test that the key does NOT have a private component - _, err = kk.bestPrivateKey() - is.Error(err) -} - -func TestKeyRing_ArmoredRoundTrip(t *testing.T) { - // Test to make sure that we can both export and import ASCII Armored keys - is := assert.New(t) - kr, err := LoadKeyRingFetcher(keyringFile, testPassphraseFetch) - _ = !is.NoError(err) && is.FailNow("failed keyring load: %s", err) - - uid, err := ParseUserID("New Key ") - is.NoError(err) - newkey, err := CreateKey(uid) - is.NoError(err) - kr.AddKey(newkey) - - td, err := ioutil.TempDir("", "duffle-testkeyring") - is.NoError(err) - defer os.RemoveAll(td) - - dest := filepath.Join(td, "tkr_art.ascii") - is.NoError(kr.SavePublic(dest, false, true)) - - kr2 := CreateKeyRing(testPassphraseFetch) - handle, err := os.Open(dest) - if err != nil { - t.Fatal(err) - } - defer handle.Close() - - is.NoError(kr2.Add(handle, true)) - - for i, k := range kr2.Keys() { - uid, err := k.UserID() - is.NoError(err) - t.Logf("%d: %q", i, uid.String()) - } - - is.Equal(3, kr2.Len()) - - k2, err := kr2.Key("newkey@example.com") - is.NoError(err) - is.Equal(k2.Fingerprint(), newkey.Fingerprint()) -} - -func TestKeyRing_PrivateKeys(t *testing.T) { - is := assert.New(t) - k, err := LoadKeyRing(keyringFile) - is.NoError(err) - - keys := k.PrivateKeys() - is.Len(keys, 2) - - // Make sure we are not loading public keys - k, err = LoadKeyRing(publicKeyFile) - is.NoError(err) - - keys = k.PrivateKeys() - is.Len(keys, 0) -} - -func TestKeyRing_Keys(t *testing.T) { - is := assert.New(t) - k, err := LoadKeyRing(publicKeyFile) - is.NoError(err) - - keys := k.Keys() - is.Len(keys, 2) -} diff --git a/pkg/signature/keys.go b/pkg/signature/keys.go deleted file mode 100644 index 54d6aaaa..00000000 --- a/pkg/signature/keys.go +++ /dev/null @@ -1,143 +0,0 @@ -package signature - -import ( - "bytes" - "errors" - "fmt" - - "golang.org/x/crypto/openpgp" - "golang.org/x/crypto/openpgp/packet" -) - -// PassphraseFetcher receives a keyname, and is responsible for returning the associated passphrase -type PassphraseFetcher func(name string) ([]byte, error) - -// Key represents an individual signing key -// -// A key can be used to verify messages. If it also contains -// a private key, it can sign messages as well. -type Key struct { - PassphraseFetcher PassphraseFetcher - entity *openpgp.Entity - // selectedPrivateKey is reserved for use in cases where we want to - // set a specific private key instead of looking through the entity - // to load the key. This is necessary when choosing subkeys. - selectedPrivateKey *packet.PrivateKey -} - -var keyCreationConfig = packet.Config{ - RSABits: 3072, // Default keylength is only 2048. Following NIST recommendation for 3072. -} - -// CreateKey creates a new key for the given user ID -// -// User ID should be in the form "NAME (COMMENT) " -func CreateKey(user UserID) (*Key, error) { - e, err := openpgp.NewEntity(user.Name, user.Comment, user.Email, &keyCreationConfig) - if err != nil { - return nil, err - } - - // Okay, this is a little weird, but certain self-signing operations must be done before - // a private key can be used. If we need to use this key before writing it to disk, - // we'll need to do something like this: - /* - var buf bytes.Buffer - if err := e.SerializePrivate(&buf, &keyCreationConfig); err != nil { - return nil, err - } - */ - - return &Key{entity: e}, nil -} - -// UserID returns the UserID for this key -// -// For OpenPGP insiders: This returns the FIRST identity that appears to have a valid name. -// -// An error is returned if no parseable user ID can be found. -func (k *Key) UserID() (UserID, error) { - for i := range k.entity.Identities { - id, err := ParseUserID(i) - if err != nil { - // Skip this one. No point in erroring out. - continue - } - return id, nil - } - return UserID{}, errors.New("no parseable user identity attached to key") -} - -// Fingerprint returns a string representation of the fingerprint. -func (k *Key) Fingerprint() string { - fields := k.entity.PrimaryKey.Fingerprint - var buf bytes.Buffer - for i, b := range fields { - if i > 0 && i%2 == 0 { - buf.WriteString(" ") - } - buf.WriteString(fmt.Sprintf("%0X", b)) - - } - return buf.String() -} - -// CanSign indicates that a key is able to be used as a signer. -func (k *Key) CanSign() bool { - _, err := k.findPrivateKey() - return err == nil -} - -// bestPrivateKey will find a private key and decrypt it if necessary. -// -// If a specific key is pinned on selectedPrivateKey, that key will be used. -// Otherwise, it will use the strategy in findPrivateKey. -func (k *Key) bestPrivateKey() (*packet.PrivateKey, error) { - - pk, err := k.findPrivateKey() - if err != nil { - return pk, err - } - - // If key is not encrypted, return now. - if !pk.Encrypted { - return pk, nil - } - - return pk, decryptPassphrase(k.entity.PrimaryKey.KeyIdShortString(), pk, k.PassphraseFetcher) -} - -// findPrivateKey finds an acceptable private key for signing. -// -// If selectedPrivateKey is set this will use that key. Otherwise, it -// will start with the subkeys and seek for a signer, defaulting back to -// the top-level key. -// -// If no keys have the CanSign flag set, this will return an error. -// -// Finally, if no selectedPrivateKey is set, this will set the found -// key so that once it is unlocked we can avoid re-decrypting it. -func (k *Key) findPrivateKey() (*packet.PrivateKey, error) { - // If a private key has already been set, use that. - if k.selectedPrivateKey != nil { - return k.selectedPrivateKey, nil - } - e := k.entity - - // It may be the case that a master key cannot be used for signing. It is not - // clear how to test for that case. (in subkeys, you can do sk.Sig.FlagSign) - if e.PrivateKey != nil && e.PrivateKey.CanSign() { - k.selectedPrivateKey = e.PrivateKey - return e.PrivateKey, nil - } - for _, sk := range e.Subkeys { - // FlagSign checks if it is allowed to sign, while CanSign - // verifies that the algorithm is capable of signing. - if sk.Sig.FlagSign && sk.PrivateKey.CanSign() { - k.selectedPrivateKey = sk.PrivateKey - return sk.PrivateKey, nil - } - } - - return nil, errors.New("no signing key found") -} diff --git a/pkg/signature/keys_test.go b/pkg/signature/keys_test.go deleted file mode 100644 index 959b8e7a..00000000 --- a/pkg/signature/keys_test.go +++ /dev/null @@ -1,112 +0,0 @@ -package signature - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -const ( - test1pw = "password" - keyringFile = "testdata/keyring.gpg" - fullKeyID = "Test One (Signer) " - keyEmail = "test1@example.com" - key2Email = "test2@example.com" - fullExtraID = "Extra Key (Signer) " - publicKeyFile = "testdata/public.gpg" -) - -func TestKey(t *testing.T) { - is := assert.New(t) - k, err := LoadKeyRing(keyringFile) - is.NoError(err) - key, err := k.Key("test1@example.com") - is.NoError(err) - - key.PassphraseFetcher = testPassphraseFetch - - pk, err := key.bestPrivateKey() - is.NoError(err) - is.NotNil(pk) -} - -func TestCreateKey(t *testing.T) { - is := assert.New(t) - u := UserID{ - Name: "User Name", - Comment: "Comment", - Email: "email@example.com", - } - k, err := CreateKey(u) - is.NoError(err) - is.NotNil(k.entity.PrimaryKey) - is.NotNil(k.entity.PrivateKey) - kk, err := k.bestPrivateKey() - is.NoError(err) - is.NotNil(kk) -} - -func TestKey_NoKeyFound(t *testing.T) { - is := assert.New(t) - k, err := LoadKeyRing(keyringFile) - is.NoError(err) - _, err = k.Key("test1111@example.com") - is.Error(err) -} - -func TestKey_NoPassphrase(t *testing.T) { - is := assert.New(t) - k, err := LoadKeyRing(keyringFile) - is.NoError(err) - key, err := k.Key("test2@example.com") - is.NoError(err) - - // First, test without a fetcher - pk, err := key.bestPrivateKey() - is.NoError(err) - is.NotNil(pk) - - // Set a fetcher, and make sure it doesn't force a call. - key.PassphraseFetcher = func(name string) ([]byte, error) { - return []byte("this should fail if there is a password on the key"), nil - } - - pk, err = key.bestPrivateKey() - is.NoError(err) - is.NotNil(pk) -} - -func TestKey_UserID(t *testing.T) { - is := assert.New(t) - k, err := LoadKeyRing(keyringFile) - is.NoError(err) - key, err := k.Key("test2@example.com") - is.NoError(err) - - u, err := key.UserID() - is.NoError(err) - is.Equal(u.String(), "test2@example.com ") - - key, err = k.Key(fullKeyID) - is.NoError(err) - - u, err = key.UserID() - is.NoError(err) - is.Equal(u.String(), fullKeyID) -} - -func TestKey_Fingerprint(t *testing.T) { - expect := "5D76 712C E625 988A 272A 7E28 9B79 91DD 4037 8340" - - is := assert.New(t) - k, err := LoadKeyRing(keyringFile) - is.NoError(err) - key, err := k.Key("test2@example.com") - is.NoError(err) - - is.Equal(key.Fingerprint(), expect) -} - -func testPassphraseFetch(name string) ([]byte, error) { - return []byte(test1pw), nil -} diff --git a/pkg/signature/signature.go b/pkg/signature/signature.go deleted file mode 100644 index 70f6b6fa..00000000 --- a/pkg/signature/signature.go +++ /dev/null @@ -1,154 +0,0 @@ -package signature - -import ( - "bytes" - "crypto" - "errors" - "io" - - "github.com/docker/go/canonical/json" - - "golang.org/x/crypto/openpgp" - "golang.org/x/crypto/openpgp/armor" - "golang.org/x/crypto/openpgp/clearsign" - "golang.org/x/crypto/openpgp/packet" - - "github.com/deislabs/duffle/pkg/bundle" -) - -// ErrNoSignature indicates that no signature was found in a block of text -var ErrNoSignature = errors.New("no signature block in data") - -// Signer can sign bundles -// -// Signatures are OpenPGP Section 7 clearsigned blocks represented as ASCII-armored. -// To sign a bundle, the signer must be provided with the keys with which to sign. -// -// Signing is sensitive to whitespace. Thus, this package also takes on the responsibility -// of marshaling bundles into a canonical format before signing them. -// -// In addition to signing a bundle, the Signer can also attest an already-signed bundle. -// Attesting will calculate a detached signature on the same message body as the clearsigned -// representation. -type Signer struct { - key *Key - Config *packet.Config -} - -// NewSigner creates a new *Signer object. -// The key given here must be able to sign (create new signatures), which means it must point -// to a private key. -func NewSigner(key *Key) *Signer { - return &Signer{ - key: key, - Config: &packet.Config{ - DefaultHash: crypto.SHA256, - }, - } -} - -// Clearsign creates a new cleartext signature for the given bundle -// -// This creates a canonically generated bundle.json file, and then signs it. It is -// important that the format for the JSON file be identical each time. -func (s *Signer) Clearsign(b *bundle.Bundle) ([]byte, error) { - sk, data, err := s.prepareSign(b) - if err != nil { - return data, err - } - - // Clearsign the data - return s.sign(data, sk) -} - -// Attest generates an attestation (detached signature) for a signed bundle. -// -// This ONLY works on signed bundle files, and it requires the signed bundle -// as a []byte. It does not verify the signature on the signed block, nor does -// it parse the payload of the clearsigned block. Instead, it extracts the bytes -// from inside the block, and then re-signs that block, but with a detached -// signature. -// -// Where possible, the signature ought to be verified before it is attested. -// However, attestation does not require that the signature of the original -// block be validated. -func (s *Signer) Attest(signedBlock []byte) ([]byte, error) { - empty := []byte{} - block, _ := clearsign.Decode(signedBlock) - if block == nil { - return empty, ErrNoSignature - } - - pk, err := s.key.bestPrivateKey() - if err != nil { - return empty, err - } - - // We clearsign instead of using the openpgp.ArmoredDetachedSignText because the - // later does not handle subkeys at all. It ONLY allows using the private key on - // the main entity. Yet all the helper methods for that are unexported. Thus it - // is more expedient to use the clearsign package and then extract the detached - // signature from the block. - signature, err := s.sign(block.Plaintext, pk) - if err != nil { - return empty, err - } - newblock, _ := clearsign.Decode(signature) - if newblock == nil { - return empty, errors.New("could not decode block just created") - } - - body := bytes.NewBuffer(nil) - if _, err := body.ReadFrom(newblock.ArmoredSignature.Body); err != nil { - return empty, err - } - - out := bytes.NewBuffer(nil) - w, err := armor.Encode(out, openpgp.SignatureType, newblock.ArmoredSignature.Header) - if err != nil { - return empty, err - } - - _, err = io.Copy(w, body) - w.Close() - if err != nil { - return empty, err - } - - return out.Bytes(), err -} - -// prepareSign does work to prepare the bundle for signing. -func (s *Signer) prepareSign(b *bundle.Bundle) (*packet.PrivateKey, []byte, error) { - res := []byte{} - - // We only proceed if we find at least one key that can be used to sign. - pk, err := s.key.bestPrivateKey() - if err != nil { - return pk, res, err - } - - // We want a canonical representation of a serialized bundle, which is why we - // take the object. - data, err := json.MarshalCanonical(b) - if err != nil { - return pk, res, err - } - return pk, data, nil -} - -// sign generates a clearsign of the text. -func (s *Signer) sign(data []byte, key *packet.PrivateKey) ([]byte, error) { - res := []byte{} - buf, dest := bytes.NewBuffer(data), bytes.NewBuffer(nil) - out, err := clearsign.Encode(dest, key, s.Config) - if err != nil { - return res, err - } - if _, err := io.Copy(out, buf); err != nil { - out.Close() - return res, err - } - out.Close() - return dest.Bytes(), nil -} diff --git a/pkg/signature/signature_test.go b/pkg/signature/signature_test.go deleted file mode 100644 index c4d50fe2..00000000 --- a/pkg/signature/signature_test.go +++ /dev/null @@ -1,70 +0,0 @@ -package signature - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/deislabs/duffle/pkg/bundle" -) - -func TestSigner_Sign(t *testing.T) { - is := assert.New(t) - k := getKey(keyEmail, t) - is.NotNil(k) - - b := &bundle.Bundle{ - Name: "mybundle", - Version: "1.2.3", - } - - // Sign the bundle twice and make sure the sigs are the same. - s := NewSigner(k) - sig1, err := s.Clearsign(b) - is.NoError(err) - sig2, err := s.Clearsign(b) - is.NoError(err) - is.Equal(sig1, sig2) - is.NotEmpty(sig1) - - // Verify that the returned identities are the same - ring, err := LoadKeyRing(keyringFile) - is.NoError(err) - v := NewVerifier(ring) - signedBy, err := v.Verify(sig1) - is.NoError(err) - id1 := k.entity.Identities[fullKeyID] - id2 := signedBy.entity.Identities[fullKeyID] - is.NotNil(id1) - is.NotNil(id2) - is.Equal(id1.Name, id2.Name) -} - -func TestSigner_Attest(t *testing.T) { - is := assert.New(t) - k := getKey(keyEmail, t) - is.NotNil(k) - - b := &bundle.Bundle{ - Name: "mybundle", - Version: "1.2.3", - } - - // Sign the bundle twice and make sure the sigs are the same. - s := NewSigner(k) - sig, err := s.Clearsign(b) - is.NoError(err) - attestation, err := s.Attest(sig) - is.NoError(err) - is.Contains(string(sig), string(attestation)) -} - -func getKey(keyname string, t *testing.T) *Key { - k, err := LoadKeyRing(keyringFile) - assert.NoError(t, err) - - key, err := k.Key(keyname) - key.PassphraseFetcher = testPassphraseFetch - assert.NoError(t, err) - return key -} diff --git a/pkg/signature/testdata/README.md b/pkg/signature/testdata/README.md deleted file mode 100644 index 692dcbcf..00000000 --- a/pkg/signature/testdata/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# Instructions for Working with Keys - -If you are using GnuPG, this document explains how to build keyrings. - -## Building a New Keybox and Keyring - -``` -# Easy -$ gpg --no-default-keyring --keyring $(pwd)/keyring.kbx --quick-generate-key foo@example.com -# Complete (recommended) -$ gpg --no-default-keyring --keyring $(pwd)/keyring.kbx --full-generate-key -``` - -Export a binary keyring to an ASCII-armored keyring: - -``` -$ gpg --no-default-keyring --keyring $(pwd)/keyring.kbx --export-secret-keys --output ./keyring.gpg -``` - -Now the `keyring.gpg` is the file to import. - -To find out various key ids, this command is the easiest: - -``` -$ gpg --keyring ./keyring.gpg --no-default-keyring --list-keys --list-options=show-uid --with-colons -``` \ No newline at end of file diff --git a/pkg/signature/testdata/broken.gpg b/pkg/signature/testdata/broken.gpg deleted file mode 100644 index 1989dd516ea26ee2b74f6512d3b436293810a6d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1455 zcmZQzU{GLWWMJ}kib!Jsg6K6IfglWGLjY?(6C;>a$H2hymw|~v=GV@&BAMSZs$#yo z{g@iVcl})$@(ZX!0i>$pOU|tj%gu`3%MI@2{bpM9y>?m?P}l|}2Ly}^JV0U|kfjRd zGcmA$X*2@pf*g?0Oh#T7AUlrh}?9R(g8_vX@%zJQh-Z!m=n^%vQJ>IsS zLu@l6Yi){SbjkfY{WU>u*Di!_=lH65D8TBW>oyC698P5~t7YEyj}|yLbk6*~|7HfO z@ey^yz|@9J|BY(fb|y@nzBN13`(e_W2UgQXQ|IheJ+xTdc< z&D`O{*^2H(*KYG$bsHKLPVrvTV%{2Vwn#%I=<;(0K}H6~4ZzS#FGz>P2g4TCkksN5 z1^>KM1&!d$^t{v}O$D10ppcy}yU|8yt+;hV|aDdnTOq)mfRtq`5SxxUS@yLc`T=6;i9R$TC;9>&YI44|AXq3 z&wRoc@7!tWs6CatVy|?QaCh&OLQ9{yThc@~=FY!U-y7n|@^tZ+*S_zMPWZZ4PVb|% zM(hubYtv;fsV8~faQm@+iJEofeD6bMlS8h2v@7uEnZGcPsW|IGZ7ZX6{$2O*prr-b z$9oQTvT;=%U2yWEMSRKnjk{m$e^?te>6U)trA?->vlUsZyV;~aXe^TBGSi(lG4a=@ zlDB~oldR0QbG96AZ}phP5M=+=-f8QeoKNrMIm45)*Gn;{iTy3=2!8SYFy}LqyUy1) zuz}-*VJ9N3o}H4RI_cq&Joedt!+$b7<~Cc(@j~eK@0r_sljdJKcF%2b?%(&Wd#9YY zd453e4$scl7tMn|Ec~~?uXw`a$Y&;{mE{|GZWdlwtvWW-@7v#kj8>PWYXfh{XVz@Z zYCRuPwLSPi^`lFyYuY}1)h>KDNlZ%o#gDMVX6xN_08do88a`D9^Y*%j7Qa9ZtTG-HtfCNu>C35^FiK}o5w|1U5ujd7ped8%zL zOPWlRn{K7P+R9Uj`<_fLiONynYj~sP{rX&?jrW{t*9al?d#xuVb~&22Z;klxe)RqY zky{E86BrbNDvFPrnP)JST`YY&yYoQj%woC8*-x&ldUL;7-`L9wfYvxsSeNWJ{t=xHk(&E bptgpdna7h9{YClf#tT<}-)@&VVwnd3+O%v8 diff --git a/pkg/signature/testdata/extra.asc b/pkg/signature/testdata/extra.asc deleted file mode 100644 index 64356c42..00000000 --- a/pkg/signature/testdata/extra.asc +++ /dev/null @@ -1,58 +0,0 @@ ------BEGIN PGP PRIVATE KEY BLOCK----- -Comment: GPGTools - https://gpgtools.org - -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 -=JaeW ------END PGP PRIVATE KEY BLOCK----- diff --git a/pkg/signature/testdata/extra.gpg b/pkg/signature/testdata/extra.gpg deleted file mode 100644 index 64356c42..00000000 --- a/pkg/signature/testdata/extra.gpg +++ /dev/null @@ -1,58 +0,0 @@ ------BEGIN PGP PRIVATE KEY BLOCK----- -Comment: GPGTools - https://gpgtools.org - -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 -=JaeW ------END PGP PRIVATE KEY BLOCK----- diff --git a/pkg/signature/testdata/extra.kbx b/pkg/signature/testdata/extra.kbx deleted file mode 100644 index cc348d24fa45ed384725cfdb994a6940f2086812..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1457 zcmZQzU{GLWWMJ}kib!Jsg6MVYxIq}ih5*)yOpIVw9RmZ)e+DK7nTx^>UzyDoH1~#Ay1&}J?lNuYZ^Zkl^Rh)fGwM_mKqY{GwP}l|}2Ly}^JV0U|kfjdh zGcmA$X*2@p0uzwXOh#T7AX|%(gW>hbDCXBoq|=tN-^u6}F~4oB+?sZ1X36h}tLa+V zIe~GvPVC~C_-(i4^q6c;SC8pB(R{O8E>~<^%dN+~h1qONMB!{fdBZ#LMe7}Y{V3$> zKJiE*aKdQ~zQ@gvYYkbfZ?M!Z$Xl8}ckNp_vnLUar=$XpT(UoWzx&JKeGkJ!WgpDg ztnYmGM6XlB=0%;2dvo2mbUV1?~Bt96nsJm8_ z6eTKnr&cOx1ZSq_r50%_*rbBQ3>{J{5_1c3QuUJabM2tom>{9t$>_%-%*e%HA*RTJ zEh<@=KtawV&CJTp$-&OdD$2#l!Obeh#0*R(a*Ryk4GdhI0^usG_K^kX1wp1^K)Uq?&_y{yz>=)u3nU}<&s_C<8#8Z*RZhe{d?}4eA&vr z>)eGiJGV?Np3u>8@|?iD_v>%(?lSn3sb{j=?825$kz5>Vu5oYAm{>$@w({O&GpBUSmx9+FHC^TJbe%qschc*PWrI60z0I1F^HJ94-(oDUL%H<6+8%?;PspC=`4lD&gIo1Cl7qqjDB4s&2ru9c!QcID%B?5zQZ3myXTWCU%!qmvz#*f zLAmsNbLEIolVrVVI)7)z+gx48qN|kI(6#*EMWaI*Lh}79G_>9wXinA>x?b<|$y8y{ zt7HqY({HTz{C7O06_OTL#k|pbb09dib~2i=NB|R>0)d3agPfo|vp5)*RkcVjZja=i z{^k5KyDJ}hmg{n!`!d;}K-VG4AyHE()c%iU{fd{%_Wn{-x|3)ad{nCPy>Z@=)qP^0 zA{K@Ot1f2Qa(}aW!Gu+oS}uoY=|(PoUwm3zS9eLL;Kclwv)4@Eh!1Xj^IE34*Dmw? zmGg&7rQ#26o_mQ&>MEOoezTt3iuLEEjRRlIwaD79?(Uczx<&o&%C9-uI(!%RFijJk zG)3WmL`zqFmB5M3%f+4ZDkC;;`!msw>DZMEF|#ipR9#Xxtu67!&UX(v_&p5xk$$j#&=6mv+leTM3Na=YkeplYr$|^-ndRpk`D&bU1 dZdiiqJF2to)S_%(O5WiC(9M z&5Jr4%U_u5yxsV>#B$@#3bDtlc$AH1ZGI8GbCp8nes3A0htV^c*1XL)lDb6n+;>4W&GWx&P=1+MP?PQqq^MrXx zgpk2c=h&v#FJ3VJH9R%(X~tys}5oY9Kun<#ZxhU-LmDy}T4)=$8ksnLcjs~4>U}I$h z1uK&@Gb=YI2Rk#XC>JLOH>(&EGb00&93zu>0|OVQ08DXjEBpT(?ZZ7&buU}#Ce#$X z?OCj)aV6h1<9)}Rp9=$aS3lL`ov-k7^`eX|m+S%`pA(+FhJ|(S-*eyO%U1SX=PsPt zxn*kcgpQ7r=LF`xUw?adm%*P*J(JyL7q)zg#3E|5mG>s2xjXj+&vMY7 z%shL+`JTW|*-N$!zR9l>N+VW2t$%#$^j^LnZ&VvvreC`4c0KmTp4{~LWg=Z$*Gag% z6uj=J=_-Gx>-2%VlU{Eu8;m;DW+-3dU;k(3^tXv|%2ys;?bz26u2gg{r@O>UaM2&> zZPuKekFq}h7GrrG%BA<^?u*Euxsxs*Sz~n2;Oo811ygqAz1VE(QMY08jl9&Q*PiY~ zq{Ku2|1oFW-ZuYDN(-mRq9-d%eXcp{R2*ZnF_m6tX!CSs+L6=ExvjaD0k7Y5PiHAK zb}p|DIeFl-X7uYCX_o6&$5Suy7_ErZ;7&^`t9d9>IP=*8#l|aX-c<_x4$l|s23?TT zPn=P_M5dA_f#++)0&&I{?@SDDu8P>t_E)&>wKwmHB5~QbAFj-eIjF04uCXrr=NIu$ zx$=HnSQkCsRjD@d_8tDn**%{;%gp3C#5g0WrV23C{rYumndOw(56Y$In=40znk4H@ z)A>6y-sb8$7G0&xhOXuRE*c%m5R&g-p`rEeKy$L5(DizsPo@fsUL{+Ioql7z=fC42 zt&p_1D&~#en*+fqv6In^MFN<>6bK|R9^{1NnZ?1dtg1zNaeE~9^e^X^*sP#7w)dB!(w#)Z;G^-Bube+zDiwcl^V~~J zQdijw^qckMR;)iSZ5;Swu0_^Ex&cHUcKG-GkL3%}!U`##m2RGZ~bR=<>d ue8&0RP41JICAUj4Hs6!qoU~nQLQ2nT@w@V_R#quu($hjeR|%(DasvQkX*9I} diff --git a/pkg/signature/testdata/fail-signed.json b/pkg/signature/testdata/fail-signed.json deleted file mode 100644 index 5f57fa2d..00000000 --- a/pkg/signature/testdata/fail-signed.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "color": "yellow" -} \ No newline at end of file diff --git a/pkg/signature/testdata/fail-signed.json.asc b/pkg/signature/testdata/fail-signed.json.asc deleted file mode 100644 index e9bfd3e3..00000000 --- a/pkg/signature/testdata/fail-signed.json.asc +++ /dev/null @@ -1,18 +0,0 @@ ------BEGIN PGP SIGNED MESSAGE----- -Hash: SHA512 - -{ - "color": "yellow" -} ------BEGIN PGP SIGNATURE----- -Comment: GPGTools - https://gpgtools.org - -iQEzBAEBCgAdFiEE+yumTPTtBoSSRd/j3NX15e8yw0UFAlutXpgACgkQ3NX15e8y -w0WyEQgAka0+O3xj1T2cA5lf6X+IXGv0CIEyh3JYNu8eC0d1t01VqVQKgmS8ARGc -rQqwJkHdVzVJ17cgRkpKHMBazJsZm1Si1/OCbUgdr0BjaO8EdIBPdlXecSY4CUir -4QmjdvfH6rMMPS58LcOKNge9YbJjskS7O8bUu6aY0DCHka5E+EY0BZySeCgJnthh -2sLTcbChLu4cY8GIciNlLJHwa9HKjBwOx8Juv/77dE5xM8hj4rt2IG539U+HzgN/ -T1awE6uOoB4Fwp++cfZmyFBe90mO6Q1kcjR7p/easH7LFOJr50D9kd392kxhM6Fa -IgQzu/8g/NOBD0BDl26ioRMg+COBSA== -=5eId ------END PGP SIGNATURE----- diff --git a/pkg/signature/testdata/keyring.asc b/pkg/signature/testdata/keyring.asc deleted file mode 100644 index 987ab959..00000000 --- a/pkg/signature/testdata/keyring.asc +++ /dev/null @@ -1,113 +0,0 @@ ------BEGIN PGP PRIVATE KEY BLOCK----- -Comment: GPGTools - https://gpgtools.org - -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 -Zl0Jm+fba+wIqFc= -=VgQe ------END PGP PRIVATE KEY BLOCK----- diff --git a/pkg/signature/testdata/keyring.gpg b/pkg/signature/testdata/keyring.gpg deleted file mode 100644 index 6b4d12621cbf634b7aaa267aed6d1685f0b69f49..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5147 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 L%Z^9rWxD?Y%(1_? diff --git a/pkg/signature/testdata/keyring.kbx b/pkg/signature/testdata/keyring.kbx deleted file mode 100644 index 735c78dd7ef6a4abbc575b9b1d597c7f262a674f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2864 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 diff --git a/pkg/signature/testdata/keyring.kbx~ b/pkg/signature/testdata/keyring.kbx~ deleted file mode 100644 index 7c69b8a11dc9a92a50db86b27bf2ffb9a972c627..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1455 zcmZQzU{GLWWMJ}kib!Jsg6K86KS3D8h5*)nCPpx;j)8&YF9Q>U%&(nkMKZr-RK2JTQ>~z+=hfY%!d6tFUdAVuBnb?zg4^Gberqyus>hZG2+tzc4 zZDwSxO>vAaxqqj>CdlpDh4AehUo{T}SUq&zW?_)SsqAI7%-jCa0_TR#ncw%{%wRP> zqHY+N+K}nLQEl7KgsIcFW@maoOj`56YPx9ZoV}{2c3sYjTdSed(bym(by-u~h~c$T z#HkZi)l0H&rex;6xEVGf_pF^$e_qMtLcYQY`=?9jT>QMOH>u-e_@DTO_+0_l^i`*s zJDfOM(Y@%}ZGNk6L!-he-fLRSTf@y3X{ZEUe$F7s$iTP(7<%ai>5%wf*rFPeT3n*w zpO>nj5uBNxms+H$U{eAVGIU6-NX#wBN!3fv&$WYUVuFNmC!-&WFe4X(g_t4>wuoe9 z0tGmeG&3tVCkHz-t0)&I2REx26EiT8$T2dBH!yH<3czCBGK>A6?&M#b7WuXtH%V}R z_Fi~a>xbpXi_C8fudX`tuv^oTJLEHeWAER~><>DR<#a4uG?iCt)-BIj)7kESP@VFb zPx#`UJ1rfxr*c>9m2ML5?!8iI=`(jrn&`&d`FHAjLp)iYF8=b`_x;fcU-!!CeU#RS z{h@Jfy6h$OB+naeKejJXvyPnaeaLKb$d!+F1^ztq7v?b)XI-dmWt7gp>mDAov>^L< z&%sVMuBxL8PF}Q#FIm5F_lx}xYojLJ(oej!$uxGhB5QRwoAd{bMRHtby3-~m{`yq% zHZWq6mHBqgmc#9>976`hcyji7Ddse>zeOFvFWw*Kd}eaj`T7Pn z24-NKFziI6)w5GFR3|+=lE*&#Z}?A!$J}O1IbI0e{ylShZ_@lr$L_f;&i(t|b?=n( zHqQ^}-Qn5!`l5O8hlT$Z_!Uoh9Qn+ow6c67&&|T?s#V8k`hELbkkRV0bZy`b`OKQF zS*_~4B~dvFd<}2ZykDOywDF!(?HVDZey{a}#4bnk_N@{B-H+bC zAaY9~VgiFgP(|@^GxH3lvWul}XLlaxoLMY4Is3_#Rd4QhGs-jsSQ|K8xOVK-J-giL zFHG~YPArnSm14cUeOX^~(>*(e4Vgk`ZX7hayE`V3&s1UKl$jF8&zm%_;K)k7b#>Co z@=HriuNWkpvc7aXSD@&1%umtnXU(N|WO3~l*wT_A%J@_NV2<0P)o0kf_@6|o7bkcf z5tr%wHUCwrWzD>buI~wYwv{{YPda^Q)q%~{#w(dj=bzuPt0*(LHtqNAtNbVBD*k%F b5|r0%AErk~vUPP&oLx12%B7d)9^y&>S0Zed diff --git a/pkg/signature/testdata/noclobber.empty b/pkg/signature/testdata/noclobber.empty deleted file mode 100644 index e69de29b..00000000 diff --git a/pkg/signature/testdata/public.asc b/pkg/signature/testdata/public.asc deleted file mode 100644 index 28422466..00000000 --- a/pkg/signature/testdata/public.asc +++ /dev/null @@ -1,57 +0,0 @@ ------BEGIN PGP PUBLIC KEY BLOCK----- -Comment: GPGTools - https://gpgtools.org - -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 -7AioVw== -=UYus ------END PGP PUBLIC KEY BLOCK----- diff --git a/pkg/signature/testdata/public.gpg b/pkg/signature/testdata/public.gpg deleted file mode 100644 index 65a7ded6fcc8a7206cd57c71d64a889b4f6ddb2e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2452 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 WO(mQeTbTB`1g3u<@|)qyL;nW}Gi>1i diff --git a/pkg/signature/testdata/signed.json b/pkg/signature/testdata/signed.json deleted file mode 100644 index 2ee43900..00000000 --- a/pkg/signature/testdata/signed.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "name": "example_bundle" -} \ No newline at end of file diff --git a/pkg/signature/testdata/signed.json.asc b/pkg/signature/testdata/signed.json.asc deleted file mode 100644 index 592decc9..00000000 --- a/pkg/signature/testdata/signed.json.asc +++ /dev/null @@ -1,18 +0,0 @@ ------BEGIN PGP SIGNED MESSAGE----- -Hash: SHA512 - -{ - "name": "example_bundle" -} ------BEGIN PGP SIGNATURE----- -Comment: GPGTools - https://gpgtools.org - -iQFGBAEBCgAwFiEEXXZxLOYlmIonKn4om3mR3UA3g0AFAlutYBUSHHRlc3QyQGV4 -YW1wbGUuY29tAAoJEJt5kd1AN4NA60IIAITsNIUO7wcRjS1ZyZBW58zFmm0wxdCw -zvV5OUhD1pgPE9gVYKIZE9uGNovrGzwzfQFeboYtS7skoUrw7uBNY4OIxfXKrsuV -J3C/NB07NVoWJWJcFs/h4xwvcMGyRdh867NdK0rf9vwQYMHAf5l7re0qNAKftLi5 -5/v/ydtPXuDTSFBTn2jU42kEU4Z0ybCpwcXZpTySRsnF01IB/TC7rRBZ5vUvGsdP -V84CvcFAsx09Z1qi41DStk0CA2cFrtfUwg3OxIciz7sIYGgU76p3Ow7KoJM0V5fa -velYfFIQ9HrsiNgdy6anWDa5S/tc5/DLfFWNtHfm/uDSKK8EmNk2HY4= -=TvO4 ------END PGP SIGNATURE----- diff --git a/pkg/signature/user_id.go b/pkg/signature/user_id.go deleted file mode 100644 index 30c902eb..00000000 --- a/pkg/signature/user_id.go +++ /dev/null @@ -1,67 +0,0 @@ -package signature - -import ( - "errors" - "fmt" - "regexp" - "strings" -) - -// UserID models a user ID -// -// In OpenPGP, this is usually structured as `NAME (COMMENT) ` -type UserID struct { - Name, Comment, Email string -} - -// String reprsents the UserID as an OpenPGP user string -func (u UserID) String() string { - comment := "" - if u.Comment != "" { - comment = fmt.Sprintf(" (%s)", u.Comment) - } - return fmt.Sprintf("%s%s <%s>", u.Name, comment, u.Email) -} - -// Not sure about using [[:print]], so I'll leave this here in case that one does not work for some edge case. -//var userIDrx = regexp.MustCompile(`^([\w\s\.\+\-\_@]+)(?:\s*?\(([\w\s\.\+\-\_@]*)\))?(?:\s+\<([a-zA-Z0-9\.\+\-\_]+@[a-zA-Z0-9\.\+\-\_]+)\>)?$`) -// The regexp breaks down this way: -// - There is a mandatory match of a set of printable chars. This is the "Name" section. -// - Next, there is an optional match of a string inside of parens "(some stuff)". This is the "Comment" section. -// - Finally, there is an optional match of an email address enclosed in angle brackets "". This is the "Email" section. -var userIDrx = regexp.MustCompile(`^([[:print:]]+?)(?:\s*?\(([[:print:]]*?)\))?(?:\s+\<([a-zA-Z0-9\.\+\-\_]+@[a-zA-Z0-9\.\+\-\_]+)\>)?$`) - -// emailish captures whether a string looks like an email address. -// It is not particularly strict because the spec says the address should conform to RFC 2282, but does not require that it be valid. -var emailish = regexp.MustCompile(`^[a-zA-Z0-9\.\+\-\_]+@[a-zA-Z0-9\.\+\-\_]+$`) - -// ParseUserID attempts to parse the format `NAME (COMMENT) ` into three fields. -// -// - If name is empty, this will return an error -// - If comment is omitted or empty, the empty string is returned -// - If email is omitted, this will check the name to see if it looks like an email address, and use it or else error out -func ParseUserID(id string) (UserID, error) { - matches := userIDrx.FindStringSubmatch(id) - ret := UserID{} - - if len(matches) != 4 { - return ret, errors.New("invalid ID format") - } - - if matches[1] == "" { - return ret, errors.New("name field is required") - } - ret.Name = strings.TrimSpace(matches[1]) - ret.Comment = strings.TrimSpace(matches[2]) - - if matches[3] == "" { - if !emailish.MatchString(ret.Name) { - return ret, errors.New("email is required") - } - ret.Email = ret.Name - } else { - ret.Email = strings.TrimSpace(matches[3]) - } - - return ret, nil -} diff --git a/pkg/signature/user_id_test.go b/pkg/signature/user_id_test.go deleted file mode 100644 index 881ec864..00000000 --- a/pkg/signature/user_id_test.go +++ /dev/null @@ -1,80 +0,0 @@ -package signature - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestParseUserID(t *testing.T) { - is := assert.New(t) - for _, tt := range []struct { - in string - expect UserID - fail bool - }{ - { - in: "NAME (COMMENT) ", - expect: UserID{Name: "NAME", Comment: "COMMENT", Email: "EMAIL@ADDRESS"}, - }, - { - in: "NAME ", - expect: UserID{Name: "NAME", Email: "EMAIL@ADDRESS"}, - }, - { - in: "EMAIL@ADDRESS", - expect: UserID{Name: "EMAIL@ADDRESS", Email: "EMAIL@ADDRESS"}, - }, - { - in: "", - fail: true, - }, - { - in: "This is a long name (this is a long comment) ", - expect: UserID{Name: "This is a long name", Comment: "this is a long comment", Email: "this.is+email@some.long.name.com"}, - }, - { - in: "me () ", - expect: UserID{Name: "me", Comment: "", Email: "me@ts"}, - }, - { - in: "me () me@ts", - fail: true, - }, - { - // So it's unclear what we want to do in this case. We can either allow '()' in names or - // we can read this as "blank name and comment". For now, we'll opt for the former. - in: "(foo) ", - expect: UserID{Name: "(foo)", Email: "email@example"}, - }, - } { - res, err := ParseUserID(tt.in) - if err != nil { - if tt.fail { - continue - } - t.Errorf("failed on %s: %s", tt.in, err) - continue - } - - if tt.fail { - t.Errorf("expected %s to fail", tt.in) - } - is.Equal(res, tt.expect, "failed on %s", tt.in) - } -} - -func TestUserID_String(t *testing.T) { - u := UserID{ - Name: "Ahab", - Comment: "Captain", - Email: "ahab@example.com", - } - assert.Equal(t, u.String(), "Ahab (Captain) ") - - u = UserID{ - Name: "Captain Ahab", - Email: "cptahab@hotmail.com", - } - assert.Equal(t, u.String(), "Captain Ahab ") -} diff --git a/pkg/signature/verifier.go b/pkg/signature/verifier.go deleted file mode 100644 index 221adb74..00000000 --- a/pkg/signature/verifier.go +++ /dev/null @@ -1,68 +0,0 @@ -package signature - -import ( - "bytes" - "encoding/json" - - "golang.org/x/crypto/openpgp" - "golang.org/x/crypto/openpgp/clearsign" - - "github.com/deislabs/duffle/pkg/bundle" -) - -// Verifier provides tools to verify a signed bundle. -// To verify a bundle, the signer may be given either a specific key, or a keyring. -// -// Because OpenPGP signing and verifying of cleartext is whitespace sensitive, this -// also provides tools to extract the exact text that was given. -type Verifier struct { - keys *KeyRing -} - -// NewVerifier creates a new *Verifier. -// -// They KeyRing is expected to contain all of the keys that are allowed to be used -// in verifying. -func NewVerifier(keyRing *KeyRing) *Verifier { - return &Verifier{ - keys: keyRing, - } -} - -// Verify takes a signed bundle and verifies that the signature was signed by a known key -// -// This will return the key that the data was signed with. -func (v *Verifier) Verify(data []byte) (*Key, error) { - // The second arg is the leftover text, which we don't care about. - block, _ := clearsign.Decode(data) - if block == nil { - return nil, ErrNoSignature - } - ent, err := v.verifyBlock(block) - return &Key{entity: ent}, err -} - -// Extract verifies the signature against the keyring, and then returns the bundle -// -// This will return the bundle that was signed and the key it was signed with. -func (v *Verifier) Extract(data []byte) (*bundle.Bundle, *Key, error) { - block, _ := clearsign.Decode(data) - if block == nil { - return nil, nil, ErrNoSignature - } - - ent, err := v.verifyBlock(block) - if err != nil { - return nil, nil, err - } - res := &bundle.Bundle{} - err = json.Unmarshal(block.Plaintext, res) - return res, &Key{entity: ent}, err -} - -// verifyBlock takes a block and verifies it as a detached signature. -func (v *Verifier) verifyBlock(block *clearsign.Block) (*openpgp.Entity, error) { - buf := bytes.NewBuffer(block.Bytes) - el := openpgp.KeyRing(v.keys.entities) - return openpgp.CheckDetachedSignature(el, buf, block.ArmoredSignature.Body) -} diff --git a/pkg/signature/verifier_test.go b/pkg/signature/verifier_test.go deleted file mode 100644 index 42c3b0d0..00000000 --- a/pkg/signature/verifier_test.go +++ /dev/null @@ -1,74 +0,0 @@ -package signature - -import ( - "io/ioutil" - "testing" - - "github.com/stretchr/testify/assert" -) - -const ( - signedFile = "testdata/signed.json.asc" - failedSignedFile = "testdata/fail-signed.json.asc" -) - -func TestVerifier_Verify(t *testing.T) { - is := assert.New(t) - - data, err := ioutil.ReadFile(signedFile) - is.NoError(err) - - keyring, err := LoadKeyRing(keyringFile) - is.NoError(err) - - v := NewVerifier(keyring) - k, err := v.Verify(data) - is.NoError(err) - is.NotNil(k.entity.Identities[key2Email]) -} -func TestVerifier_VerifyFail(t *testing.T) { - is := assert.New(t) - - data, err := ioutil.ReadFile(failedSignedFile) - is.NoError(err) - - keyring, err := LoadKeyRing(keyringFile) - is.NoError(err) - - // The file was signed by a user not in the keyring, so should fail. - v := NewVerifier(keyring) - _, err = v.Verify(data) - is.Error(err) - is.Contains(err.Error(), "signature made by unknown entity") -} - -func TestVerifier_Extract(t *testing.T) { - is := assert.New(t) - - data, err := ioutil.ReadFile(signedFile) - is.NoError(err) - - keyring, err := LoadKeyRing(keyringFile) - is.NoError(err) - v := NewVerifier(keyring) - - b, k, err := v.Extract(data) - is.NoError(err) - is.NotNil(k.entity.Identities[key2Email]) - is.Equal("example_bundle", b.Name) -} - -func TestVerifier_ExtractFail(t *testing.T) { - is := assert.New(t) - - data, err := ioutil.ReadFile(failedSignedFile) - is.NoError(err) - - keyring, err := LoadKeyRing(keyringFile) - is.NoError(err) - v := NewVerifier(keyring) - - _, _, err = v.Extract(data) - is.Error(err) - is.Contains(err.Error(), "signature made by unknown entity") -}