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 for issue 3476 #3486

Merged
merged 9 commits into from
Jan 25, 2024
Merged

fix for issue 3476 #3486

merged 9 commits into from
Jan 25, 2024

Conversation

Mukuls77
Copy link
Contributor

Summary

The PR fixes the issue "Closes #3476"
Use case Impacted:
A company sign the artifact using its PKI using
companyRootCA->companyIntermediateCrt->CompanyLeafcert
Client download the software and want to sign it again using client PKI before it can be used using
ClientRootCA->clientIntermediatecrt->Clientleafcrt
so in this case we will have multiple signatures attached to the artifact with different Root->intermediate->leaf chains.
Now if client verify the artifact just passing the client RootCA, the verification fails randomly.

The main issue identified is that
func verifySignatures(ctx context.Context, sigs oci.Signatures, h v1.Hash, co *CheckOpts) (checkedSignatures []oci.Signature, bundleVerified bool, err error)
function invokes multiple worker thread and call the function VerifyImageSignature() by passing common CheckOpts reference.
now in downstream the function
func verifyInternal(ctx context.Context, sig oci.Signature, h v1.Hash,
verifyFn signatureVerificationFn, co *CheckOpts)
try to update the co.IntermediateCerts if it is null. ( which will be the case in this scenario as we are just passing root certificate for verification)
since this function is being executed by multiple threads so it can corrupt the co.IntermediateCerts (which ever thread find it Null)
This can corrupt the verification chain , so sometime verification fails.
To solve this issue a new function is introduced
func ValidateAndUnpackCertWithIntermediates(cert *x509.Certificate, co *CheckOpts, intermediateCerts *x509.CertPool) (signature.Verifier, error)
this has intermediate as separate argument. this new function is now called inside verifyInternal()

Release Note

File modified:
cosign/pkg/cosign/verify.go

Documentation

no change in existing API

Signed-off-by: Mukuls77 <[email protected]>
Copy link

codecov bot commented Jan 18, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Comparison is base (26e8440) 40.07% compared to head (0aea637) 40.10%.
Report is 7 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3486      +/-   ##
==========================================
+ Coverage   40.07%   40.10%   +0.02%     
==========================================
  Files         155      155              
  Lines       10040    10044       +4     
==========================================
+ Hits         4024     4028       +4     
  Misses       5530     5530              
  Partials      486      486              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

// as it uses a common reference of CheckOpts so in multi thread invocation co.intermediateCert object
// can get overwritten my multiple threads. So to solve this ValidateAndUnpackCertWithIntermediates
// is created which has a separate argument for Intermediate certs.

Copy link
Member

Choose a reason for hiding this comment

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

nit: remove this blank line

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the review i will remove the empty line

Copy link
Member

@cpanato cpanato left a comment

Choose a reason for hiding this comment

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

there is any tests that we can add or the existing ones cover this change?

@Mukuls77
Copy link
Contributor Author

The new function written ValidateAndUnpackCertWithIntermediates()
is covered by already existing verify testing function like
TestVerifyImageSignature()
TestVerifyImageSignatureMultipleSubs()
TestVerifyImageSignatureWithNoChain()
TestVerifyImageSignatureWithOnlyRoot()
TestVerifyImageSignatureWithMissingSub ()

also test function TestVerifyImageSignature()
create an oci.sig with Root->Intermediate->leaf cert
and verify it using only root cert.

The only logic which existing function can not test is that we need to invoke verification on Multiple Signature case. i am not sure if we can create a unit test for the same. But if required i can create an end to end test where we can attach multiple signatures with different root chains and do verification using any of the root certificate. Pls suggest shall we create an end to end test case for this.

@Mukuls77
Copy link
Contributor Author

i propose to enhance existing end to end test case
cosign/test/e2e_test_attach.sh
to attach another signature to the artifact with different root chain and verify it using root certificate only.

Signed-off-by: Mukuls77 <[email protected]>
@Mukuls77
Copy link
Contributor Author

I have enhanced end to end attach test case to attach two signatures with different root chains and tested the verification using root certificate only. attached are the logs of the testing done.
e2e_attach_test_logs.txt

