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

Fix tlog entry matching #584

Merged
merged 1 commit into from
Oct 21, 2024
Merged
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
63 changes: 38 additions & 25 deletions pkg/rekor/rekor.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import (
"encoding/pem"
"errors"
"fmt"
"strings"

"github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt"
Expand Down Expand Up @@ -110,12 +109,16 @@ func (c *Client) WriteMessage(ctx context.Context, message, signature []byte, ce
}

func (c *Client) get(ctx context.Context, data []byte, cert *x509.Certificate) (*models.LogEntryAnon, error) {
h := sha256.New()
h.Write(data)
hash := hex.EncodeToString(h.Sum(nil))

pem, err := cryptoutils.MarshalCertificateToPEM(cert)
if err != nil {
return nil, err
}

uuids, err := c.findTLogEntriesByPayloadAndPK(ctx, data, pem)
uuids, err := c.findTLogEntriesByPK(ctx, pem)
if err != nil {
return nil, err
}
Expand All @@ -132,13 +135,20 @@ func (c *Client) get(ctx context.Context, data []byte, cert *x509.Certificate) (
return nil, err
}

// Verify that the cert used in the tlog matches the cert
// used to sign the data.
tlogCerts, err := extractCerts(e)
// Extract the hash of payload the entry is for
// and its certificates
entryHash, tlogCerts, err := extractData(e)
if err != nil {
fmt.Println("could not extract cert", err)
continue
}
if entryHash != hash {
fmt.Println("hashes don't match")
continue
}

// Verify that the cert used in the tlog matches the cert
// used to sign the data.
for _, c := range tlogCerts {
if cert.Equal(c) {
return e, nil
Expand All @@ -149,16 +159,14 @@ func (c *Client) get(ctx context.Context, data []byte, cert *x509.Certificate) (
return nil, errors.New("could not find matching tlog entry")
}

// findTLogEntriesByPayloadAndPK is roughly equivalent to cosign.FindTLogEntriesByPayload,
// but also filters by the public key used.
func (c *Client) findTLogEntriesByPayloadAndPK(ctx context.Context, payload, pubKey []byte) (uuids []string, err error) {
// findTLogEntriesByPK is roughly equivalent to cosign.FindTLogEntriesByPayload,
// but only filters by the public key used. Earlier, this filtered on both the
// payload and the public key, but returned uuids of entries that matched
// either.
func (c *Client) findTLogEntriesByPK(ctx context.Context, pubKey []byte) (uuids []string, err error) {
params := index.NewSearchIndexParamsWithContext(ctx)
params.Query = &models.SearchIndex{}

h := sha256.New()
h.Write(payload)
params.Query.Hash = fmt.Sprintf("sha256:%s", strings.ToLower(hex.EncodeToString(h.Sum(nil))))
Copy link
Member

Choose a reason for hiding this comment

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

(optional): we may want to consider switching to cosign.FindTLogEntriesByPayload if we think there are going to be many cases of the same Pk being used.

Copy link
Member Author

Choose a reason for hiding this comment

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

i can take a look tomorrow, I'm in a bit of a brain freeze atm and I'm not sure what we'd do to counter potential sha-1 collisions here.

Copy link
Member

Choose a reason for hiding this comment

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

I don't think we have to worry about that too much. we'll still filter by PK+hash, and collisions would need to be generated by the same key within the inclusion window. It's also questionable if you distrust sha1 altogether if you could even trust the signature content being served to you in a sha1 commit. 😅

I think the long term answer is move away from the dual signing and only support offline verification, though we'll always need to support verification in some form.


params.Query.PublicKey = &models.SearchIndexPublicKey{
Content: strfmt.Base64(pubKey),
Format: swag.String(models.SearchIndexPublicKeyFormatX509),
Expand Down Expand Up @@ -188,55 +196,60 @@ func (c *Client) Verify(ctx context.Context, commitSHA string, cert *x509.Certif
return e, cosign.VerifyTLogEntryOffline(ctx, e, c.publicKeys)
}

// extractCerts is taken from cosign's cmd/cosign/cli/verify/verify_blob.go.
// extractData extracts the data hash and certs from a given LogEntryAnon.
// TODO: Refactor this into a shared lib.
func extractCerts(e *models.LogEntryAnon) ([]*x509.Certificate, error) {
func extractData(e *models.LogEntryAnon) (string, []*x509.Certificate, error) {
b, err := base64.StdEncoding.DecodeString(e.Body.(string))
if err != nil {
return nil, err
return "", nil, err
}

pe, err := models.UnmarshalProposedEntry(bytes.NewReader(b), runtime.JSONConsumer())
if err != nil {
return nil, err
return "", nil, err
}

eimpl, err := types.CreateVersionedEntry(pe)
if err != nil {
return nil, err
return "", nil, err
}

var publicKeyB64 []byte
var (
publicKeyB64 []byte
entryHash string
)
switch e := eimpl.(type) {
case *rekord_v001.V001Entry:
entryHash = *e.RekordObj.Data.Hash.Value
publicKeyB64, err = e.RekordObj.Signature.PublicKey.Content.MarshalText()
if err != nil {
return nil, err
return "", nil, err
}
case *hashedrekord_v001.V001Entry:
entryHash = *e.HashedRekordObj.Data.Hash.Value
publicKeyB64, err = e.HashedRekordObj.Signature.PublicKey.Content.MarshalText()
if err != nil {
return nil, err
return "", nil, err
}
default:
return nil, errors.New("unexpected tlog entry type")
return "", nil, errors.New("unexpected tlog entry type")
}

publicKey, err := base64.StdEncoding.DecodeString(string(publicKeyB64))
if err != nil {
return nil, err
return "", nil, err
}

certs, err := cryptoutils.UnmarshalCertificatesFromPEM(publicKey)
if err != nil {
return nil, err
return "", nil, err
}

if len(certs) == 0 {
return nil, errors.New("no certs found in pem tlog")
return "", nil, errors.New("no certs found in pem tlog")
}

return certs, err
return entryHash, certs, err
}

func (c *Client) PublicKeys() *cosign.TrustedTransparencyLogPubKeys {
Expand Down
Loading