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

policy.json BYOPKI signature verification API #2579

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

QiWang19
Copy link
Collaborator

@QiWang19 QiWang19 commented Sep 20, 2024

@QiWang19 QiWang19 changed the title policy.json BYOPKI signature verification API WIP: policy.json BYOPKI signature verification API Sep 20, 2024
@QiWang19
Copy link
Collaborator Author

Hi @mtrmac , I’ve created this draft PR for the policy.json API change, could you take a look and provide your initial thoughts on the structure of the API update?
I just want to make sure I’m on the right path before proceeding further. My plan is to merge both the API and code support in a single PR. What do you think?

Copy link
Collaborator

@mtrmac mtrmac left a comment

Choose a reason for hiding this comment

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

Thanks! Looks good overall.

Earlier discussion openshift/enhancements#1658 .

Doing both the API and implementation in one PR works for me, having the API available but broken is not really helping anything.

@@ -167,6 +170,22 @@ type prSigstoreSignedFulcio struct {
SubjectEmail string `json:"subjectEmail,omitempty"`
}

// prSigstoreSignedPKI contains non-fulcio certificate PKI configuration options for prSigstoreSigned
type prSigstoreSignedPKI struct {
Copy link
Collaborator

Choose a reason for hiding this comment

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

There should be some public type callers can use to refer to the value. Following the other examples here, probably an interface with a private method, and public functions to construct it (NewPRSigstoreSignedPKI etc.) using the “functional option” pattern.

CAIntermediatesPath string `json:"caIntermediatesPath"`
// CAIntermediatesData contains accepted CA intermediate certificates in PEM format, all of that base64-encoded. Only one of CAIntermediatesPath or CAIntermediatesData can be specified, not both.
CAIntermediatesData []byte `json:"caIntermediatesData"`
// SubjectEmail specifies the expected email address imposed on the subject to which the certificate was issued. Exactly one of SubjectEmail and Hostname must be specified.
Copy link
Collaborator

Choose a reason for hiding this comment

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

(Nit: A blank line above would separate the two kinds of fields.)

// SubjectEmail specifies the expected email address imposed on the subject to which the certificate was issued. Exactly one of SubjectEmail and Hostname must be specified.
SubjectEmail string `json:"subjectEmail"`
// Hostname specifies the expected hostname imposed on the subject to which the certificate was issued. Exactly one of SubjectEmail and Hostname must be specified.
Hostname string `json:"hostname"`
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
Hostname string `json:"hostname"`
SubjectHostname string `json:"subjectHostname"`

@QiWang19 QiWang19 changed the title WIP: policy.json BYOPKI signature verification API policy.json BYOPKI signature verification API Oct 3, 2024
@QiWang19 QiWang19 marked this pull request as ready for review October 3, 2024 02:12
@QiWang19
Copy link
Collaborator Author

QiWang19 commented Oct 3, 2024

@mtrmac Could you please take a look? It's ready for review. I am unsure about how I’m handling the intermediate certificates.

Copy link
Collaborator

@mtrmac mtrmac left a comment

Choose a reason for hiding this comment

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

Just a few-minute first pass — this looks very good. I didn’t read this carefully, and I skipped the tests completely, for now.

Combining the policy-provided and signature-provided intermediate certificates makes sense to me, but I’ll at least check what Cosign does.

Comment on lines +58 to +81
untrustedIntermediatePool := x509.NewCertPool()
if len(untrustedIntermediateChainBytes) > 0 {
untrustedIntermediateChain, err := cryptoutils.UnmarshalCertificatesFromPEM(untrustedIntermediateChainBytes)
if err != nil {
return nil, internal.NewInvalidSignatureError(fmt.Sprintf("loading certificate chain: %v", err))
}
if len(untrustedIntermediateChain) > 1 {
for _, untrustedIntermediateCert := range untrustedIntermediateChain {
if isIntermediateCA(untrustedIntermediateCert) {
untrustedIntermediatePool.AddCert(untrustedIntermediateCert)
}
}
}
}

if len(pkiTrustRoot.caIntermediateCertificates) > 0 {
trustedIntermediates, err := cryptoutils.UnmarshalCertificatesFromPEM(pkiTrustRoot.caIntermediateCertificates)
if err != nil {
return nil, internal.NewInvalidSignatureError(fmt.Sprintf("loading trusted intermediate certificates: %v", err))
}
for _, trustedIntermediateCert := range trustedIntermediates {
untrustedIntermediatePool.AddCert(trustedIntermediateCert)
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this can all happen in prSigstoreSignedPKI.prepareTrustRoot:

  • With incorrect configurations we don’t even start parsing the high-risk signature itself.
  • It’s a long-term goal to store the trust root data in PolicyContext instead of doing that work on every single signature verification. (That’s certainly not this PR, just that structuring the code to make that possible would be nice.)

}

// isIntermediateCA checks if the certificate is an intermediate CA
func isIntermediateCA(cert *x509.Certificate) bool {
Copy link
Collaborator

Choose a reason for hiding this comment

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

It’s not immediately clear to me that this is necessary, I’d expect the crypto/x509 code to validate the intermediates are usable. Is there a specific reason to filter them this way? If so, it would be nice to document that.

Comment on lines 203 to +210
case trustRoot.publicKeys != nil && trustRoot.fulcio != nil: // newPRSigstoreSigned rejects such combinations.
return sarRejected, errors.New("Internal inconsistency: Both a public key and Fulcio CA specified")
case trustRoot.publicKeys == nil && trustRoot.fulcio == nil: // newPRSigstoreSigned rejects such combinations.
return sarRejected, errors.New("Internal inconsistency: Neither a public key nor a Fulcio CA specified")
case trustRoot.publicKeys != nil && trustRoot.pki != nil: // newPRSigstoreSigned rejects such combinations.
return sarRejected, errors.New("Internal inconsistency: Both a public key and PKI specified")
case trustRoot.fulcio != nil && trustRoot.pki != nil: // newPRSigstoreSigned rejects such combinations.
return sarRejected, errors.New("Internal inconsistency: Both Fulcio CA and PKI specified")
case trustRoot.publicKeys == nil && trustRoot.fulcio == nil && trustRoot.pki == nil: // newPRSigstoreSigned rejects such combinations.
return sarRejected, errors.New("Internal inconsistency: A public key, Fulcio, or PKI must be specified.")
Copy link
Collaborator

@mtrmac mtrmac Oct 3, 2024

Choose a reason for hiding this comment

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

This O(N^2) item list is getting large — maybe switching this to check counting keySources == 1 would be easier to follow.

return nil, err
}
if caIntermediatesCertPEMs != nil {
pki.caIntermediateCertificates = caIntermediatesCertPEMs[0]
Copy link
Collaborator

Choose a reason for hiding this comment

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

A sanity check that len() == 1 would be nice here.

@mtrmac
Copy link
Collaborator

mtrmac commented Oct 3, 2024

Cc: @Honny1 as well — c/image/signature is a somewhat separate part of the codebase, and as security-critical with a lot of paranoia. Worth understanding the structure and idioms.

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.

2 participants