From 126474ccb4019876d4e5a6ad9bb53fce95a468e2 Mon Sep 17 00:00:00 2001 From: Dmitry Savintsev Date: Fri, 25 Aug 2023 17:41:47 +0200 Subject: [PATCH] add mTLS to TSA for sign-blob command (#3200) * add mTLS to TSA for sign-blob Signed-off-by: Dmitry S * add functional test for sign-blobl mTLS to TSA Signed-off-by: Dmitry S --------- Signed-off-by: Dmitry S --- .github/workflows/e2e-tests.yml | 4 ++ cmd/cosign/cli/options/signblob.go | 16 +++++ cmd/cosign/cli/sign/sign_blob.go | 30 +++++++--- cmd/cosign/cli/signblob.go | 4 ++ doc/cosign_sign-blob.md | 4 ++ test/e2e_signblob_tsa_mtls.sh | 93 ++++++++++++++++++++++++++++++ test/e2e_tsa_mtls.sh | 7 ++- 7 files changed, 146 insertions(+), 12 deletions(-) create mode 100755 test/e2e_signblob_tsa_mtls.sh diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 2c14d77ab1c..57484258ad7 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -69,3 +69,7 @@ jobs: - name: Run e2e_tsa_mtls.sh shell: bash run: make && PATH="$PWD:$PATH" ./test/e2e_tsa_mtls.sh + + - name: Run e2e_signblob_tsa_mtls.sh + shell: bash + run: make && PATH="$PWD:$PATH" ./test/e2e_signblob_tsa_mtls.sh diff --git a/cmd/cosign/cli/options/signblob.go b/cmd/cosign/cli/options/signblob.go index 99c1a473a52..7cddde63dfc 100644 --- a/cmd/cosign/cli/options/signblob.go +++ b/cmd/cosign/cli/options/signblob.go @@ -35,6 +35,10 @@ type SignBlobOptions struct { BundlePath string SkipConfirmation bool TlogUpload bool + TSAClientCACert string + TSAClientCert string + TSAClientKey string + TSAServerName string TSAServerURL string RFC3161TimestampPath string IssueCertificate bool @@ -77,6 +81,18 @@ func (o *SignBlobOptions) 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") diff --git a/cmd/cosign/cli/sign/sign_blob.go b/cmd/cosign/cli/sign/sign_blob.go index 8226ebf6a72..b60db5b7a28 100644 --- a/cmd/cosign/cli/sign/sign_blob.go +++ b/cmd/cosign/cli/sign/sign_blob.go @@ -24,15 +24,14 @@ import ( "os" "path/filepath" - "github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa" - "github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa/client" - "github.com/sigstore/cosign/v2/internal/ui" - cbundle "github.com/sigstore/cosign/v2/pkg/cosign/bundle" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/cmd/cosign/cli/rekor" internal "github.com/sigstore/cosign/v2/internal/pkg/cosign" + "github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa" + "github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa/client" + "github.com/sigstore/cosign/v2/internal/ui" "github.com/sigstore/cosign/v2/pkg/cosign" + cbundle "github.com/sigstore/cosign/v2/pkg/cosign/bundle" "github.com/sigstore/sigstore/pkg/cryptoutils" signatureoptions "github.com/sigstore/sigstore/pkg/signature/options" ) @@ -77,10 +76,23 @@ func SignBlobCmd(ro *options.RootOptions, ko options.KeyOpts, payloadPath string if ko.RFC3161TimestampPath == "" { return nil, fmt.Errorf("timestamp output path must be set") } - - respBytes, err := tsa.GetTimestampedSignature(sig, client.NewTSAClient(ko.TSAServerURL)) - if err != nil { - return nil, err + var respBytes []byte + var err error + if ko.TSAClientCACert == "" && ko.TSAClientCert == "" { // no mTLS params or custom CA + respBytes, err = tsa.GetTimestampedSignature(sig, client.NewTSAClient(ko.TSAServerURL)) + if err != nil { + return nil, err + } + } else { + respBytes, err = tsa.GetTimestampedSignature(sig, client.NewTSAClientMTLS(ko.TSAServerURL, + ko.TSAClientCACert, + ko.TSAClientCert, + ko.TSAClientKey, + ko.TSAServerName, + )) + if err != nil { + return nil, err + } } rfc3161Timestamp = cbundle.TimestampToRFC3161Timestamp(respBytes) diff --git a/cmd/cosign/cli/signblob.go b/cmd/cosign/cli/signblob.go index 50a89f6b076..ce3035a1d15 100644 --- a/cmd/cosign/cli/signblob.go +++ b/cmd/cosign/cli/signblob.go @@ -84,6 +84,10 @@ func SignBlob() *cobra.Command { OIDCDisableProviders: o.OIDC.DisableAmbientProviders, BundlePath: o.BundlePath, SkipConfirmation: o.SkipConfirmation, + TSAClientCACert: o.TSAClientCACert, + TSAClientCert: o.TSAClientCert, + TSAClientKey: o.TSAClientKey, + TSAServerName: o.TSAServerName, TSAServerURL: o.TSAServerURL, RFC3161TimestampPath: o.RFC3161TimestampPath, IssueCertificateForExistingKey: o.IssueCertificate, diff --git a/doc/cosign_sign-blob.md b/doc/cosign_sign-blob.md index d58f0367985..f49b5461906 100644 --- a/doc/cosign_sign-blob.md +++ b/doc/cosign_sign-blob.md @@ -57,6 +57,10 @@ cosign sign-blob [flags] --rfc3161-timestamp string write the RFC3161 timestamp to a file --sk whether to use a hardware security key --slot string security key slot to use for generated key (default: signature) (authentication|signature|card-authentication|key-management) + --timestamp-client-cacert string path to the X.509 CA certificate file in PEM format to be used for the connection to the TSA Server + --timestamp-client-cert string path to the X.509 certificate file in PEM format to be used for the connection to the TSA Server + --timestamp-client-key string 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 + --timestamp-server-name string SAN name to use as the 'ServerName' tls.Config field to verify the mTLS connection to the TSA Server --timestamp-server-url string 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 --tlog-upload whether or not to upload to the tlog (default true) -y, --yes skip confirmation prompts for non-destructive operations diff --git a/test/e2e_signblob_tsa_mtls.sh b/test/e2e_signblob_tsa_mtls.sh new file mode 100755 index 00000000000..ed36543d361 --- /dev/null +++ b/test/e2e_signblob_tsa_mtls.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash +# +# Copyright 2023 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. + +# This test checks that verify-blob will iterate over all entries and check for at least one valid entry before erroring out +# This is to prevent verify-blob from only checking the most recent entry, which could result +# in a "denial of service" type attack if someone signs a piece of software +# with their own certificate which doesn't chain up to Sigstore + +set -ex + +COSIGN_CLI=./cosign +CERT_BASE="test/testdata" + +# the certificates listed below are generated with the `gen-tsa-mtls-certs.sh` script. +TIMESTAMP_CACERT=$CERT_BASE/tsa-mtls-ca.crt +TIMESTAMP_CLIENT_CERT=$CERT_BASE/tsa-mtls-client.crt +TIMESTAMP_CLIENT_KEY=$CERT_BASE/tsa-mtls-client.key +TIMESTAMP_SERVER_CERT=$CERT_BASE/tsa-mtls-server.crt +TIMESTAMP_SERVER_KEY=$CERT_BASE/tsa-mtls-server.key +TIMESTAMP_SERVER_NAME="server.example.com" +TIMESTAMP_SERVER_URL=https://localhost:3000/api/v1/timestamp +TIMESTAMP_CHAIN_FILE="timestamp-chain.pem" + +set +e +COSIGN_CLI=./cosign +command -v timestamp-server >& /dev/null +exit_code=$? +set -e +if [[ $exit_code != 0 ]]; then + rm -fr /tmp/timestamp-authority + git clone https://github.com/sigstore/timestamp-authority /tmp/timestamp-authority + pushd /tmp/timestamp-authority + make + export PATH="/tmp/timestamp-authority/bin:$PATH" + popd +fi + +timestamp-server serve --disable-ntp-monitoring --tls-host 0.0.0.0 --tls-port 3000 \ + --scheme https --tls-ca $TIMESTAMP_CACERT --tls-key $TIMESTAMP_SERVER_KEY \ + --tls-certificate $TIMESTAMP_SERVER_CERT & + +sleep 1 +curl -k -s --key test/testdata/tsa-mtls-client.key \ + --cert test/testdata/tsa-mtls-client.crt \ + --cacert test/testdata/tsa-mtls-ca.crt https://localhost:3000/api/v1/timestamp/certchain \ + > $TIMESTAMP_CHAIN_FILE +echo "DONE: $(ls -l $TIMESTAMP_CHAIN_FILE)" +echo "Creating a unique blob" +BLOB=verify-experimental-blob +date > $BLOB +cat $BLOB + +rm -f ca-key.pem cacert.pem cert.pem key.pem import-cosign.* +# use gencert to generate CA, keys and certificates +echo "generate keys and certificates with gencert" + +passwd=$(uuidgen | head -c 32 | tr 'A-Z' 'a-z') +go run test/gencert/main.go \ + && COSIGN_PASSWORD="$passwd" $COSIGN_CLI import-key-pair --key key.pem + +echo "Sign the blob with cosign first and upload to rekor" +COSIGN_PASSWORD="$passwd" $COSIGN_CLI sign-blob --yes \ + --key import-cosign.key \ + --timestamp-server-url "${TIMESTAMP_SERVER_URL}" \ + --timestamp-client-cacert ${TIMESTAMP_CACERT} --timestamp-client-cert ${TIMESTAMP_CLIENT_CERT} \ + --timestamp-client-key ${TIMESTAMP_CLIENT_KEY} --timestamp-server-name ${TIMESTAMP_SERVER_NAME} \ + --rfc3161-timestamp=timestamp.txt --tlog-upload=false \ + --bundle cosign.bundle $BLOB + +echo "Verifying ..." +$COSIGN_CLI verify-blob --bundle cosign.bundle \ + --certificate-identity-regexp '.*' --certificate-oidc-issuer-regexp '.*' \ + --rfc3161-timestamp=timestamp.txt --timestamp-certificate-chain=$TIMESTAMP_CHAIN_FILE \ + --insecure-ignore-tlog=true --key import-cosign.pub $BLOB + +# cleanup +rm -fr blob.sig ca-key.pem cacert.pem cert.pem cosign.bundle import-cosign.key \ + import-cosign.pub key.pem timestamp.txt timestamp-chain.pem \ + /tmp/timestamp-authority verify-experimental-blob +pkill -f 'timestamp-server' diff --git a/test/e2e_tsa_mtls.sh b/test/e2e_tsa_mtls.sh index 177abc33d17..8ee32b09098 100755 --- a/test/e2e_tsa_mtls.sh +++ b/test/e2e_tsa_mtls.sh @@ -33,6 +33,7 @@ TIMESTAMP_SERVER_NAME="server.example.com" TIMESTAMP_SERVER_URL=https://localhost:3000/api/v1/timestamp set +e +COSIGN_CLI=./cosign command -v timestamp-server >& /dev/null exit_code=$? set -e @@ -71,9 +72,9 @@ rm -f *.pem import-cosign.* key.pem echo "generate keys and certificates with gencert" passwd=$(uuidgen | head -c 32 | tr 'A-Z' 'a-z') -rm -f *.pem import-cosign.* && go run test/gencert/main.go && COSIGN_PASSWORD="$passwd" cosign import-key-pair --key key.pem +rm -f *.pem import-cosign.* && go run test/gencert/main.go && COSIGN_PASSWORD="$passwd" $COSIGN_CLI import-key-pair --key key.pem -COSIGN_PASSWORD="$passwd" cosign sign --timestamp-server-url "${TIMESTAMP_SERVER_URL}" \ +COSIGN_PASSWORD="$passwd" $COSIGN_CLI sign --timestamp-server-url "${TIMESTAMP_SERVER_URL}" \ --timestamp-client-cacert ${TIMESTAMP_CACERT} --timestamp-client-cert ${TIMESTAMP_CLIENT_CERT} \ --timestamp-client-key ${TIMESTAMP_CLIENT_KEY} --timestamp-server-name ${TIMESTAMP_SERVER_NAME}\ --upload=true --tlog-upload=false --key import-cosign.key --certificate-chain cacert.pem --cert cert.pem $IMG @@ -82,7 +83,7 @@ COSIGN_PASSWORD="$passwd" cosign sign --timestamp-server-url "${TIMESTAMP_SERVER rm -f key.pem import-cosign.* echo "cosign verify:" -cosign verify --insecure-ignore-tlog --insecure-ignore-sct --check-claims=true \ +$COSIGN_CLI verify --insecure-ignore-tlog --insecure-ignore-sct --check-claims=true \ --certificate-identity-regexp 'xyz@nosuchprovider.com' --certificate-oidc-issuer-regexp '.*' \ --certificate-chain cacert.pem $IMG