Skip to content

Commit

Permalink
feat: add signing certificate to envelope (#330)
Browse files Browse the repository at this point in the history
* add signing certificate to envelope

Signed-off-by: Asra Ali <asraa@google.com>
  • Loading branch information
asraa authored Jun 17, 2022
1 parent 21c07c4 commit cb8f03b
Show file tree
Hide file tree
Showing 5 changed files with 291 additions and 1 deletion.
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ require (
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/Microsoft/go-winio v0.5.2 // indirect
github.com/PaesslerAG/gval v1.0.0 // indirect
github.com/PaesslerAG/jsonpath v0.1.1 // indirect
github.com/ThalesIgnite/crypto11 v1.2.5 // indirect
Expand Down Expand Up @@ -169,6 +170,7 @@ require (
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.12.0 // indirect
github.com/spiffe/go-spiffe/v2 v2.1.0 // indirect
github.com/subosito/gotenv v1.3.0 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
github.com/tent/canonical-json-go v0.0.0-20130607151641-96e4ba3a7613 // indirect
Expand All @@ -181,6 +183,7 @@ require (
github.com/vbatts/tar-split v0.11.2 // indirect
github.com/xanzy/go-gitlab v0.68.0 // indirect
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
github.com/zeebo/errs v1.2.2 // indirect
go.etcd.io/bbolt v1.3.6 // indirect
go.etcd.io/etcd/api/v3 v3.5.4 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.4 // indirect
Expand Down
9 changes: 9 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,8 @@ github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JP
github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ=
Expand Down Expand Up @@ -1697,6 +1699,8 @@ github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy
github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU=
github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ=
github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI=
github.com/spiffe/go-spiffe/v2 v2.1.0 h1:IZRlWhyFpPbJOiK8K+MwEFPU/QCdaW4Zf5bmIKBd3XM=
github.com/spiffe/go-spiffe/v2 v2.1.0/go.mod h1:5qg6rpqlwIub0JAiF1UK9IMD6BpPTmvG6yfSgDBs5lg=
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I=
github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8=
Expand Down Expand Up @@ -1837,6 +1841,8 @@ github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=
github.com/zalando/go-keyring v0.1.0/go.mod h1:RaxNwUITJaHVdQ0VC7pELPZ3tOWn13nr0gZMZEhpVU0=
github.com/zeebo/errs v1.2.2 h1:5NFypMTuSdoySVTqlNs1dEoU21QVamMQJxW/Fii5O7g=
github.com/zeebo/errs v1.2.2/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
Expand Down Expand Up @@ -2568,6 +2574,7 @@ google.golang.org/genproto v0.0.0-20200626011028-ee7919e894b5/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20200707001353-8e8330bf89df/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
Expand Down Expand Up @@ -2687,6 +2694,7 @@ google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8=
google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.2.0/go.mod h1:DNq5QpG7LJqD2AamLZ7zvKE0DEpVl2BSEVjFycAAjRY=
google.golang.org/grpc/examples v0.0.0-20201130180447-c456688b1860/go.mod h1:Ly7ZA/ARzg8fnPU9TyZIxoz33sEUuWX7txiqs8lPTgE=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
Expand Down Expand Up @@ -2736,6 +2744,7 @@ gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
Expand Down
86 changes: 86 additions & 0 deletions signing/envelope/envelope.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package envelope

import (
"encoding/json"
"fmt"

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

/*
Envelope captures an envelope as described by the Secure Systems Lab
Signing Specification. See here:
https://github.com/secure-systems-lab/signing-spec/blob/master/envelope.md
*/
type Envelope struct {
PayloadType string `json:"payloadType"`
Payload string `json:"payload"`
Signatures []Signature `json:"signatures"`
}

/*
Signature represents a generic in-toto signature that contains the identifier
of the key which was used to create the signature.
The used signature scheme has to be agreed upon by the signer and verifer
out of band.
The signature is a base64 encoding of the raw bytes from the signature
algorithm.
The cert is a PEM encoded string of the signing certificate
*/
type Signature struct {
KeyID string `json:"keyid"`
Sig string `json:"sig"`
Cert string `json:"cert"`
}

// AddCertToEnvelope takes a signed DSSE Envelope and a PEM-encoded certificate, and
// returns an Envelope with the certificate inside the Signature of the Envelope.
// This assumes there is only one signature present in the envelope.
func AddCertToEnvelope(signedAtt []byte, cert []byte) ([]byte, error) {
// Unmarshal into a DSSE envelope.
env := &dsse.Envelope{}
if err := json.Unmarshal(signedAtt, env); err != nil {
return nil, err
}

// Create an envelope.Envelope.
envWithCert := &Envelope{
PayloadType: env.PayloadType,
Payload: env.Payload,
Signatures: []Signature{},
}

if len(env.Signatures) != 1 {
return nil, fmt.Errorf("expected exactly one signature in the envelope")
}

if certs, err := cryptoutils.UnmarshalCertificatesFromPEM(cert); err != nil || len(certs) != 1 {
return nil, fmt.Errorf("invalid certificate, expected PEM encoded certificate")
}

for _, sig := range env.Signatures {
envWithCert.Signatures = append(envWithCert.Signatures,
Signature{Sig: sig.Sig, KeyID: sig.KeyID, Cert: string(cert)})
}

// Return marshalled result
return json.Marshal(envWithCert)
}

// GetCertFromEnvelope takes a signed Envelope and extracts the PEM-encoded
// certificate from the signature.
// This assumes there is only one signature present in the envelope.
func GetCertFromEnvelope(signedAtt []byte) ([]byte, error) {
// Unmarshal into an envelope.
env := &Envelope{}
if err := json.Unmarshal(signedAtt, env); err != nil {
return nil, err
}

if len(env.Signatures) != 1 {
return nil, fmt.Errorf("expected exactly one signature in the envelope")
}

return []byte(env.Signatures[0].Cert), nil
}
184 changes: 184 additions & 0 deletions signing/envelope/envelope_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package envelope

import (
"bytes"
"context"
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"encoding/json"
"encoding/pem"
"math/big"
"testing"

"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/in-toto/in-toto-golang/in_toto"
"github.com/secure-systems-lab/go-securesystemslib/dsse"
sdsse "github.com/sigstore/sigstore/pkg/signature/dsse"

intoto "github.com/in-toto/in-toto-golang/in_toto"
"github.com/sigstore/rekor/pkg/generated/models"
"github.com/sigstore/rekor/pkg/types"
intotod "github.com/sigstore/rekor/pkg/types/intoto/v0.0.1"
"github.com/sigstore/sigstore/pkg/signature"
)

func intotoEntry(certPem []byte, provenance []byte) (*intotod.V001Entry, error) {
cert := strfmt.Base64(certPem)
return &intotod.V001Entry{
IntotoObj: models.IntotoV001Schema{
Content: &models.IntotoV001SchemaContent{
Envelope: string(provenance),
},
PublicKey: &cert,
},
}, nil
}

// marshals a dsse envelope for testing
func marshalEnvelope(t *testing.T, env *dsse.Envelope) string {
b, err := json.Marshal(env)
if err != nil {
t.Fatalf("marshalling envelope: %s", err)
}
return string(b)
}

// test utility to sign a payload with a given signer
func envelope(t *testing.T, k *ecdsa.PrivateKey, payload []byte) string {
s, err := signature.LoadECDSASigner(k, crypto.SHA256)
if err != nil {
t.Fatal(err)
}
wrappedSigner := sdsse.WrapSigner(s, intoto.PayloadType)
if err != nil {
t.Fatal(err)
}
dsseEnv, err := wrappedSigner.SignMessage(bytes.NewReader(payload))
if err != nil {
t.Fatal(err)
}
return string(dsseEnv)
}

func TestAddCert(t *testing.T) {
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatal(err)
}

ca := &x509.Certificate{
SerialNumber: big.NewInt(1),
}
caBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, &priv.PublicKey, priv)
if err != nil {
t.Fatal(err)
}
certPemBytes := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: caBytes,
})
validPayload := "hellothispayloadisvalid"

tests := []struct {
name string
env string
cert []byte
addErr bool
}{
{
name: "invalid empty envelope with no signatures",
env: marshalEnvelope(t, &dsse.Envelope{}),
cert: nil,
addErr: true,
},
{
name: "invalid envelope with two signatures",
env: marshalEnvelope(t, &dsse.Envelope{
Payload: "",
PayloadType: in_toto.PayloadType,
Signatures: []dsse.Signature{
{
Sig: "abc",
},
{
Sig: "xyz",
},
},
}),
cert: nil,
addErr: true,
},
{
name: "invalid cert with valid envelope",
env: marshalEnvelope(t, &dsse.Envelope{
Payload: "",
PayloadType: in_toto.PayloadType,
Signatures: []dsse.Signature{
{
Sig: "abc",
},
},
}),
cert: nil,
addErr: true,
},
{
name: "valid envelope",
env: envelope(t, priv, []byte(validPayload)),
cert: certPemBytes,
addErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Add certificate to envelope.
envWithCert, err := AddCertToEnvelope([]byte(tt.env), tt.cert)
if (err != nil) != tt.addErr {
t.Errorf("AddCertToEnvelope() error = %v, wanted %v", err, tt.addErr)
}
if err != nil {
return
}

// Now get cert from envelope and compare.
gotCert, err := GetCertFromEnvelope(envWithCert)
if err != nil {
t.Fatalf("GetCertFromEnvelope() error = %v", err)
}

if !bytes.EqualFold(gotCert, tt.cert) {
t.Errorf("expected cert equality")
}

// Now test compatibility with Rekor intoto entry type.
testRekorSupport(t, tt.cert, envWithCert)
})
}
}

