Skip to content

Commit

Permalink
feat: add DSSE encoder/decoder
Browse files Browse the repository at this point in the history
  • Loading branch information
tri-adam committed Nov 1, 2022
1 parent b6f5cb1 commit 129bd25
Show file tree
Hide file tree
Showing 13 changed files with 605 additions and 3 deletions.
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4
github.com/google/uuid v1.3.0
github.com/sebdah/goldie/v2 v2.5.3
github.com/secure-systems-lab/go-securesystemslib v0.4.0
github.com/sigstore/sigstore v1.4.5
github.com/spf13/cobra v1.6.1
github.com/spf13/pflag v1.0.5
Expand All @@ -14,8 +15,10 @@ require (
require (
github.com/cloudflare/circl v1.1.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-containerregistry v0.12.0 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/letsencrypt/boulder v0.0.0-20220929215747-76583552c2be // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/sergi/go-diff v1.2.0 // indirect
github.com/theupdateframework/go-tuf v0.5.2-0.20220930112810-3890c1e7ace4 // indirect
Expand Down
10 changes: 9 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,30 @@ github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7N
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cloudflare/circl v1.1.0 h1:bZgT/A+cikZnKIwn7xL2OBj012Bmvho/o6RpRvv3GKY=
github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a h1:yDWHCSQ40h88yih2JAcL6Ls/kVkSE8GFACTGVnMPruw=
github.com/facebookgo/limitgroup v0.0.0-20150612190941-6abd8d71ec01 h1:IeaD1VDVBPlx3viJT9Md8if8IxxJnO+x0JCGb054heg=
github.com/facebookgo/muster v0.0.0-20150708232844-fd3d7953fd52 h1:a4DFiKFJiDRGFD1qIcqGLX/WlUMD9dyLSLDt+9QZgt8=
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-containerregistry v0.12.0 h1:nidOEtFYlgPCRqxCKj/4c/js940HVWplCWc5ftdfdUA=
github.com/google/go-containerregistry v0.12.0/go.mod h1:sdIK+oHQO7B93xI8UweYdl887YhuIwg9vz8BSLH3+8k=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/honeycombio/beeline-go v1.10.0 h1:cUDe555oqvw8oD76BQJ8alk7FP0JZ/M/zXpNvOEDLDc=
github.com/honeycombio/libhoney-go v1.16.0 h1:kPpqoz6vbOzgp7jC6SR7SkNj7rua7rgxvznI6M3KdHc=
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jmhodges/clock v0.0.0-20160418191101-880ee4c33548 h1:dYTbLf4m0a5u0KLmPfB6mgxbcV7588bOCx79hxa5Sr4=
github.com/klauspost/compress v1.15.7 h1:7cgTQxJCU/vy+oP/E3B9RGbQTgbiVzIJWIKOLoAsPok=
github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
Expand All @@ -33,6 +37,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/letsencrypt/boulder v0.0.0-20220929215747-76583552c2be h1:Cx2bsfM27RBF/45zP1xhFN9FHDxo40LdYdE5L+GWVTw=
github.com/letsencrypt/boulder v0.0.0-20220929215747-76583552c2be/go.mod h1:j/WMsOEcTSfy6VR1PkiIo20qH1V9iRRzb7ishoKkN0g=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
Expand All @@ -43,6 +49,8 @@ github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sebdah/goldie/v2 v2.5.3 h1:9ES/mNN+HNUbNWpVAlrzuZ7jE+Nrczbj8uFRjM7624Y=
github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
github.com/secure-systems-lab/go-securesystemslib v0.4.0 h1:b23VGrQhTA8cN2CbBw7/FulN9fTtqYUdS5+Oxzt+DUE=
github.com/secure-systems-lab/go-securesystemslib v0.4.0/go.mod h1:FGBZgq2tXWICsxWQW1msNf49F0Pf2Op5Htayx335Qbs=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
Expand Down
212 changes: 212 additions & 0 deletions pkg/integrity/dsse.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
// Copyright (c) 2022, 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.

package integrity

import (
"bytes"
"crypto"
"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/options"
)

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

type dsseEncoder struct {
es *dsse.EnvelopeSigner
h crypto.Hash
payloadType string
}

// 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) {
var so crypto.SignerOpts
for _, opt := range opts {
opt.ApplyCryptoSignerOpts(&so)
}

// If SignerOpts not explicitly supplied, set default hash algorithm.
if so == nil {
so = crypto.SHA256
opts = append(opts, options.WithCryptoSignerOpts(so))
}

dss := make([]dsse.SignVerifier, 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(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(en.payloadType, body)
if err != nil {
return 0, err
}

return en.h, json.NewEncoder(w).Encode(e)
}

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

// 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,
}
}

var (
errDSSEVerifyEnvelopeFailed = errors.New("dsse: verify envelope failed")
errDSSEUnexpectedPayloadType = errors.New("unexpected DSSE payload type")
)

// 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(r io.Reader, h crypto.Hash, vr *VerifyResult) ([]byte, error) {
vs := make([]dsse.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)
}

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

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

vr.aks, err = v.Verify(&e)
if err != nil {
return nil, fmt.Errorf("%w: %v", 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.SignVerifier 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 {
return nil, err
}

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

// Sign signs the supplied data.
func (s *dsseSigner) Sign(data []byte) ([]byte, error) {
return s.s.SignMessage(bytes.NewReader(data), s.opts...)
}

var errSignNotImplemented = errors.New("sign not implemented")

// Verify is not implemented, but required for the dsse.SignVerifier interface.
func (s *dsseSigner) Verify(data, sig []byte) error {
return errSignNotImplemented
}

// Public returns the public key associated with s.
func (s *dsseSigner) Public() crypto.PublicKey {
return s.pub
}

// KeyID returns the key ID associated with s.
func (s dsseSigner) KeyID() (string, error) {
return dsse.SHA256KeyID(s.pub)
}

type dsseVerifier struct {
v signature.Verifier
opts []signature.VerifyOption
pub crypto.PublicKey
}

// 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
}

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

// Verify verifies that sig is a valid signature of data.
func (v *dsseVerifier) Verify(data, sig []byte) error {
return v.v.VerifySignature(bytes.NewReader(sig), bytes.NewReader(data), v.opts...)
}

// Public returns the public key associated with v.
func (v *dsseVerifier) Public() crypto.PublicKey {
return v.pub
}

// KeyID returns the key ID associated with v.
func (v *dsseVerifier) KeyID() (string, error) {
return dsse.SHA256KeyID(v.pub)
}
Loading

0 comments on commit 129bd25

Please sign in to comment.