Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Verify SCTs returned by fulcio #600

Merged
merged 1 commit into from
Sep 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 74 additions & 12 deletions cmd/cosign/cli/fulcio/fulcio.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import (
"crypto/sha256"
"crypto/x509"
_ "embed" // To enable the `go:embed` directive.
"encoding/base64"
"encoding/json"
"encoding/pem"
"fmt"
"io/ioutil"
Expand All @@ -34,6 +36,10 @@ import (
"github.com/go-openapi/runtime"
httptransport "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt"
ct "github.com/google/certificate-transparency-go"
"github.com/google/certificate-transparency-go/ctutil"
ctx509 "github.com/google/certificate-transparency-go/x509"
"github.com/google/certificate-transparency-go/x509util"
"github.com/pkg/errors"
"golang.org/x/term"

Expand All @@ -53,11 +59,24 @@ const (
altRoot = "SIGSTORE_ROOT_FILE"
)

type Resp struct {
CertPEM []byte
ChainPEM []byte
SCT []byte
}

// This is the root in the fulcio project.
//go:embed fulcio.pem
var rootPem string

var ctPublicKeyStr = `ctfe.pub`
var fulcioTargetStr = `fulcio.crt.pem`

var (
// For testing
VerifySCT = verifySCT
)

type oidcConnector interface {
OIDConnect(string, string, string) (*oauthflow.OIDCIDToken, error)
}
Expand All @@ -74,22 +93,22 @@ type signingCertProvider interface {
SigningCert(params *operations.SigningCertParams, authInfo runtime.ClientAuthInfoWriter, opts ...operations.ClientOption) (*operations.SigningCertCreated, error)
}

func getCertForOauthID(priv *ecdsa.PrivateKey, scp signingCertProvider, connector oidcConnector, oidcIssuer string, oidcClientID string) (certPem, chainPem []byte, err error) {
func getCertForOauthID(priv *ecdsa.PrivateKey, scp signingCertProvider, connector oidcConnector, oidcIssuer string, oidcClientID string) (Resp, error) {
pubBytes, err := x509.MarshalPKIXPublicKey(&priv.PublicKey)
if err != nil {
return nil, nil, err
return Resp{}, err
}

tok, err := connector.OIDConnect(oidcIssuer, oidcClientID, "")
if err != nil {
return nil, nil, err
return Resp{}, err
}

// Sign the email address as part of the request
h := sha256.Sum256([]byte(tok.Subject))
proof, err := ecdsa.SignASN1(rand.Reader, priv, h[:])
if err != nil {
return nil, nil, err
return Resp{}, err
}

bearerAuth := httptransport.BearerToken(tok.RawString)
Expand All @@ -109,17 +128,58 @@ func getCertForOauthID(priv *ecdsa.PrivateKey, scp signingCertProvider, connecto

resp, err := scp.SigningCert(params, bearerAuth)
if err != nil {
return nil, nil, err
return Resp{}, err
}
sct, err := base64.StdEncoding.DecodeString(resp.SCT.String())
if err != nil {
return Resp{}, err
}

// split the cert and the chain
certBlock, chainPem := pem.Decode([]byte(resp.Payload))
certPem = pem.EncodeToMemory(certBlock)
return certPem, chainPem, nil
certPem := pem.EncodeToMemory(certBlock)
fr := Resp{
CertPEM: certPem,
ChainPEM: chainPem,
SCT: sct,
}

// verify the sct
if err := VerifySCT(fr); err != nil {
fmt.Printf("Unable to verify SCT: %v\n", err)
} else {
fmt.Println("Successfully verified SCT...")
}
return fr, nil
}

// verifySCT verifies the SCT against the Fulcio CT log public key
// The SCT is a `Signed Certificate Timestamp`, which promises that
// the certificate issued by Fulcio was also added to the public CT log within
// some defined time period
func verifySCT(fr Resp) error {
buf := tuf.ByteDestination{Buffer: &bytes.Buffer{}}
if err := tuf.GetTarget(context.TODO(), ctPublicKeyStr, &buf); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not really sure if we should error out on this -- because it will fail if you haven't run cosign init and i don't think we want that to be failure behavior (rather preferrable behavior)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure, i can add in a warning that SCTs won't be verified if the user doesn't run cosign init

in the future i wonder if we could just automatically run cosign init for someone if they have experimental mode set!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a silly question, but If we have all the info to validate against in the binary to run "cosign init", could we just validate it there before putting it on disk?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"there"?
i think i'm having trouble parsing the question! do you mean if you haven't run cosign init at this point, go ahead and run it?

fmt.Println("Unable to verify SCT, try running `cosign init`...")
return err
}
pubKey, err := cosign.PemToECDSAKey(buf.Bytes())
if err != nil {
return err
}
cert, err := x509util.CertificateFromPEM(fr.CertPEM)
if err != nil {
return err
}
var sct ct.SignedCertificateTimestamp
if err := json.Unmarshal(fr.SCT, &sct); err != nil {
return errors.Wrap(err, "unmarshal")
}
return ctutil.VerifySCT(pubKey, []*ctx509.Certificate{cert}, &sct, false)
}

// GetCert returns the PEM-encoded signature of the OIDC identity returned as part of an interactive oauth2 flow plus the PEM-encoded cert chain.
func GetCert(ctx context.Context, priv *ecdsa.PrivateKey, idToken, flow, oidcIssuer, oidcClientID string, fClient *fulcioClient.Fulcio) (certPemBytes, chainPemBytes []byte, err error) {
func GetCert(ctx context.Context, priv *ecdsa.PrivateKey, idToken, flow, oidcIssuer, oidcClientID string, fClient *fulcioClient.Fulcio) (Resp, error) {
c := &realConnector{}
switch flow {
case FlowDevice:
Expand All @@ -130,7 +190,7 @@ func GetCert(ctx context.Context, priv *ecdsa.PrivateKey, idToken, flow, oidcIss
case FlowToken:
c.flow = &oauthflow.StaticTokenGetter{RawToken: idToken}
default:
return nil, nil, fmt.Errorf("unsupported oauth flow: %s", flow)
return Resp{}, fmt.Errorf("unsupported oauth flow: %s", flow)
}

return getCertForOauthID(priv, fClient.Operations, c, oidcIssuer, oidcClientID)
Expand All @@ -139,6 +199,7 @@ func GetCert(ctx context.Context, priv *ecdsa.PrivateKey, idToken, flow, oidcIss
type Signer struct {
Cert []byte
Chain []byte
SCT []byte
pub *ecdsa.PublicKey
*signature.ECDSASignerVerifier
}
Expand All @@ -164,15 +225,16 @@ func NewSigner(ctx context.Context, idToken, oidcIssuer, oidcClientID string, fC
default:
flow = FlowNormal
}
cert, chain, err := GetCert(ctx, priv, idToken, flow, oidcIssuer, oidcClientID, fClient) // TODO, use the chain.
Resp, err := GetCert(ctx, priv, idToken, flow, oidcIssuer, oidcClientID, fClient) // TODO, use the chain.
if err != nil {
return nil, errors.Wrap(err, "retrieving cert")
}
f := &Signer{
pub: &priv.PublicKey,
ECDSASignerVerifier: signer,
Cert: cert,
Chain: chain,
Cert: Resp.CertPEM,
Chain: Resp.ChainPEM,
SCT: Resp.SCT,
}
return f, nil

Expand Down
11 changes: 7 additions & 4 deletions cmd/cosign/cli/fulcio/fulcio_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,10 @@ func TestGetCertForOauthID(t *testing.T) {
err: tc.tokenGetterErr,
}

cert, chain, err := getCertForOauthID(testKey, tscp, &tf, "", "")
VerifySCT = func(Resp) error { return nil }
defer func() { VerifySCT = verifySCT }()

resp, err := getCertForOauthID(testKey, tscp, &tf, "", "")

if err != nil {
if !tc.expectErr {
Expand All @@ -122,16 +125,16 @@ func TestGetCertForOauthID(t *testing.T) {
return
}
if tc.expectErr {
t.Fatalf("getCertForOauthID got: %q, %q wanted error", cert, chain)
t.Fatalf("getCertForOauthID got: %q, %q wanted error", resp.CertPEM, resp.ChainPEM)
}

expectedCert := string(expectedCertBytes)
actualCert := string(cert)
actualCert := string(resp.CertPEM)
if actualCert != expectedCert {
t.Errorf("getCertForOauthID returned cert %q, wanted %q", actualCert, expectedCert)
}
expectedChain := string(expectedExtraBytes)
actualChain := string(chain)
actualChain := string(resp.ChainPEM)
if actualChain != expectedChain {
t.Errorf("getCertForOauthID returned chain %q, wanted %q", actualChain, expectedChain)
}
Expand Down
76 changes: 67 additions & 9 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/sigstore/cosign
go 1.17

require (
cloud.google.com/go v0.90.0 // indirect
cloud.google.com/go v0.92.3 // indirect
cloud.google.com/go/storage v1.16.0
github.com/Azure/azure-sdk-for-go v55.8.0+incompatible // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
Expand Down Expand Up @@ -45,7 +45,7 @@ require (
github.com/go-logr/logr v0.4.0 // indirect
github.com/go-logr/zapr v0.4.0 // indirect
github.com/go-openapi/analysis v0.20.1 // indirect
github.com/go-openapi/errors v0.20.0 // indirect
github.com/go-openapi/errors v0.20.1 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.6 // indirect
github.com/go-openapi/loads v0.20.2 // indirect
Expand Down Expand Up @@ -88,7 +88,6 @@ require (
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.11 // indirect
github.com/jstemmer/go-junit-report v0.9.1 // indirect
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a // indirect
github.com/klauspost/compress v1.13.0 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
Expand Down Expand Up @@ -125,7 +124,7 @@ require (
github.com/sassoftware/relic v0.0.0-20210427151427-dfb082b79b74 // indirect
github.com/segmentio/ksuid v1.0.4 // indirect
github.com/shibumi/go-pathspec v1.2.0 // indirect
github.com/sigstore/fulcio v0.1.1
github.com/sigstore/fulcio v0.1.2-0.20210831152525-42f7422734bb
github.com/sigstore/rekor v0.3.0
github.com/sigstore/sigstore v0.0.0-20210729211320-56a91f560f44
github.com/sirupsen/logrus v1.8.1 // indirect
Expand All @@ -149,9 +148,8 @@ require (
go.uber.org/atomic v1.8.0 // indirect
go.uber.org/automaxprocs v1.4.0 // indirect
go.uber.org/multierr v1.7.0 // indirect
go.uber.org/zap v1.18.1 // indirect
go.uber.org/zap v1.19.0 // indirect
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect
golang.org/x/mod v0.4.2 // indirect
golang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a // indirect
Expand All @@ -160,12 +158,10 @@ require (
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b
golang.org/x/text v0.3.6 // indirect
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
golang.org/x/tools v0.1.5 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
google.golang.org/api v0.54.0
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67 // indirect
google.golang.org/genproto v0.0.0-20210813162853-db860fec028c // indirect
google.golang.org/grpc v1.39.1 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
Expand All @@ -185,3 +181,65 @@ require (
sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect
sigs.k8s.io/yaml v1.2.0 // indirect
)

require (
cloud.google.com/go/kms v0.1.0 // indirect
cloud.google.com/go/security v0.1.0 // indirect
github.com/ThalesIgnite/crypto11 v1.2.4 // indirect
github.com/bgentry/speakeasy v0.1.0 // indirect
github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403 // indirect
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed // indirect
github.com/coreos/go-semver v0.3.0 // indirect
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0 // indirect
github.com/envoyproxy/protoc-gen-validate v0.3.0-java // indirect
github.com/fullstorydev/grpcurl v1.8.1 // indirect
github.com/golang/glog v0.0.0-20210429001901-424d2337a529 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/google/btree v1.0.1 // indirect
github.com/google/certificate-transparency-go v1.1.2-0.20210512142713-bed466244fa6 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
github.com/jhump/protoreflect v1.8.2 // indirect
github.com/jonboulle/clockwork v0.2.2 // indirect
github.com/miekg/pkcs11 v1.0.3 // indirect
github.com/rs/cors v1.8.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/soheilhy/cmux v0.1.5 // indirect
github.com/thales-e-security/pool v0.0.2 // indirect
github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect
github.com/urfave/cli v1.22.4 // indirect
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
go.etcd.io/bbolt v1.3.6 // indirect
go.etcd.io/etcd/api/v3 v3.5.0 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.0 // indirect
go.etcd.io/etcd/client/v2 v2.305.0 // indirect
go.etcd.io/etcd/client/v3 v3.5.0 // indirect
go.etcd.io/etcd/etcdctl/v3 v3.5.0 // indirect
go.etcd.io/etcd/etcdutl/v3 v3.5.0 // indirect
go.etcd.io/etcd/pkg/v3 v3.5.0 // indirect
go.etcd.io/etcd/raft/v3 v3.5.0 // indirect
go.etcd.io/etcd/server/v3 v3.5.0 // indirect
go.etcd.io/etcd/tests/v3 v3.5.0 // indirect
go.etcd.io/etcd/v3 v3.5.0 // indirect
go.opentelemetry.io/contrib v0.20.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0 // indirect
go.opentelemetry.io/otel v0.20.0 // indirect
go.opentelemetry.io/otel/exporters/otlp v0.20.0 // indirect
go.opentelemetry.io/otel/metric v0.20.0 // indirect
go.opentelemetry.io/otel/sdk v0.20.0 // indirect
go.opentelemetry.io/otel/sdk/export/metric v0.20.0 // indirect
go.opentelemetry.io/otel/sdk/metric v0.20.0 // indirect
go.opentelemetry.io/otel/trace v0.20.0 // indirect
go.opentelemetry.io/proto/otlp v0.7.0 // indirect
golang.org/x/tools v0.1.5 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
)
Loading