@Mukuls77
Copy link
Contributor Author

@haydentherapper ; @cpanato
I have done the required changes and added the end to end test case to test multiple signatures with different root chains. kindly review

pkg/cosign/verify.go Show resolved Hide resolved
}

// Now verify the cert, then the signature.
if intermediateCerts == nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

I would remove this conditional, as the purpose of this function is to pass in the intermediates that should be used for chain building.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This check is important and should be present.
we will reach the new function ValidateAndUnpackCertWithIntermediates() via two paths

  1. ValidateAndUnpackCert()
  2. verifyInternal()

verifyInternal is called in some cases where we dont have an intermediate present in signature but we pass that as an argument in verification in those cases co.IntermediateCerts will have a valid intermediate cert but we will not be able to set the intermediatePool argument. one scenario is verification of Blob signatures.
so we need to have this condition if the intermediate Argument is Null that set the value from co.IntermediateCerts
I tested this by removing this condition and i could see verify blob signature test cases were failing so i put this condition back.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah I think I understand the issue. Correct me if I misunderstand, you're saying that co.IntermediateCerts may have been set by one of the functions that calls verifyInternal.

I think the fix would just be to pass co.IntermediateCerts to ValidateAndUnpackCertWithIntermediates on line 745 if co.IntermediateCerts != nil. Then you could remove this check.

