-
Notifications
You must be signed in to change notification settings - Fork 550
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
Add trusted-root create helper command #3876
Changes from 8 commits
2552371
ea1c77f
a53ec25
e0041bb
06284e2
b3262d7
cab9148
f705836
b8d58d7
1e7a436
1bd2b08
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
// | ||
// Copyright 2024 The Sigstore Authors. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package options | ||
|
||
import ( | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
type TrustedRootCreateOptions struct { | ||
CertChain []string | ||
CtfeKeyPath []string | ||
CtfeStartTime []string | ||
Out string | ||
RekorKeyPath []string | ||
RekorStartTime []string | ||
TSACertChainPath []string | ||
} | ||
|
||
var _ Interface = (*TrustedRootCreateOptions)(nil) | ||
|
||
func (o *TrustedRootCreateOptions) AddFlags(cmd *cobra.Command) { | ||
cmd.Flags().StringArrayVar(&o.CertChain, "certificate-chain", nil, | ||
"path to a list of CA certificates in PEM format which will be needed "+ | ||
"when building the certificate chain for the signing certificate. "+ | ||
"Must start with the parent intermediate CA certificate of the "+ | ||
"signing certificate and end with the root certificate. Conflicts with --ca-roots and --ca-intermediates.") | ||
_ = cmd.Flags().SetAnnotation("certificate-chain", cobra.BashCompFilenameExt, []string{"cert"}) | ||
|
||
cmd.Flags().StringArrayVar(&o.CtfeKeyPath, "ctfe-key", nil, | ||
"path to a PEM-encoded public key used by certificate authority for "+ | ||
"certificate transparency log.") | ||
|
||
cmd.Flags().StringArrayVar(&o.CtfeStartTime, "ctfe-start-time", nil, | ||
"RFC 3339 string describing validity start time for key use by "+ | ||
"certificate transparency log.") | ||
|
||
cmd.Flags().StringVar(&o.Out, "out", "", "path to output trusted root") | ||
|
||
cmd.Flags().StringArrayVar(&o.RekorKeyPath, "rekor-key", nil, | ||
"path to a PEM-encoded public key used by transparency log like Rekor.") | ||
|
||
cmd.Flags().StringArrayVar(&o.RekorStartTime, "rekor-start-time", nil, | ||
"RFC 3339 string describing validity start time for key use by "+ | ||
"transparency log like Rekor.") | ||
|
||
cmd.Flags().StringArrayVar(&o.TSACertChainPath, "timestamp-certificate-chain", nil, | ||
"path to PEM-encoded certificate chain file for the RFC3161 timestamp authority. Must contain the root CA certificate. "+ | ||
"Optionally may contain intermediate CA certificates") | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I'm leaning towards keeping this tool more minimal and mirroring the flags of existing |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
// | ||
// Copyright 2024 The Sigstore Authors. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package cli | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/spf13/cobra" | ||
|
||
"github.com/sigstore/cosign/v2/cmd/cosign/cli/options" | ||
"github.com/sigstore/cosign/v2/cmd/cosign/cli/trustedroot" | ||
) | ||
|
||
func TrustedRoot() *cobra.Command { | ||
cmd := &cobra.Command{ | ||
Use: "trusted-root", | ||
Short: "Interact with a Sigstore protobuf trusted root", | ||
Long: "Tools for interacting with a Sigstore protobuf trusted root", | ||
} | ||
|
||
cmd.AddCommand(trustedRootCreate()) | ||
|
||
return cmd | ||
} | ||
|
||
func trustedRootCreate() *cobra.Command { | ||
o := &options.TrustedRootCreateOptions{} | ||
|
||
cmd := &cobra.Command{ | ||
Use: "create", | ||
Short: "Create a Sigstore protobuf trusted root", | ||
Long: "Create a Sigstore protobuf trusted root by supplying verification material", | ||
RunE: func(cmd *cobra.Command, _ []string) error { | ||
trCreateCmd := &trustedroot.CreateCmd{ | ||
CertChain: o.CertChain, | ||
CtfeKeyPath: o.CtfeKeyPath, | ||
CtfeStartTime: o.CtfeStartTime, | ||
Out: o.Out, | ||
RekorKeyPath: o.RekorKeyPath, | ||
RekorStartTime: o.RekorStartTime, | ||
TSACertChainPath: o.TSACertChainPath, | ||
} | ||
|
||
ctx, cancel := context.WithTimeout(cmd.Context(), ro.Timeout) | ||
defer cancel() | ||
|
||
return trCreateCmd.Exec(ctx) | ||
}, | ||
} | ||
|
||
o.AddFlags(cmd) | ||
return cmd | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,206 @@ | ||
// | ||
// Copyright 2024 The Sigstore Authors. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package trustedroot | ||
|
||
import ( | ||
"context" | ||
"crypto" | ||
"crypto/x509" | ||
"encoding/hex" | ||
"encoding/pem" | ||
"fmt" | ||
"os" | ||
"time" | ||
|
||
"github.com/sigstore/sigstore-go/pkg/root" | ||
"github.com/sigstore/sigstore/pkg/cryptoutils" | ||
|
||
"github.com/sigstore/cosign/v2/pkg/cosign" | ||
steiza marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) | ||
|
||
type CreateCmd struct { | ||
CertChain []string | ||
CtfeKeyPath []string | ||
CtfeStartTime []string | ||
Out string | ||
RekorKeyPath []string | ||
RekorStartTime []string | ||
TSACertChainPath []string | ||
} | ||
|
||
func (c *CreateCmd) Exec(_ context.Context) error { | ||
var fulcioCertAuthorities []root.CertificateAuthority | ||
ctLogs := make(map[string]*root.TransparencyLog) | ||
var timestampAuthorities []root.CertificateAuthority | ||
rekorTransparencyLogs := make(map[string]*root.TransparencyLog) | ||
|
||
for i := 0; i < len(c.CertChain); i++ { | ||
fulcioAuthority, err := parsePEMFile(c.CertChain[i]) | ||
if err != nil { | ||
return err | ||
} | ||
fulcioCertAuthorities = append(fulcioCertAuthorities, *fulcioAuthority) | ||
} | ||
|
||
for i := 0; i < len(c.CtfeKeyPath); i++ { | ||
ctLogPubKey, id, idBytes, err := getPubKey(c.CtfeKeyPath[i]) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
startTime := time.Unix(0, 0) | ||
|
||
if i < len(c.CtfeStartTime) { | ||
startTime, err = time.Parse(time.RFC3339, c.CtfeStartTime[i]) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
|
||
ctLogs[id] = &root.TransparencyLog{ | ||
HashFunc: crypto.SHA256, | ||
ID: idBytes, | ||
ValidityPeriodStart: startTime, | ||
PublicKey: *ctLogPubKey, | ||
SignatureHashFunc: crypto.SHA256, | ||
} | ||
} | ||
|
||
for i := 0; i < len(c.RekorKeyPath); i++ { | ||
tlogPubKey, id, idBytes, err := getPubKey(c.RekorKeyPath[i]) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
startTime := time.Unix(0, 0) | ||
|
||
if i < len(c.RekorStartTime) { | ||
startTime, err = time.Parse(time.RFC3339, c.RekorStartTime[i]) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
|
||
rekorTransparencyLogs[id] = &root.TransparencyLog{ | ||
HashFunc: crypto.SHA256, | ||
ID: idBytes, | ||
ValidityPeriodStart: startTime, | ||
PublicKey: *tlogPubKey, | ||
SignatureHashFunc: crypto.SHA256, | ||
} | ||
} | ||
|
||
for i := 0; i < len(c.TSACertChainPath); i++ { | ||
timestampAuthority, err := parsePEMFile(c.TSACertChainPath[i]) | ||
if err != nil { | ||
return err | ||
} | ||
timestampAuthorities = append(timestampAuthorities, *timestampAuthority) | ||
} | ||
|
||
newTrustedRoot, err := root.NewTrustedRoot(root.TrustedRootMediaType01, | ||
fulcioCertAuthorities, ctLogs, timestampAuthorities, | ||
rekorTransparencyLogs, | ||
) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
var trBytes []byte | ||
|
||
trBytes, err = newTrustedRoot.MarshalJSON() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if c.Out != "" { | ||
err = os.WriteFile(c.Out, trBytes, 0600) | ||
if err != nil { | ||
return err | ||
} | ||
} else { | ||
fmt.Println(string(trBytes)) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func parsePEMFile(path string) (*root.CertificateAuthority, error) { | ||
certs, err := parseCerts(path) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var ca root.CertificateAuthority | ||
ca.Root = certs[len(certs)-1] | ||
ca.ValidityPeriodStart = certs[len(certs)-1].NotBefore | ||
if len(certs) > 1 { | ||
ca.Intermediates = certs[:len(certs)-1] | ||
} | ||
|
||
return &ca, nil | ||
} | ||
|
||
func parseCerts(path string) ([]*x509.Certificate, error) { | ||
var certs []*x509.Certificate | ||
|
||
contents, err := os.ReadFile(path) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
for block, contents := pem.Decode(contents); ; block, contents = pem.Decode(contents) { | ||
cert, err := x509.ParseCertificate(block.Bytes) | ||
if err != nil { | ||
return nil, err | ||
} | ||
certs = append(certs, cert) | ||
|
||
if len(contents) == 0 { | ||
break | ||
} | ||
} | ||
|
||
if len(certs) == 0 { | ||
return nil, fmt.Errorf("no certificates in file %s", path) | ||
} | ||
|
||
return certs, nil | ||
} | ||
|
||
func getPubKey(path string) (*crypto.PublicKey, string, []byte, error) { | ||
pemBytes, err := os.ReadFile(path) | ||
if err != nil { | ||
return nil, "", []byte{}, err | ||
} | ||
|
||
pubKey, err := cryptoutils.UnmarshalPEMToPublicKey(pemBytes) | ||
if err != nil { | ||
return nil, "", []byte{}, err | ||
} | ||
|
||
keyID, err := cosign.GetTransparencyLogID(pubKey) | ||
if err != nil { | ||
return nil, "", []byte{}, err | ||
} | ||
|
||
idBytes, err := hex.DecodeString(keyID) | ||
if err != nil { | ||
return nil, "", []byte{}, err | ||
} | ||
|
||
return &pubKey, keyID, idBytes, nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not seeing a way to provide keys for the CT log service, is that intentional?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sort of - I was trying to mirror the flags of commands like
cosign verify-blob ...
as much as possible, and I didn't see a flag for specifying the public key of the CT log. If there is one, and I missed it, I'm happy to add it!There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is overridden via an environment variable
cosign/pkg/cosign/env/env.go
Line 57 in 780780b
The CTFE key is used for signing, so unfortunately I don't think the flags for verify-blob are a good analog for what needs to be included in the trusted root.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a little tricky because of the diversity of private deployments of Sigstore. I added a
--ignore-sct
so that if the private deployment is using long-lived keys instead of a private Fulcio we don't try to add CT Log keys to their trusted root; otherwise we callcosign.GetCTLogPubs()
which will make use of the environment variable or TUF target.