// This servers as a regression test to make sure that the Rekor intoto
// type can successfully unmarshal our "Envelope" with included cert.
func testRekorSupport(t *testing.T, certPem []byte, envWithCert []byte) {
ctx := context.Background()
intotoEntry, err := intotoEntry(certPem, envWithCert)
if err != nil {
t.Fatalf("error creating intoto entry: %s", err)
}
e := models.Intoto{
APIVersion: swag.String(intotoEntry.APIVersion()),
Spec: intotoEntry.IntotoObj,
}
pe := models.ProposedEntry(&e)
entry, err := types.NewEntry(pe)
if err != nil {
t.Fatalf("error creating valid intoto entry")
}
_, err = types.CanonicalizeEntry(ctx, entry)
if err != nil {
t.Fatalf("error creating valid intoto entry")
}
}
10 changes: 9 additions & 1 deletion signing/sigstore/fulcio.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/sigstore/cosign/pkg/providers"
"github.com/sigstore/sigstore/pkg/signature/dsse"
"github.com/slsa-framework/slsa-github-generator/signing"
"github.com/slsa-framework/slsa-github-generator/signing/envelope"

intoto "github.com/in-toto/in-toto-golang/in_toto"
)
Expand Down Expand Up @@ -101,8 +102,15 @@ func (s *Fulcio) Sign(ctx context.Context, p *intoto.Statement) (signing.Attesta
return nil, fmt.Errorf("signing message: %v", err)
}

// Add certificate to envelope.
// TODO: Remove when DSSE spec includes a cert field inside the signatures.
signedAttWithCert, err := envelope.AddCertToEnvelope(signedAtt, k.Cert)
if err != nil {
return nil, fmt.Errorf("adding certificate to DSSE: %v", err)
}

return &attestation{
att: signedAtt,
att: signedAttWithCert,
cert: k.Cert,
}, nil
}

0 comments on commit cb8f03b

Please sign in to comment.