@@ -721,19 +803,19 @@ func verifyInternal(ctx context.Context, sig oci.Signature, h v1.Hash,
return false, err
}
// If there is no chain annotation present, we preserve the pools set in the CheckOpts.
var pool = (*x509.CertPool)(nil)
if len(chain) > 0 {
if len(chain) == 1 {
co.IntermediateCerts = nil
Copy link
Contributor

Choose a reason for hiding this comment

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

This line needs to be updated too, we can't overwrite co.IntermediateCerts

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed i will incorporate this comment

@@ -721,19 +803,19 @@ func verifyInternal(ctx context.Context, sig oci.Signature, h v1.Hash,
return false, err
}
// If there is no chain annotation present, we preserve the pools set in the CheckOpts.
var pool = (*x509.CertPool)(nil)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
var pool = (*x509.CertPool)(nil)
var pool *x509.CertPool

Copy link
Contributor

Choose a reason for hiding this comment

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

This should be sufficient to create a pointer.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed i will incorporate this comment

pkg/cosign/verify.go Show resolved Hide resolved
@Mukuls77
Copy link
Contributor Author

@haydentherapper i have implemented the review comments. kindly review

@@ -216,9 +216,16 @@ func verifyOCISignature(ctx context.Context, verifier signature.Verifier, sig pa
return verifier.VerifySignature(bytes.NewReader(signature), bytes.NewReader(payload), options.WithContext(ctx))
}

// ValidateAndUnpackCert creates a Verifier from a certificate. Veries that the certificate
// chains up to a trusted root. Optionally verifies the subject and issuer of the certificate.
// ValidateAndUnpackCert calls ValidateAndUnpackCertWithIntermediates() by passing intermediate
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd keep the previous comment, just adding the detail that the intermediate certificate chain comes from CheckOpts. You can omit the detail that it calls another function.

}

// Now verify the cert, then the signature.
if intermediateCerts == nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

Ah I think I understand the issue. Correct me if I misunderstand, you're saying that co.IntermediateCerts may have been set by one of the functions that calls verifyInternal.

I think the fix would just be to pass co.IntermediateCerts to ValidateAndUnpackCertWithIntermediates on line 745 if co.IntermediateCerts != nil. Then you could remove this check.

@Mukuls77
Copy link
Contributor Author

@haydentherapper i have incorporated the review comments pls review

Copy link
Contributor

@haydentherapper haydentherapper left a comment

Choose a reason for hiding this comment

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

Thanks for the fix!

@Mukuls77
Copy link
Contributor Author

@haydentherapper i have incorporated the review comment. please check

@@ -25,6 +25,10 @@ cp ./test/testdata/test_attach_private_key $tmp/private_key
cp ./test/testdata/test_attach_leafcert.pem $tmp/leafcert.pem
cp ./test/testdata/test_attach_certchain.pem $tmp/certchain.pem
cp ./test/testdata/test_attach_rootcert.pem $tmp/rootcert.pem
cp ./test/testdata/test_attach_second_private_key $tmp/secondprivate_key
Copy link
Contributor

Choose a reason for hiding this comment

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

@Mukuls77 What is the purpose of this extra set of certificates? It looks like this is testing the same behavior, are these e2e test changes needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This test case creates the scenario when multiple signatures are attached to a artifact with different root chains and we want to verify the artifact with any one of the root certificate. because there are more than one signatures attached so multiple threads will be created during verification and each thread will verify the allocated signature. During this operation previously when threads were modifying co.intermediates sometime verification use to fail, now with this fix it will not fail. so in my view this is an important test cases of verifying multiple signatures.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks, that makes sense. Can you add a comment about this in the test file?

@haydentherapper
Copy link
Contributor

LGTM, just one comment about tests

hectorj2f
hectorj2f previously approved these changes Jan 24, 2024
Copy link
Contributor

@hectorj2f hectorj2f left a comment

Choose a reason for hiding this comment

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

lgtm

@Mukuls77
Copy link
Contributor Author

@haydentherapper @hectorj2f
if there are no further comments i request to merge the pull request.
Thanks

@@ -25,6 +25,10 @@ cp ./test/testdata/test_attach_private_key $tmp/private_key
cp ./test/testdata/test_attach_leafcert.pem $tmp/leafcert.pem
cp ./test/testdata/test_attach_certchain.pem $tmp/certchain.pem
cp ./test/testdata/test_attach_rootcert.pem $tmp/rootcert.pem
cp ./test/testdata/test_attach_second_private_key $tmp/secondprivate_key
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks, that makes sense. Can you add a comment about this in the test file?

@@ -59,24 +67,22 @@ echo "Payload: $PAYLOAD"

## Attach Signature, payload, cert and cert-chain
./cosign attach signature --signature ./payloadbase64.sig --payload ./payload.json --cert ./leafcert.pem --cert-chain ./certchain.pem $IMAGE_URI_DIGEST
./cosign attach signature --signature ./secondpayloadbase64.sig --payload ./payload.json --cert ./secondleafcert.pem --cert-chain ./secondcertchain.pem $IMAGE_URI_DIGEST
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we modify this test so that we still test verification with a single attached cert, then after verification, attach another and try verification?

@Mukuls77
Copy link
Contributor Author

@haydentherapper i have incorporated the review comments. kindly check

Copy link
Contributor

@haydentherapper haydentherapper left a comment

Choose a reason for hiding this comment

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

Thanks @Mukuls77!

@haydentherapper haydentherapper merged commit f43eb6b into sigstore:main Jan 25, 2024
28 checks passed
@github-actions github-actions bot added this to the v2.3.0 milestone Jan 25, 2024
@ret2libc
Copy link

What is the difference between the new ValidateAndUnpackCertWithIntermediates and ValidateAndUnpackCertWithChain?

@Mukuls77
Copy link
Contributor Author

Mukuls77 commented Jan 26, 2024 via email

@haydentherapper
Copy link
Contributor

Purpose was a nonbreaking API change. Long term there's a larger refactor to do to pass a set of read-only CA cert metadata to the verification function.

@Mukuls77
Copy link
Contributor Author

@haydentherapper thanks for Merging the pull request. I suppose this fix will be available in 2.3.0 release, i wanted to check what is the expected timeframe of next cosign release.
Also i have one query if i see the cosign main page it shows

image

when i checked further i see cross build to macOS is failing in upload artifact step
image

so wanted to check if there is some action i can do to solve this issue,

@haydentherapper
Copy link
Contributor

I'll likely cut a release next week or so, there's a few open PRs we should get in first.

I've been noticing that failure too, I'm not sure the root cause, but it's not related to this PR specifically.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

cosign verify fails randomly when we have multiple signatures attached to image
5 participants