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

Allow parsing of certificates from Fulcio if ctlog is disabled #288

Merged
merged 4 commits into from
Oct 16, 2024
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
18 changes: 14 additions & 4 deletions pkg/sign/certificate.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,18 @@ type publicKey struct {
}

type fulcioResponse struct {
SctCertWithChain signedCertificateEmbeddedSct `json:"signedCertificateEmbeddedSct"`
SignedCertificateEmbeddedSct signedCertificateEmbeddedSct `json:"signedCertificateEmbeddedSct"`
SignedCertificateDetachedSct signedCertificateDetachedSct `json:"signedCertificateDetachedSct"`
Copy link
Contributor

Choose a reason for hiding this comment

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

We want to deprecate this in fulcio, no other client is using it so far.

Can we avoid adding this?

Copy link
Member Author

Choose a reason for hiding this comment

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

GitHub Artifact Attestations does not use certificate transparency for private CA, using essentially fulcio serve --ct-log-url="", and when configured this way, Fulcio's response includes the certificate in signedCertificateDetachedSct. So this change is currently required to support GitHub's private CA.

Is the deprecation plan in Fulcio to disallow running Fulcio without a CT log?

Copy link
Member

Choose a reason for hiding this comment

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

I would parse this as detached SCT is going to be deprecated, which seems fair?

Copy link
Member Author

@codysoyland codysoyland Sep 9, 2024

Choose a reason for hiding this comment

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

I located the Fulcio issue referencing this deprecation, and I noticed a comment from @bdehamer (with my name cc'd no less 😆 ):

One thing to note . . . we're running a private Fulcio instance w/ NO transparency log. In this mode, I noticed that the response from the create-signing-certificate API always uses the signedCertificateDetachedSct field in the response (despite the fact that there is no SCT). As the code is cleaned-up we'll want to make sure not to change this behavior so we don't break existing clients

This is the behavior I'm wanting to preserve/support in this PR -- not add support for detached SCTs in sigstore-go. I'm open to deprecating that JSON key if we want to improve naming the Fulcio response, but for now, this key is required for us to consume Fulcio certs in GitHub's Fulcio deployment.

@haydentherapper, btw, you commented in that issue that the deprecation would preserve that field for this use case.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry for taking so long to get back to you - yep, you're right, I was thinking we were writing the certificate in embedded still and that this field was only used when issuing a detached SCT. This LGTM, we'll make breaking changes as part of a 2.0 release that fixes this, but no changes now.

}

type signedCertificateEmbeddedSct struct {
Chain chain `json:"chain"`
}

type signedCertificateDetachedSct struct {
Chain chain `json:"chain"`
}

type chain struct {
Certificates []string `json:"certificates"`
}
Expand Down Expand Up @@ -204,12 +209,17 @@ func (f *Fulcio) GetCertificate(ctx context.Context, keypair Keypair, opts *Cert
return nil, err
}

certs := fulcioResp.SctCertWithChain.Chain.Certificates
if len(certs) == 0 {
var cert []byte
switch {
case len(fulcioResp.SignedCertificateEmbeddedSct.Chain.Certificates) > 0:
cert = []byte(fulcioResp.SignedCertificateEmbeddedSct.Chain.Certificates[0])
case len(fulcioResp.SignedCertificateDetachedSct.Chain.Certificates) > 0:
cert = []byte(fulcioResp.SignedCertificateDetachedSct.Chain.Certificates[0])
default:
return nil, errors.New("Fulcio returned no certificates")
}

certBlock, _ := pem.Decode([]byte(certs[0]))
certBlock, _ := pem.Decode(cert)
if certBlock == nil {
return nil, errors.New("unable to parse Fulcio certificate")
}
Expand Down
43 changes: 32 additions & 11 deletions pkg/sign/certificate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func setupVirtualSigstore() {
}
}

func getFulcioResponse() (*http.Response, error) {
func getFulcioResponse(detachedSct bool) (*http.Response, error) {
virtualSigstoreOnce.Do(setupVirtualSigstore)
if virtualSigstoreErr != nil {
return nil, virtualSigstoreErr
Expand All @@ -55,14 +55,24 @@ func getFulcioResponse() (*http.Response, error) {
Bytes: leafCert.Raw,
}))

responseStruct := fulcioResponse{
SctCertWithChain: signedCertificateEmbeddedSct{
Chain: chain{
Certificates: []string{certPEM},
var responseStruct fulcioResponse
if detachedSct {
responseStruct = fulcioResponse{
SignedCertificateDetachedSct: signedCertificateDetachedSct{
Chain: chain{
Certificates: []string{certPEM},
},
},
codysoyland marked this conversation as resolved.
Show resolved Hide resolved
},
}
} else {
responseStruct = fulcioResponse{
SignedCertificateEmbeddedSct: signedCertificateEmbeddedSct{
Chain: chain{
Certificates: []string{certPEM},
},
},
}
}

fulcioJSON, err := json.Marshal(responseStruct)
if err != nil {
return nil, err
Expand All @@ -76,14 +86,17 @@ func getFulcioResponse() (*http.Response, error) {
return response, nil
}

type mockFulcio struct{}
type mockFulcio struct {
detachedSct bool
}

func (m *mockFulcio) RoundTrip(_ *http.Request) (*http.Response, error) {
return getFulcioResponse()
return getFulcioResponse(m.detachedSct)
}

type failFirstFulcio struct {
Count int
Count int
detachedSct bool
}

func (f *failFirstFulcio) RoundTrip(_ *http.Request) (*http.Response, error) {
Expand All @@ -96,7 +109,7 @@ func (f *failFirstFulcio) RoundTrip(_ *http.Request) (*http.Response, error) {
return response, nil
}

return getFulcioResponse()
return getFulcioResponse(f.detachedSct)
}

func Test_GetCertificate(t *testing.T) {
Expand Down Expand Up @@ -135,4 +148,12 @@ func Test_GetCertificate(t *testing.T) {
cert, err = retryFulcio.GetCertificate(ctx, keypair, certOpts)
assert.Nil(t, cert)
assert.NotNil(t, err)

// Test detached SCT
detachedOpts := &FulcioOptions{Retries: 1, Transport: &mockFulcio{detachedSct: true}}
detachedFulcio := NewFulcio(detachedOpts)

cert, err = detachedFulcio.GetCertificate(ctx, keypair, certOpts)
assert.NotNil(t, cert)
assert.NoError(t, err)
}