Skip to content

Commit

Permalink
Add verification for embedded SCTs
Browse files Browse the repository at this point in the history
This adds support for verifying SCTs embedded in certificates in
addition to being detached. Embedded SCTs will be verified while parsing
the certificate on verification (for verify, verify-blob, and
verify-attestation), and on signing if a certificate chain is provided.

Signed-off-by: Hayden Blauzvern <hblauzvern@google.com>
  • Loading branch information
haydentherapper committed Apr 11, 2022
1 parent de85b7e commit 566c421
Show file tree
Hide file tree
Showing 11 changed files with 576 additions and 198 deletions.
116 changes: 2 additions & 114 deletions cmd/cosign/cli/fulcio/fulcioverifier/fulcioverifier.go
Expand Up @@ -17,139 +17,27 @@ package fulcioverifier

import (
"context"
"crypto"
"crypto/x509"
"encoding/json"
"encoding/pem"
"fmt"
"os"

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"

"github.com/sigstore/cosign/cmd/cosign/cli/fulcio"
"github.com/sigstore/cosign/pkg/cosign"
"github.com/sigstore/cosign/pkg/cosign/tuf"
"github.com/sigstore/cosign/pkg/cosign/ctl"
"github.com/sigstore/fulcio/pkg/api"
)

// This is the CT log public key target name
var ctPublicKeyStr = `ctfe.pub`

// Setting this env variable will over ride what is used to validate
// the SCT coming back from Fulcio.
const altCTLogPublicKeyLocation = "SIGSTORE_CT_LOG_PUBLIC_KEY_FILE"

// verifySCT verifies the SCT against the Fulcio CT log public key.
// By default this comes from TUF, but you can override this (for test)
// purposes by using an env variable `SIGSTORE_CT_LOG_PUBLIC_KEY_FILE`. If using
// an alternate, the file can be PEM, or DER format.
//
// 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(ctx context.Context, certPEM, rawSCT []byte) error {
pubKeys := make(map[crypto.PublicKey]tuf.StatusKind)
rootEnv := os.Getenv(altCTLogPublicKeyLocation)
if rootEnv == "" {
tufClient, err := tuf.NewFromEnv(ctx)
if err != nil {
return err
}
defer tufClient.Close()

targets, err := tufClient.GetTargetsByMeta(tuf.CTFE, []string{ctPublicKeyStr})
if err != nil {
return err
}
for _, t := range targets {
ctPub, err := cosign.PemToECDSAKey(t.Target)
if err != nil {
return errors.Wrap(err, "converting Public CT to ECDSAKey")
}
pubKeys[ctPub] = t.Status
}
} else {
fmt.Fprintf(os.Stderr, "**Warning** Using a non-standard public key for verifying SCT: %s\n", rootEnv)
raw, err := os.ReadFile(rootEnv)
if err != nil {
return errors.Wrap(err, "error reading alternate public key file")
}
pubKey, err := getAlternatePublicKey(raw)
if err != nil {
return errors.Wrap(err, "error parsing alternate public key from the file")
}
pubKeys[pubKey] = tuf.Active
}
if len(pubKeys) == 0 {
return errors.New("none of the CTFE keys have been found")
}
cert, err := x509util.CertificateFromPEM(certPEM)
if err != nil {
return err
}
var addChainResp ct.AddChainResponse
if err := json.Unmarshal(rawSCT, &addChainResp); err != nil {
return errors.Wrap(err, "unmarshal")
}
sct, err := addChainResp.ToSignedCertificateTimestamp()
if err != nil {
return err
}
var verifySctErr error
for pubKey, status := range pubKeys {
verifySctErr = ctutil.VerifySCT(pubKey, []*ctx509.Certificate{cert}, sct, false)
// Exit after successful verification of the SCT
if verifySctErr == nil {
if status != tuf.Active {
fmt.Fprintf(os.Stderr, "**Info** Successfully verified SCT using an expired verification key\n")
}
return nil
}
}
// Return the last error that occurred during verification.
return verifySctErr
}

func NewSigner(ctx context.Context, idToken, oidcIssuer, oidcClientID, oidcClientSecret, oidcRedirectURL string, fClient api.Client) (*fulcio.Signer, error) {
fs, err := fulcio.NewSigner(ctx, idToken, oidcIssuer, oidcClientID, oidcClientSecret, oidcRedirectURL, fClient)
if err != nil {
return nil, err
}

// verify the sct
if err := verifySCT(ctx, fs.Cert, fs.SCT); err != nil {
if err := ctl.VerifySCT(ctx, fs.Cert, fs.Chain, fs.SCT); err != nil {
return nil, errors.Wrap(err, "verifying SCT")
}
fmt.Fprintln(os.Stderr, "Successfully verified SCT...")

return fs, nil
}

// Given a byte array, try to construct a public key from it.
// Will try first to see if it's PEM formatted, if not, then it will
// try to parse it as der publics, and failing that
func getAlternatePublicKey(in []byte) (crypto.PublicKey, error) {
var pubKey crypto.PublicKey
var err error
var derBytes []byte
pemBlock, _ := pem.Decode(in)
if pemBlock == nil {
fmt.Fprintf(os.Stderr, "Failed to decode non-standard public key for verifying SCT using PEM decode, trying as DER")
derBytes = in
} else {
derBytes = pemBlock.Bytes
}
pubKey, err = x509.ParsePKIXPublicKey(derBytes)
if err != nil {
// Try using the PKCS1 before giving up.
pubKey, err = x509.ParsePKCS1PublicKey(derBytes)
if err != nil {
return nil, errors.Wrap(err, "failed to parse alternate public key")
}
}
return pubKey, nil
}
73 changes: 0 additions & 73 deletions cmd/cosign/cli/fulcio/fulcioverifier/fulcioverifier_test.go

This file was deleted.

16 changes: 15 additions & 1 deletion cmd/cosign/cli/sign/sign.go
Expand Up @@ -39,6 +39,7 @@ import (
ipayload "github.com/sigstore/cosign/internal/pkg/cosign/payload"
irekor "github.com/sigstore/cosign/internal/pkg/cosign/rekor"
"github.com/sigstore/cosign/pkg/cosign"
"github.com/sigstore/cosign/pkg/cosign/ctl"
"github.com/sigstore/cosign/pkg/cosign/pivkey"
"github.com/sigstore/cosign/pkg/cosign/pkcs11key"
cremote "github.com/sigstore/cosign/pkg/cosign/remote"
Expand Down Expand Up @@ -414,9 +415,22 @@ func signerFromKeyRef(ctx context.Context, certPath, certChainPath, keyRef strin
for _, c := range certChain[:len(certChain)-1] {
subPool.AddCert(c)
}
if err := cosign.TrustedCert(leafCert, rootPool, subPool); err != nil {
if _, err := cosign.TrustedCert(leafCert, rootPool, subPool); err != nil {
return nil, errors.Wrap(err, "unable to validate certificate chain")
}
// Verify SCT if present in the leaf certificate.
contains, err := ctl.ContainsSCT(leafCert.Raw)
if err != nil {
return nil, err
}
if contains {
var chain []*x509.Certificate
chain = append(chain, leafCert)
chain = append(chain, certChain...)
if err := ctl.VerifyEmbeddedSCT(context.Background(), chain); err != nil {
return nil, err
}
}
certSigner.Chain = certChainBytes

return certSigner, nil
Expand Down
File renamed without changes.
File renamed without changes.

0 comments on commit 566c421

Please sign in to comment.