-
Notifications
You must be signed in to change notification settings - Fork 547
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add trusted-root create helper command (#3876)
* Fixes #3700: add trusted-root create helper command To help cosign users move from providing disparate verification material to a single file that contains the needed verification material. This makes it easier for users to rotate key material and specify what time period different keys were valid. Signed-off-by: Zach Steindler <steiza@github.com> * Linter fixes and docgen Signed-off-by: Zach Steindler <steiza@github.com> * Fix Windows unit test Signed-off-by: Zach Steindler <steiza@github.com> * Output via stdout instead of stderr Signed-off-by: Zach Steindler <steiza@github.com> * Add ctlogs to `cosign trusted-root create` With `--ignore-sct` to support if you are using keys instead of Fulcio. Signed-off-by: Zach Steindler <steiza@github.com> * Replace `--rekor-url` with `--ignore-tlog` Similar to `--ignore-sct` Signed-off-by: Zach Steindler <steiza@github.com> * Just use paths to files on disk Instead of clients querying remote servers Signed-off-by: Zach Steindler <steiza@github.com> * Add the ability to supply multiple verification material Also add ability to specify validity start time for keys Signed-off-by: Zach Steindler <steiza@github.com> * Don't panic if there's unexpected content in PEM file Update tests, also fix documentation for flags that were removed. Co-authored-by: Dmitry S <dsavints@gmail.com> Signed-off-by: Zach Steindler <steiza@github.com> * remove trailing newline Signed-off-by: Zach Steindler <steiza@github.com> * Simplify imports Signed-off-by: Zach Steindler <steiza@github.com> --------- Signed-off-by: Zach Steindler <steiza@github.com> Co-authored-by: Dmitry S <dsavints@gmail.com>
- Loading branch information
Showing
8 changed files
with
532 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.") | ||
_ = 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") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,205 @@ | ||
// | ||
// 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/cosign/v2/pkg/cosign" | ||
"github.com/sigstore/sigstore-go/pkg/root" | ||
"github.com/sigstore/sigstore/pkg/cryptoutils" | ||
) | ||
|
||
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 != nil; 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 | ||
} |
Oops, something went wrong.