1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
|
// 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.
package integrity
import (
"bytes"
"context"
"crypto"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"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 {
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 {
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)
}
// If SignerOpts not explicitly supplied, set default hash algorithm.
if so == nil {
so = crypto.SHA256
opts = append(opts, options.WithCryptoSignerOpts(so))
}
s := dsse.WrapMultiSigner(metadataMediaType, en.ss...)
b, err := s.SignMessage(r, opts...)
if err != nil {
return 0, err
}
_, err = w.Write(b)
return so.HashFunc(), err
}
type dsseDecoder struct {
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,
}
}
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(ctx context.Context, r io.Reader, h crypto.Hash, vr *VerifyResult) ([]byte, error) { //nolint:lll
// Wrap the verifiers so we can accumulate the accepted public keys.
vs := make([]signature.Verifier, 0, len(de.vs))
for _, v := range de.vs {
vs = append(vs, wrappedVerifier{
Verifier: v,
keys: &vr.keys,
})
}
raw, err := io.ReadAll(r)
if err != nil {
return nil, err
}
v := dsse.WrapMultiVerifier(metadataMediaType, 1, vs...)
if err := v.VerifySignature(bytes.NewReader(raw), nil, options.WithContext(ctx), options.WithHash(h)); err != nil {
return nil, fmt.Errorf("%w: %w", errDSSEVerifyEnvelopeFailed, err)
}
var e dsseEnvelope
if err := json.Unmarshal(raw, &e); err != nil {
return nil, err
}
if e.PayloadType != metadataMediaType {
return nil, fmt.Errorf("%w: %v", errDSSEUnexpectedPayloadType, e.PayloadType)
}
return e.DecodedPayload()
}
type wrappedVerifier struct {
signature.Verifier
keys *[]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
}
*wv.keys = append(*wv.keys, pub)
}
return err
}
// 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"`
}
// 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 dsseEnvelope
if err := json.NewDecoder(r).Decode(&e); err != nil {
return false
}
return metadataMediaType == e.PayloadType
}
|