Skip to content

Commit

Permalink
Merge pull request #366 from tri-adam/dsse-refactor
Browse files Browse the repository at this point in the history
refactor: use DSSE wrappers from sigstore
  • Loading branch information
tri-adam committed Jun 24, 2024
2 parents d63ee14 + fc6a037 commit d242f61
Show file tree
Hide file tree
Showing 13 changed files with 93 additions and 164 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ require (
github.com/google/go-containerregistry v0.19.2
github.com/google/uuid v1.6.0
github.com/sebdah/goldie/v2 v2.5.3
github.com/secure-systems-lab/go-securesystemslib v0.8.0
github.com/sigstore/sigstore v1.8.4
github.com/spf13/cobra v1.8.1
github.com/spf13/pflag v1.0.5
Expand All @@ -20,6 +19,7 @@ require (
github.com/letsencrypt/boulder v0.0.0-20230907030200-6d76a0f91e1e // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/secure-systems-lab/go-securesystemslib v0.8.0 // indirect
github.com/sergi/go-diff v1.2.0 // indirect
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect
golang.org/x/crypto v0.23.0 // indirect
Expand Down
188 changes: 67 additions & 121 deletions pkg/integrity/dsse.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2022-2023, Sylabs Inc. All rights reserved.
// Copyright (c) 2022-2024, Sylabs Inc. All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the LICENSE.md file
// distributed with the sources of this project regarding your rights to use or distribute this
// software.
Expand All @@ -9,27 +9,39 @@ import (
"bytes"
"context"
"crypto"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"

"github.com/secure-systems-lab/go-securesystemslib/dsse"
"github.com/sigstore/sigstore/pkg/signature"
"github.com/sigstore/sigstore/pkg/signature/dsse"
"github.com/sigstore/sigstore/pkg/signature/options"
)

const metadataMediaType = "application/vnd.sylabs.sif-metadata+json"

type dsseEncoder struct {
es *dsse.EnvelopeSigner
h crypto.Hash
payloadType string
ss []signature.Signer
opts []signature.SignOption
}

// newDSSEEncoder returns an encoder that signs messages in DSSE format according to opts, with key
// material from ss. SHA256 is used as the hash algorithm, unless overridden by opts.
func newDSSEEncoder(ss []signature.Signer, opts ...signature.SignOption) (*dsseEncoder, error) {
func newDSSEEncoder(ss []signature.Signer, opts ...signature.SignOption) *dsseEncoder {
return &dsseEncoder{
ss: ss,
opts: opts,
}
}

// signMessage signs the message from r in DSSE format, and writes the result to w. On success, the
// hash function is returned.
func (en *dsseEncoder) signMessage(ctx context.Context, w io.Writer, r io.Reader) (crypto.Hash, error) {
opts := en.opts
opts = append(opts, options.WithContext(ctx))

var so crypto.SignerOpts
for _, opt := range opts {
opt.ApplyCryptoSignerOpts(&so)
Expand All @@ -41,57 +53,25 @@ func newDSSEEncoder(ss []signature.Signer, opts ...signature.SignOption) (*dsseE
opts = append(opts, options.WithCryptoSignerOpts(so))
}

dss := make([]dsse.Signer, 0, len(ss))
for _, s := range ss {
ds, err := newDSSESigner(s, opts...)
if err != nil {
return nil, err
}

dss = append(dss, ds)
}

es, err := dsse.NewEnvelopeSigner(dss...)
if err != nil {
return nil, err
}

return &dsseEncoder{
es: es,
h: so.HashFunc(),
payloadType: metadataMediaType,
}, nil
}

// signMessage signs the message from r in DSSE format, and writes the result to w. On success, the
// hash function is returned.
func (en *dsseEncoder) signMessage(ctx context.Context, w io.Writer, r io.Reader) (crypto.Hash, error) {
body, err := io.ReadAll(r)
if err != nil {
return 0, err
}

e, err := en.es.SignPayload(ctx, en.payloadType, body)
s := dsse.WrapMultiSigner(metadataMediaType, en.ss...)
b, err := s.SignMessage(r, opts...)
if err != nil {
return 0, err
}

return en.h, json.NewEncoder(w).Encode(e)
_, err = w.Write(b)
return so.HashFunc(), err
}

type dsseDecoder struct {
vs []signature.Verifier
threshold int
payloadType string
vs []signature.Verifier
}

// newDSSEDecoder returns a decoder that verifies messages in DSSE format using key material from
// vs.
func newDSSEDecoder(vs ...signature.Verifier) *dsseDecoder {
return &dsseDecoder{
vs: vs,
threshold: 1, // Envelope considered verified if at least one verifier succeeds.
payloadType: metadataMediaType,
vs: vs,
}
}

Expand All @@ -103,112 +83,78 @@ var (
// verifyMessage reads a message from r, verifies its signature(s), and returns the message
// contents. On success, the accepted public keys are set in vr.
func (de *dsseDecoder) verifyMessage(ctx context.Context, r io.Reader, h crypto.Hash, vr *VerifyResult) ([]byte, error) { //nolint:lll
vs := make([]dsse.Verifier, 0, len(de.vs))
// Wrap the verifiers so we can accumulate the accepted public keys.
vs := make([]signature.Verifier, 0, len(de.vs))
for _, v := range de.vs {
dv, err := newDSSEVerifier(v, options.WithCryptoSignerOpts(h))
if err != nil {
return nil, err
}

vs = append(vs, dv)
vs = append(vs, wrappedVerifier{
Verifier: v,
keys: &vr.keys,
})
}

v, err := dsse.NewMultiEnvelopeVerifier(de.threshold, vs...)
raw, err := io.ReadAll(r)
if err != nil {
return nil, err
}

var e dsse.Envelope
if err := json.NewDecoder(r).Decode(&e); err != nil {
return nil, err
}
v := dsse.WrapMultiVerifier(metadataMediaType, 1, vs...)

vr.aks, err = v.Verify(ctx, &e)
if err != nil {
if err := v.VerifySignature(bytes.NewReader(raw), nil, options.WithContext(ctx), options.WithHash(h)); err != nil {
return nil, fmt.Errorf("%w: %w", errDSSEVerifyEnvelopeFailed, err)
}

if e.PayloadType != de.payloadType {
return nil, fmt.Errorf("%w: %v", errDSSEUnexpectedPayloadType, e.PayloadType)
}

return e.DecodeB64Payload()
}

type dsseSigner struct {
s signature.Signer
opts []signature.SignOption
pub crypto.PublicKey
}

// newDSSESigner returns a dsse.Signer that uses s to sign according to opts.
func newDSSESigner(s signature.Signer, opts ...signature.SignOption) (*dsseSigner, error) {
pub, err := s.PublicKey()
if err != nil {
var e dsseEnvelope
if err := json.Unmarshal(raw, &e); err != nil {
return nil, err
}

return &dsseSigner{
s: s,
opts: opts,
pub: pub,
}, nil
}

// Sign signs the supplied data.
func (s *dsseSigner) Sign(ctx context.Context, data []byte) ([]byte, error) {
opts := s.opts
opts = append(opts, options.WithContext(ctx))
if e.PayloadType != metadataMediaType {
return nil, fmt.Errorf("%w: %v", errDSSEUnexpectedPayloadType, e.PayloadType)
}

return s.s.SignMessage(bytes.NewReader(data), opts...)
return e.DecodedPayload()
}

// KeyID returns the key ID associated with s.
func (s dsseSigner) KeyID() (string, error) {
return dsse.SHA256KeyID(s.pub)
type wrappedVerifier struct {
signature.Verifier
keys *[]crypto.PublicKey
}

type dsseVerifier struct {
v signature.Verifier
opts []signature.VerifyOption
pub crypto.PublicKey
}
func (wv wrappedVerifier) VerifySignature(signature, message io.Reader, opts ...signature.VerifyOption) error {
err := wv.Verifier.VerifySignature(signature, message, opts...)
if err == nil {
pub, err := wv.Verifier.PublicKey()
if err != nil {
return err
}

// newDSSEVerifier returns a dsse.Verifier that uses v to verify according to opts.
func newDSSEVerifier(v signature.Verifier, opts ...signature.VerifyOption) (*dsseVerifier, error) {
pub, err := v.PublicKey()
if err != nil {
return nil, err
*wv.keys = append(*wv.keys, pub)
}

return &dsseVerifier{
v: v,
opts: opts,
pub: pub,
}, nil
}

// Verify verifies that sig is a valid signature of data.
func (v *dsseVerifier) Verify(ctx context.Context, data, sig []byte) error {
opts := v.opts
opts = append(opts, options.WithContext(ctx))

return v.v.VerifySignature(bytes.NewReader(sig), bytes.NewReader(data), opts...)
return err
}

// Public returns the public key associated with v.
func (v *dsseVerifier) Public() crypto.PublicKey {
return v.pub
// dsseEnvelope describes a DSSE envelope.
type dsseEnvelope struct {
PayloadType string `json:"payloadType"`
Payload string `json:"payload"`
Signatures []struct {
KeyID string `json:"keyid"`
Sig string `json:"sig"`
} `json:"signatures"`
}

// KeyID returns the key ID associated with v.
func (v *dsseVerifier) KeyID() (string, error) {
return dsse.SHA256KeyID(v.pub)
// DecodedPayload returns the decoded payload from envelope e.
func (e *dsseEnvelope) DecodedPayload() ([]byte, error) {
b, err := base64.StdEncoding.DecodeString(e.Payload)
if err != nil {
return base64.URLEncoding.DecodeString(e.Payload)
}
return b, nil
}

// isDSSESignature returns true if r contains a signature in a DSSE envelope.
func isDSSESignature(r io.Reader) bool {
var e dsse.Envelope
var e dsseEnvelope
if err := json.NewDecoder(r).Decode(&e); err != nil {
return false
}
Expand Down
Loading

0 comments on commit d242f61

Please sign in to comment.