-
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.
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 <[email protected]>
- Loading branch information
Showing
7 changed files
with
458 additions
and
6 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,65 @@ | ||
// | ||
// 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 { | ||
CAIntermediates string | ||
CARoots string | ||
CertChain string | ||
Out string | ||
RekorURL string | ||
TSACertChainPath string | ||
} | ||
|
||
var _ Interface = (*TrustedRootCreateOptions)(nil) | ||
|
||
func (o *TrustedRootCreateOptions) AddFlags(cmd *cobra.Command) { | ||
cmd.Flags().StringVar(&o.CAIntermediates, "ca-intermediates", "", | ||
"path to a file of intermediate CA certificates in PEM format which will be needed "+ | ||
"when building the certificate chains for the signing certificate. "+ | ||
"The flag is optional and must be used together with --ca-roots, conflicts with "+ | ||
"--certificate-chain.") | ||
_ = cmd.Flags().SetAnnotation("ca-intermediates", cobra.BashCompFilenameExt, []string{"cert"}) | ||
|
||
cmd.Flags().StringVar(&o.CARoots, "ca-roots", "", | ||
"path to a bundle file of CA certificates in PEM format which will be needed "+ | ||
"when building the certificate chains for the signing certificate. Conflicts with --certificate-chain.") | ||
_ = cmd.Flags().SetAnnotation("ca-roots", cobra.BashCompFilenameExt, []string{"cert"}) | ||
|
||
cmd.Flags().StringVar(&o.CertChain, "certificate-chain", "", | ||
"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.MarkFlagsMutuallyExclusive("ca-roots", "certificate-chain") | ||
cmd.MarkFlagsMutuallyExclusive("ca-intermediates", "certificate-chain") | ||
|
||
cmd.Flags().StringVar(&o.Out, "out", "", | ||
"path to output trusted root") | ||
|
||
cmd.Flags().StringVar(&o.RekorURL, "rekor-url", "", | ||
"address of rekor STL server") | ||
|
||
cmd.Flags().StringVar(&o.TSACertChainPath, "timestamp-certificate-chain", "", | ||
"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,65 @@ | ||
// | ||
// 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, args []string) error { | ||
trCreateCmd := &trustedroot.TrustedRootCreateCmd{ | ||
CAIntermediates: o.CAIntermediates, | ||
CARoots: o.CARoots, | ||
CertChain: o.CertChain, | ||
Out: o.Out, | ||
RekorURL: o.RekorURL, | ||
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,194 @@ | ||
// | ||
// 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/sha256" | ||
"crypto/x509" | ||
"encoding/base64" | ||
"encoding/pem" | ||
"errors" | ||
"fmt" | ||
"os" | ||
|
||
"github.com/sigstore/sigstore-go/pkg/root" | ||
|
||
"github.com/sigstore/cosign/v2/cmd/cosign/cli/rekor" | ||
"github.com/sigstore/cosign/v2/internal/ui" | ||
) | ||
|
||
type TrustedRootCreateCmd struct { | ||
CAIntermediates string | ||
CARoots string | ||
CertChain string | ||
Out string | ||
RekorURL string | ||
TSACertChainPath string | ||
} | ||
|
||
func (c *TrustedRootCreateCmd) Exec(ctx context.Context) error { | ||
var fulcioCertAuthorities []root.CertificateAuthority | ||
var timestampAuthorities []root.CertificateAuthority | ||
rekorTransparencyLogs := make(map[string]*root.TransparencyLog) | ||
|
||
if c.CertChain != "" { | ||
fulcioAuthority, err := parsePEMFile(c.CertChain) | ||
if err != nil { | ||
return err | ||
} | ||
fulcioCertAuthorities = append(fulcioCertAuthorities, *fulcioAuthority) | ||
|
||
} else if c.CARoots != "" { | ||
roots, err := parseCerts(c.CARoots) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
var intermediates []*x509.Certificate | ||
if c.CAIntermediates != "" { | ||
intermediates, err = parseCerts(c.CAIntermediates) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
|
||
// Here we're trying to "flatten" the x509.CertPool cosign was using | ||
// into a trusted root with a clear mapping between roots and | ||
// intermediates. Make a guess that if there are intermediates, there | ||
// is one per root. | ||
|
||
for i, rootCert := range roots { | ||
var fulcioAuthority root.CertificateAuthority | ||
fulcioAuthority.Root = rootCert | ||
if i < len(intermediates) { | ||
fulcioAuthority.Intermediates = []*x509.Certificate{intermediates[i]} | ||
} | ||
fulcioCertAuthorities = append(fulcioCertAuthorities, fulcioAuthority) | ||
} | ||
} | ||
|
||
if c.RekorURL != "" { | ||
rekorClient, err := rekor.NewClient(c.RekorURL) | ||
if err != nil { | ||
return fmt.Errorf("creating Rekor client: %w", err) | ||
} | ||
|
||
rekorPubKey, err := rekorClient.Pubkey.GetPublicKey(nil) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
block, _ := pem.Decode([]byte(rekorPubKey.Payload)) | ||
if block == nil { | ||
return errors.New("failed to decode public key of server") | ||
} | ||
|
||
pub, err := x509.ParsePKIXPublicKey(block.Bytes) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
keyHash := sha256.Sum256(block.Bytes) | ||
keyID := base64.StdEncoding.EncodeToString(keyHash[:]) | ||
|
||
rekorTransparencyLog := root.TransparencyLog{ | ||
BaseURL: c.RekorURL, | ||
HashFunc: crypto.Hash(crypto.SHA256), | ||
ID: keyHash[:], | ||
PublicKey: pub, | ||
SignatureHashFunc: crypto.Hash(crypto.SHA256), | ||
} | ||
|
||
rekorTransparencyLogs[keyID] = &rekorTransparencyLog | ||
} | ||
|
||
if c.TSACertChainPath != "" { | ||
timestampAuthority, err := parsePEMFile(c.TSACertChainPath) | ||
if err != nil { | ||
return err | ||
} | ||
timestampAuthorities = append(timestampAuthorities, *timestampAuthority) | ||
} | ||
|
||
newTrustedRoot, err := root.NewTrustedRoot(root.TrustedRootMediaType01, | ||
fulcioCertAuthorities, nil, 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, 0640) | ||
if err != nil { | ||
return err | ||
} | ||
} else { | ||
ui.Infof(ctx, 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] | ||
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 | ||
} |
Oops, something went wrong.