Skip to content

Commit

Permalink
Merge pull request sigstore#14 from dlorenc/munge
Browse files Browse the repository at this point in the history
Change the naming scheme to use a ".cosign" suffix.
  • Loading branch information
dlorenc authored Feb 15, 2021
2 parents 69ffa38 + 36d8fd9 commit e3d4b01
Show file tree
Hide file tree
Showing 7 changed files with 380 additions and 6 deletions.
3 changes: 1 addition & 2 deletions cmd/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,7 @@ func sign(ctx context.Context, keyPath string,
}

// sha256:... -> sha256-...
munged := strings.ReplaceAll(get.Descriptor.Digest.String(), ":", "-")
dstTag := ref.Context().Tag(munged)
dstTag := ref.Context().Tag(cosign.Munge(get.Descriptor))

fmt.Fprintln(os.Stderr, "Pushing signature to:", dstTag.String())
return cosign.Upload(signature, payload, dstTag)
Expand Down
6 changes: 2 additions & 4 deletions cmd/upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
"flag"
"io/ioutil"
"os"
"strings"

"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
Expand Down Expand Up @@ -80,9 +79,8 @@ func upload(ctx context.Context, sigRef, imageRef string) error {
if err != nil {
return err
}
// sha256:... -> sha256-...
munged := strings.ReplaceAll(get.Descriptor.Digest.String(), ":", "-")
dstTag := ref.Context().Tag(munged)

dstTag := ref.Context().Tag(cosign.Munge(get.Descriptor))

payload, err := cosign.Payload(get.Descriptor, nil)
if err != nil {
Expand Down
96 changes: 96 additions & 0 deletions pkg/cosign/fetch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
Copyright The Cosign Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package cosign

import (
"fmt"
"io/ioutil"
"net/http"
"strings"

"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/google/go-containerregistry/pkg/v1/remote/transport"
"github.com/google/go-containerregistry/pkg/v1/types"
)

type SignedPayload struct {
Base64Signature string
Payload []byte
}

func Munge(desc v1.Descriptor) string {
// sha256:... -> sha256-...
munged := strings.ReplaceAll(desc.Digest.String(), ":", "-")
munged += ".cosign"
return munged
}

func FetchSignatures(ref name.Reference) ([]SignedPayload, *v1.Descriptor, error) {
var idxRef name.Reference
targetDesc, err := remote.Get(ref, remote.WithAuthFromKeychain(authn.DefaultKeychain))
if err != nil {
return nil, nil, err
}
idxRef = ref.Context().Tag(Munge(targetDesc.Descriptor))

rdesc, err := remote.Get(idxRef, remote.WithAuthFromKeychain(authn.DefaultKeychain))
if err != nil {
if te, ok := err.(*transport.Error); ok && te.StatusCode == http.StatusNotFound {
return nil, nil, fmt.Errorf("manifest not found: %s", idxRef)
}
return nil, nil, err
}

if rdesc.MediaType != types.DockerManifestSchema2 {
return nil, nil, fmt.Errorf("unsupported media type: %s", rdesc.MediaType)
}
descriptors, err := Descriptors(idxRef)
if err != nil {
return nil, nil, err
}

signatures := []SignedPayload{}
for _, desc := range descriptors {
base64sig, ok := desc.Annotations[sigkey]
if !ok {
continue
}
l, err := remote.Layer(ref.Context().Digest(desc.Digest.String()), remote.WithAuthFromKeychain(authn.DefaultKeychain))
if err != nil {
return nil, nil, err
}

r, err := l.Compressed()
if err != nil {
return nil, nil, err

}

payload, err := ioutil.ReadAll(r)
if err != nil {
return nil, nil, err
}
signatures = append(signatures, SignedPayload{
Payload: payload,
Base64Signature: base64sig,
})
}
return signatures, &targetDesc.Descriptor, nil
}
42 changes: 42 additions & 0 deletions pkg/cosign/payload.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
Copyright The Cosign Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package cosign

import (
"encoding/json"

v1 "github.com/google/go-containerregistry/pkg/v1"
)

func Payload(img v1.Descriptor, a map[string]string) ([]byte, error) {

simpleSigning := SimpleSigning{
Critical: Critical{
Image: Image{
DockerManifestDigest: img.Digest.Hex,
},
Type: "cosign container signature",
},
Optional: a,
}

b, err := json.Marshal(simpleSigning)
if err != nil {
return nil, err
}
return b, err
}
100 changes: 100 additions & 0 deletions pkg/cosign/remote.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package cosign

import (
"bytes"
"encoding/base64"
"io"
"io/ioutil"
"net/http"

"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/empty"
"github.com/google/go-containerregistry/pkg/v1/mutate"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/google/go-containerregistry/pkg/v1/remote/transport"
"github.com/google/go-containerregistry/pkg/v1/types"
)

func Descriptors(ref name.Reference) ([]v1.Descriptor, error) {
img, err := remote.Image(ref, remote.WithAuthFromKeychain(authn.DefaultKeychain))
if err != nil {
return nil, err
}
m, err := img.Manifest()
if err != nil {
return nil, err
}

return m.Layers, nil
}

func Upload(signature, payload []byte, dstTag name.Reference) error {
l := &staticLayer{
b: payload,
mt: types.OCIContentDescriptor,
}
base, err := remote.Image(dstTag, remote.WithAuthFromKeychain(authn.DefaultKeychain))
if err != nil {
if te, ok := err.(*transport.Error); ok {
if te.StatusCode != http.StatusNotFound {
return te
}
base = empty.Image
} else {
return err
}
}

img, err := mutate.Append(base, mutate.Addendum{
Layer: l,
Annotations: map[string]string{
sigkey: base64.StdEncoding.EncodeToString(signature),
},
})
if err != nil {
return err
}

if err := remote.Write(dstTag, img, remote.WithAuthFromKeychain(authn.DefaultKeychain)); err != nil {
return err
}
return nil
}

type staticLayer struct {
b []byte
mt types.MediaType
}

func (l *staticLayer) Digest() (v1.Hash, error) {
h, _, err := v1.SHA256(bytes.NewReader(l.b))
return h, err
}

// DiffID returns the Hash of the uncompressed layer.
func (l *staticLayer) DiffID() (v1.Hash, error) {
h, _, err := v1.SHA256(bytes.NewReader(l.b))
return h, err
}

// Compressed returns an io.ReadCloser for the compressed layer contents.
func (l *staticLayer) Compressed() (io.ReadCloser, error) {
return ioutil.NopCloser(bytes.NewReader(l.b)), nil
}

// Uncompressed returns an io.ReadCloser for the uncompressed layer contents.
func (l *staticLayer) Uncompressed() (io.ReadCloser, error) {
return ioutil.NopCloser(bytes.NewReader(l.b)), nil
}

// Size returns the compressed size of the Layer.
func (l *staticLayer) Size() (int64, error) {
return int64(len(l.b)), nil
}

// MediaType returns the media type of the Layer.
func (l *staticLayer) MediaType() (types.MediaType, error) {
return l.mt, nil
}
69 changes: 69 additions & 0 deletions pkg/cosign/sign.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
Copyright The Cosign Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package cosign

import (
"crypto/ed25519"
"encoding/pem"
"fmt"
"io/ioutil"

"github.com/theupdateframework/go-tuf/encrypted"
)

const (
pemType = "ENCRYPTED COSIGN PRIVATE KEY"
sigkey = "dev.cosignproject.cosign/signature"
)

func LoadPrivateKey(keyPath string, pass []byte) (ed25519.PrivateKey, error) {
b, err := ioutil.ReadFile(keyPath)
if err != nil {
return nil, err
}

// Decrypt first
p, _ := pem.Decode(b)
if p.Type != pemType {
return nil, fmt.Errorf("unsupported pem type: %s", p.Type)
}

priv, err := encrypted.Decrypt(p.Bytes, pass)
if err != nil {
return nil, err
}
return ed25519.PrivateKey(priv), nil
}

type SimpleSigning struct {
Critical Critical
Optional map[string]string
}

type Critical struct {
Identity Identity
Image Image
Type string
}

type Identity struct {
DockerReference string `json:"docker-reference"`
}

type Image struct {
DockerManifestDigest string `json:"Docker-manifest-digest"`
}
Loading

0 comments on commit e3d4b01

Please sign in to comment.