Skip to content

Commit

Permalink
add 'cosign sign' command-line parameters for mTLS
Browse files Browse the repository at this point in the history
Add command-line parameters for key/cert/cacert used for
the connection to the TSA server.

Fixes #3006

Signed-off-by: Dmitry S <dsavints@gmail.com>
  • Loading branch information
dmitris committed Jun 27, 2023
1 parent f3bbc60 commit e9ebc25
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 2 deletions.
4 changes: 4 additions & 0 deletions cmd/cosign/cli/options/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ type KeyOpts struct {
OIDCProvider string // Specify which OIDC credential provider to use for keyless signer
BundlePath string
SkipConfirmation bool
TSAClientCACert string
TSAClientCert string
TSAClientKey string
TSAServerName string // expected SAN field in the TSA server's certificate - https://pkg.go.dev/crypto/tls#Config.ServerName
TSAServerURL string
RFC3161TimestampPath string
TSACertChainPath string
Expand Down
18 changes: 18 additions & 0 deletions cmd/cosign/cli/options/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ type SignOptions struct {
Attachment string
SkipConfirmation bool
TlogUpload bool
TSAClientCACert string
TSAClientCert string
TSAClientKey string
TSAServerName string
TSAServerURL string
IssueCertificate bool
SignContainerIdentity string
Expand Down Expand Up @@ -104,9 +108,23 @@ func (o *SignOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().BoolVar(&o.TlogUpload, "tlog-upload", true,
"whether or not to upload to the tlog")

cmd.Flags().StringVar(&o.TSAClientCACert, "timestamp-client-cacert", "",
"path to the X.509 CA certificate file in PEM format to be used for the connection to the TSA Server")

cmd.Flags().StringVar(&o.TSAClientCert, "timestamp-client-cert", "",
"path to the X.509 certificate file in PEM format to be used for the connection to the TSA Server")

cmd.Flags().StringVar(&o.TSAClientKey, "timestamp-client-key", "",
"path to the X.509 private key file in PEM format to be used, together with the 'timestamp-client-cert' value, for the connection to the TSA Server")

cmd.Flags().StringVar(&o.TSAServerName, "timestamp-server-name", "",
"SAN name to use as the 'ServerName' tls.Config field to verify the mTLS connection to the TSA Server")

cmd.Flags().StringVar(&o.TSAServerURL, "timestamp-server-url", "",
"url to the Timestamp RFC3161 server, default none. Must be the path to the API to request timestamp responses, e.g. https://freetsa.org/tsr")

_ = cmd.Flags().SetAnnotation("certificate", cobra.BashCompFilenameExt, []string{"cert"})

cmd.Flags().BoolVar(&o.IssueCertificate, "issue-certificate", false,
"issue a code signing certificate from Fulcio, even if a key is provided")

Expand Down
4 changes: 4 additions & 0 deletions cmd/cosign/cli/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ race conditions or (worse) malicious tampering.
OIDCDisableProviders: o.OIDC.DisableAmbientProviders,
OIDCProvider: o.OIDC.Provider,
SkipConfirmation: o.SkipConfirmation,
TSAClientCACert: o.TSAClientCACert,
TSAClientCert: o.TSAClientCert,
TSAClientKey: o.TSAClientKey,
TSAServerName: o.TSAServerName,
TSAServerURL: o.TSAServerURL,
IssueCertificateForExistingKey: o.IssueCertificate,
}
Expand Down
11 changes: 10 additions & 1 deletion cmd/cosign/cli/sign/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,16 @@ func signDigest(ctx context.Context, digest name.Digest, payload []byte, ko opti
}

if ko.TSAServerURL != "" {
s = tsa.NewSigner(s, client.NewTSAClient(ko.TSAServerURL))
if ko.TSAClientCACert == "" && ko.TSAClientCert == "" { // no mTLS params or custom CA
s = tsa.NewSigner(s, client.NewTSAClient(ko.TSAServerURL))
} else {
s = tsa.NewSigner(s, client.NewTSAClientMTLS(ko.TSAServerURL,
ko.TSAClientCACert,
ko.TSAClientCert,
ko.TSAClientKey,
ko.TSAServerName,
))
}
}
shouldUpload, err := ShouldUploadToTlog(ctx, ko, digest, signOpts.TlogUpload)
if err != nil {
Expand Down
4 changes: 4 additions & 0 deletions doc/cosign_sign.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

112 changes: 111 additions & 1 deletion internal/pkg/cosign/tsa/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ package client

import (
"bytes"
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"net"
"net/http"
"os"
"time"
Expand All @@ -37,17 +40,113 @@ type TimestampAuthorityClientImpl struct {

// URL is the path to the API to request timestamp responses
URL string
// CACert is the filepath to the PEM-encoded CA certificate for the connection to the TSA server
CACert string
// Cert is the filepath to the PEM-encoded certificate for the connection to the TSA server
Cert string
// Cert is the filepath to the PEM-encoded key corresponding to the certificate for the connection to the TSA server
Key string
// ServerName is the expected SAN value in the server's certificate - used for https://pkg.go.dev/crypto/tls#Config.ServerName
ServerName string

// Timeout is the request timeout
Timeout time.Duration
}

const defaultTimeout = 10 * time.Second

func getHTTPTransport(cacertFilename, certFilename, keyFilename, serverName string, timeout time.Duration) (http.RoundTripper, error) {
if timeout == 0 {
timeout = defaultTimeout
}
tr := &http.Transport{
TLSClientConfig: &tls.Config{
CipherSuites: []uint16{
// TLS 1.3 cipher suites.
tls.TLS_AES_128_GCM_SHA256,
tls.TLS_AES_256_GCM_SHA384,
tls.TLS_CHACHA20_POLY1305_SHA256,
},
MinVersion: tls.VersionTLS13,
SessionTicketsDisabled: true,
},
// the rest of default settings are copied verbatim from https://golang.org/pkg/net/http/#DefaultTransport
// to minimize surprises for the users.
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: timeout,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
var pool *x509.CertPool
if cacertFilename != "" {
f, err := os.Open(cacertFilename)
if err != nil {
return nil, err
}
defer f.Close()
caCertBytes, err := io.ReadAll(f)
if err != nil {
return nil, fmt.Errorf("unable to read CA certs from %s: %w", cacertFilename, err)
}
pool = x509.NewCertPool()
if !pool.AppendCertsFromPEM(caCertBytes) {
return nil, fmt.Errorf("no valid CA certs found in %s", cacertFilename)
}
tr.TLSClientConfig.RootCAs = pool
}
if certFilename != "" && keyFilename != "" {
cert, err := tls.LoadX509KeyPair(certFilename, keyFilename)
if err != nil {
return nil, fmt.Errorf("unable to read CA certs from cert %s, key %s: %w",
certFilename, keyFilename, err)
}
tr.TLSClientConfig.Certificates = []tls.Certificate{cert}
}

if serverName != "" {
// copied from https://pkg.go.dev/crypto/tls#example-Config-VerifyConnection,
// changed the DNSName setting from cs.Servername to serverName
tr.TLSClientConfig.InsecureSkipVerify = true
tr.TLSClientConfig.VerifyConnection = func(cs tls.ConnectionState) error {
opts := x509.VerifyOptions{
DNSName: serverName,
Intermediates: x509.NewCertPool(),
Roots: pool,
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
}
for _, cert := range cs.PeerCertificates[1:] {
opts.Intermediates.AddCert(cert)
}

_, err := cs.PeerCertificates[0].Verify(opts)
return err
}
}
return tr, nil
}

// GetTimestampResponse sends a timestamp query to a timestamp authority, returning a timestamp response.
// The query and response are defined by RFC 3161.
func (t *TimestampAuthorityClientImpl) GetTimestampResponse(tsq []byte) ([]byte, error) {
client := http.Client{
Timeout: t.Timeout,
}

// if mTLS-related fields are set, create a custom Transport for the Client
if t.CACert != "" || t.Cert != "" {
tr, err := getHTTPTransport(t.CACert, t.Cert, t.Key, t.ServerName, t.Timeout)
if err != nil {
return nil, err
}
client.Transport = tr
}

req, err := http.NewRequest("POST", t.URL, bytes.NewReader(tsq))
if err != nil {
return nil, errors.Wrap(err, "error creating HTTP request")
Expand Down Expand Up @@ -79,5 +178,16 @@ func (t *TimestampAuthorityClientImpl) GetTimestampResponse(tsq []byte) ([]byte,
}

func NewTSAClient(url string) *TimestampAuthorityClientImpl {
return &TimestampAuthorityClientImpl{URL: url, Timeout: 10 * time.Second}
return &TimestampAuthorityClientImpl{URL: url, Timeout: defaultTimeout}
}

func NewTSAClientMTLS(url, cacert, cert, key, serverName string) *TimestampAuthorityClientImpl {
return &TimestampAuthorityClientImpl{
URL: url,
CACert: cacert,
Cert: cert,
Key: key,
ServerName: serverName,
Timeout: defaultTimeout,
}
}

0 comments on commit e9ebc25

Please sign in to comment.