From d31b5e0dbdeecf5d45402a332362859826adb2d6 Mon Sep 17 00:00:00 2001 From: Dmitry S Date: Thu, 1 Feb 2024 17:06:37 +0100 Subject: [PATCH] Add --ca-intermediates flag Add --ca-intermediates flag to enable to pass a PEM file with intermediate CA certificates. One can use either --ca-roots, optionally together with --ca-intermediates - or --certificate-chain, which contains zero, one or several intermediate CA certificate followed by the root CA certificate. Expand the helper Go program test/gencert/main.go to allow to generate root and intermediate CA certificates, and a certificate signed by the intermediate CA. Expand the functional test e2e_tsa_certbundle.sh to test the --ca-intermediates flag (together with --ca-roots). Fixed #3462. Signed-off-by: Dmitry S --- cmd/cosign/cli/options/certificate.go | 8 +- cmd/cosign/cli/verify.go | 5 ++ cmd/cosign/cli/verify/verify.go | 13 +++ doc/cosign_dockerfile_verify.md | 3 +- doc/cosign_manifest_verify.md | 3 +- doc/cosign_verify-attestation.md | 3 +- doc/cosign_verify-blob-attestation.md | 3 +- doc/cosign_verify-blob.md | 3 +- doc/cosign_verify.md | 7 +- pkg/cosign/verify.go | 2 +- test/e2e_tsa_certbundle.sh | 63 ++++--------- test/e2e_tsa_mtls.sh | 10 ++- test/gencert/main.go | 124 +++++++++++++++++++++----- 13 files changed, 170 insertions(+), 77 deletions(-) diff --git a/cmd/cosign/cli/options/certificate.go b/cmd/cosign/cli/options/certificate.go index 16d60605ed0..d2b7c0b38c5 100644 --- a/cmd/cosign/cli/options/certificate.go +++ b/cmd/cosign/cli/options/certificate.go @@ -33,6 +33,7 @@ type CertVerifyOptions struct { CertGithubWorkflowName string CertGithubWorkflowRepository string CertGithubWorkflowRef string + CAIntermediates string CARoots string CertChain string SCT string @@ -76,6 +77,10 @@ func (o *CertVerifyOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.CertGithubWorkflowRef, "certificate-github-workflow-ref", "", "contains the ref claim from the GitHub OIDC Identity token that contains the git ref that the workflow run was based upon.") // -- Cert extensions end -- + 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. 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.") @@ -85,9 +90,10 @@ func (o *CertVerifyOptions) AddFlags(cmd *cobra.Command) { "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.") + "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.SCT, "sct", "", "path to a detached Signed Certificate Timestamp, formatted as a RFC6962 AddChainResponse struct. "+ diff --git a/cmd/cosign/cli/verify.go b/cmd/cosign/cli/verify.go index 536eb6818a6..a2540923082 100644 --- a/cmd/cosign/cli/verify.go +++ b/cmd/cosign/cli/verify.go @@ -62,6 +62,10 @@ against the transparency log.`, # verify image with local certificate and certificate chain cosign verify --cert cosign.crt --cert-chain chain.crt + # verify image with local certificate and certificate bundles of CA roots + # and (optionally) CA intermediates + cosign verify --cert cosign.crt --ca-roots ca-roots.pem --ca-intermediates ca-intermediates.pem + # verify image using keyless verification with the given certificate # chain and identity parameters, without Fulcio roots (for BYO PKI): cosign verify --cert-chain chain.crt --certificate-oidc-issuer https://issuer.example.com --certificate-identity foo@example.com @@ -115,6 +119,7 @@ against the transparency log.`, CertGithubWorkflowName: o.CertVerify.CertGithubWorkflowName, CertGithubWorkflowRepository: o.CertVerify.CertGithubWorkflowRepository, CertGithubWorkflowRef: o.CertVerify.CertGithubWorkflowRef, + CAIntermediates: o.CertVerify.CAIntermediates, CARoots: o.CertVerify.CARoots, CertChain: o.CertVerify.CertChain, IgnoreSCT: o.CertVerify.IgnoreSCT, diff --git a/cmd/cosign/cli/verify/verify.go b/cmd/cosign/cli/verify/verify.go index 8b3f60e202b..4c90e9c882b 100644 --- a/cmd/cosign/cli/verify/verify.go +++ b/cmd/cosign/cli/verify/verify.go @@ -60,6 +60,7 @@ type VerifyCommand struct { CertGithubWorkflowName string CertGithubWorkflowRepository string CertGithubWorkflowRef string + CAIntermediates string CARoots string CertChain string CertOidcProvider string @@ -206,6 +207,18 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) { co.RootCerts.AddCert(cert) } } + if c.CAIntermediates != "" { + caIntermediates, err := loadCertChainFromFileOrURL(c.CAIntermediates) + if err != nil { + return err + } + if len(caIntermediates) > 0 { + co.IntermediateCerts = x509.NewCertPool() + for _, cert := range caIntermediates { + co.IntermediateCerts.AddCert(cert) + } + } + } } default: { diff --git a/doc/cosign_dockerfile_verify.md b/doc/cosign_dockerfile_verify.md index c9f5d3d4116..a68e9532e3f 100644 --- a/doc/cosign_dockerfile_verify.md +++ b/doc/cosign_dockerfile_verify.md @@ -55,9 +55,10 @@ cosign dockerfile verify [flags] --attachment string DEPRECATED, related image attachment to verify (sbom), default none --attachment-tag-prefix [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] optional custom prefix to use for attached image tags. Attachment images are tagged as: [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] --base-image-only only verify the base image (the last FROM image in the Dockerfile) + --ca-intermediates string path to a file of intermediate CA certificates in PEM format which will be needed when building the certificate chains for the signing certificate. Conflicts with --certificate-chain. --ca-roots string 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. --certificate string path to the public certificate. The certificate will be verified against the Fulcio roots if the --certificate-chain option is not passed. - --certificate-chain string 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. + --certificate-chain string 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. --certificate-github-workflow-name string contains the workflow claim from the GitHub OIDC Identity token that contains the name of the executed workflow. --certificate-github-workflow-ref string contains the ref claim from the GitHub OIDC Identity token that contains the git ref that the workflow run was based upon. --certificate-github-workflow-repository string contains the repository claim from the GitHub OIDC Identity token that contains the repository that the workflow run was based upon diff --git a/doc/cosign_manifest_verify.md b/doc/cosign_manifest_verify.md index 70556526124..6440225283f 100644 --- a/doc/cosign_manifest_verify.md +++ b/doc/cosign_manifest_verify.md @@ -49,9 +49,10 @@ cosign manifest verify [flags] -a, --annotations strings extra key=value pairs to sign --attachment string DEPRECATED, related image attachment to verify (sbom), default none --attachment-tag-prefix [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] optional custom prefix to use for attached image tags. Attachment images are tagged as: [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] + --ca-intermediates string path to a file of intermediate CA certificates in PEM format which will be needed when building the certificate chains for the signing certificate. Conflicts with --certificate-chain. --ca-roots string 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. --certificate string path to the public certificate. The certificate will be verified against the Fulcio roots if the --certificate-chain option is not passed. - --certificate-chain string 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. + --certificate-chain string 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. --certificate-github-workflow-name string contains the workflow claim from the GitHub OIDC Identity token that contains the name of the executed workflow. --certificate-github-workflow-ref string contains the ref claim from the GitHub OIDC Identity token that contains the git ref that the workflow run was based upon. --certificate-github-workflow-repository string contains the repository claim from the GitHub OIDC Identity token that contains the repository that the workflow run was based upon diff --git a/doc/cosign_verify-attestation.md b/doc/cosign_verify-attestation.md index 4891973c7e4..88bf142875f 100644 --- a/doc/cosign_verify-attestation.md +++ b/doc/cosign_verify-attestation.md @@ -59,9 +59,10 @@ cosign verify-attestation [flags] --allow-http-registry whether to allow using HTTP protocol while connecting to registries. Don't use this for anything but testing --allow-insecure-registry whether to allow insecure connections to registries (e.g., with expired or self-signed TLS certificates). Don't use this for anything but testing --attachment-tag-prefix [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] optional custom prefix to use for attached image tags. Attachment images are tagged as: [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] + --ca-intermediates string path to a file of intermediate CA certificates in PEM format which will be needed when building the certificate chains for the signing certificate. Conflicts with --certificate-chain. --ca-roots string 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. --certificate string path to the public certificate. The certificate will be verified against the Fulcio roots if the --certificate-chain option is not passed. - --certificate-chain string 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. + --certificate-chain string 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. --certificate-github-workflow-name string contains the workflow claim from the GitHub OIDC Identity token that contains the name of the executed workflow. --certificate-github-workflow-ref string contains the ref claim from the GitHub OIDC Identity token that contains the git ref that the workflow run was based upon. --certificate-github-workflow-repository string contains the repository claim from the GitHub OIDC Identity token that contains the repository that the workflow run was based upon diff --git a/doc/cosign_verify-blob-attestation.md b/doc/cosign_verify-blob-attestation.md index d8b038b91d8..6e6651eb05a 100644 --- a/doc/cosign_verify-blob-attestation.md +++ b/doc/cosign_verify-blob-attestation.md @@ -29,9 +29,10 @@ cosign verify-blob-attestation [flags] ``` --bundle string path to bundle FILE + --ca-intermediates string path to a file of intermediate CA certificates in PEM format which will be needed when building the certificate chains for the signing certificate. Conflicts with --certificate-chain. --ca-roots string 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. --certificate string path to the public certificate. The certificate will be verified against the Fulcio roots if the --certificate-chain option is not passed. - --certificate-chain string 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. + --certificate-chain string 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. --certificate-github-workflow-name string contains the workflow claim from the GitHub OIDC Identity token that contains the name of the executed workflow. --certificate-github-workflow-ref string contains the ref claim from the GitHub OIDC Identity token that contains the git ref that the workflow run was based upon. --certificate-github-workflow-repository string contains the repository claim from the GitHub OIDC Identity token that contains the repository that the workflow run was based upon diff --git a/doc/cosign_verify-blob.md b/doc/cosign_verify-blob.md index 630b44819e8..71bf5bd1b8f 100644 --- a/doc/cosign_verify-blob.md +++ b/doc/cosign_verify-blob.md @@ -59,9 +59,10 @@ cosign verify-blob [flags] ``` --bundle string path to bundle FILE + --ca-intermediates string path to a file of intermediate CA certificates in PEM format which will be needed when building the certificate chains for the signing certificate. Conflicts with --certificate-chain. --ca-roots string 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. --certificate string path to the public certificate. The certificate will be verified against the Fulcio roots if the --certificate-chain option is not passed. - --certificate-chain string 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. + --certificate-chain string 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. --certificate-github-workflow-name string contains the workflow claim from the GitHub OIDC Identity token that contains the name of the executed workflow. --certificate-github-workflow-ref string contains the ref claim from the GitHub OIDC Identity token that contains the git ref that the workflow run was based upon. --certificate-github-workflow-repository string contains the repository claim from the GitHub OIDC Identity token that contains the repository that the workflow run was based upon diff --git a/doc/cosign_verify.md b/doc/cosign_verify.md index d26e0a11a52..9f5d9ae5623 100644 --- a/doc/cosign_verify.md +++ b/doc/cosign_verify.md @@ -38,6 +38,10 @@ cosign verify [flags] # verify image with local certificate and certificate chain cosign verify --cert cosign.crt --cert-chain chain.crt + # verify image with local certificate and certificate bundles of CA roots + # and (optionally) CA intermediates + cosign verify --cert cosign.crt --ca-roots ca-roots.pem --ca-intermediates ca-intermediates.pem + # verify image using keyless verification with the given certificate # chain and identity parameters, without Fulcio roots (for BYO PKI): cosign verify --cert-chain chain.crt --certificate-oidc-issuer https://issuer.example.com --certificate-identity foo@example.com @@ -72,9 +76,10 @@ cosign verify [flags] -a, --annotations strings extra key=value pairs to sign --attachment string DEPRECATED, related image attachment to verify (sbom), default none --attachment-tag-prefix [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] optional custom prefix to use for attached image tags. Attachment images are tagged as: [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] + --ca-intermediates string path to a file of intermediate CA certificates in PEM format which will be needed when building the certificate chains for the signing certificate. Conflicts with --certificate-chain. --ca-roots string 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. --certificate string path to the public certificate. The certificate will be verified against the Fulcio roots if the --certificate-chain option is not passed. - --certificate-chain string 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. + --certificate-chain string 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. --certificate-github-workflow-name string contains the workflow claim from the GitHub OIDC Identity token that contains the name of the executed workflow. --certificate-github-workflow-ref string contains the ref claim from the GitHub OIDC Identity token that contains the git ref that the workflow run was based upon. --certificate-github-workflow-repository string contains the repository claim from the GitHub OIDC Identity token that contains the repository that the workflow run was based upon diff --git a/pkg/cosign/verify.go b/pkg/cosign/verify.go index 3a41ad59536..8f428ae8cda 100644 --- a/pkg/cosign/verify.go +++ b/pkg/cosign/verify.go @@ -434,7 +434,7 @@ func ValidateAndUnpackCertWithChain(cert *x509.Certificate, chain []*x509.Certif } // ValidateAndUnpackCertWithCertPools creates a Verifier from a certificate. Verifies that the certificate -// chains up to the provided root. CheckOpts should contain a pool of CA Roots and optionally the Intermediates +// chains up to the provided root. CheckOpts should contain a pool of CA Roots and optionally the Intermediates. // Optionally verifies the subject and issuer of the certificate. func ValidateAndUnpackCertWithCertPools(cert *x509.Certificate, co *CheckOpts) (signature.Verifier, error) { if co.RootCerts == nil { diff --git a/test/e2e_tsa_certbundle.sh b/test/e2e_tsa_certbundle.sh index dbc12c96387..cfe3d84b704 100755 --- a/test/e2e_tsa_certbundle.sh +++ b/test/e2e_tsa_certbundle.sh @@ -27,40 +27,11 @@ set -exuo pipefail 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 +export TIMESTAMP_SERVER_URL=https://freetsa.org/tsr TIMESTAMP_CHAIN_FILE="timestamp-chain" - -set +e +curl -s https://www.freetsa.org/files/cacert.pem > $TIMESTAMP_CHAIN_FILE +echo "TIMESTAMP_CHAIN_FILE: $(ls -l $TIMESTAMP_CHAIN_FILE)" 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)" # unlike e2e_tsa_mtls.sh, there is no option to pass an image as a command-line parameter. @@ -76,36 +47,40 @@ done echo "IMG01: $IMG01, IMG02: $IMG02, TIMESTAMP_SERVER_URL: $TIMESTAMP_SERVER_URL" -rm -f *.pem import-cosign.* key.pem - # use gencert to generate two CAs (for testing certificate bundle feature), # keys and certificates echo "generate CAs, keys and certificates with gencert" - passwd=$(uuidgen | head -c 32 | tr 'A-Z' 'a-z') rm -f *.pem import-cosign.* for i in 01 02; do - go run test/gencert/main.go && mv cacert.pem cacert${i}.pem && mv ca-key.pem ca-key${i}.pem && mv cert.pem cert${i}.pem && mv key.pem key${i}.pem - COSIGN_PASSWORD="$passwd" $COSIGN_CLI import-key-pair --key key${i}.pem --output-key-prefix import-cosign${i} + go run test/gencert/main.go -output-suffix "$i" -intermediate + COSIGN_PASSWORD="$passwd" $COSIGN_CLI import-key-pair --key ca-intermediate-key${i}.pem --output-key-prefix import-cosign${i} IMG="IMG${i}" + cat ca-intermediate${i}.pem ca-root${i}.pem > certchain${i}.pem 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${i}.key --certificate-chain cacert${i}.pem --cert cert${i}.pem "${!IMG}" + --upload=true --tlog-upload=false --key import-cosign${i}.key --certificate-chain certchain${i}.pem --cert cert${i}.pem "${!IMG}" # key is now longer needed rm -f key${i}.pem import-cosign${i}.* done + # create a certificate bundle - concatenate both generated CA certificates -cat cacert01.pem cacert02.pem > ca-roots.pem +ls -l *.pem +cat ca-root01.pem ca-root02.pem > ca-roots.pem +cat ca-intermediate01.pem ca-intermediate02.pem > ca-intermediates.pem echo "cosign verify:" for i in 01 02; do IMG="IMG${i}" + # first try with --certificate-chain parameter + $COSIGN_CLI verify --insecure-ignore-tlog --insecure-ignore-sct --check-claims=true \ + --certificate-identity-regexp 'xyz@nosuchprovider.com' --certificate-oidc-issuer-regexp '.*' \ + --certificate-chain certchain${i}.pem --timestamp-certificate-chain $TIMESTAMP_CHAIN_FILE "${!IMG}" + + # then do the same but now with --ca-roots and --ca-intermediates parameters $COSIGN_CLI verify --insecure-ignore-tlog --insecure-ignore-sct --check-claims=true \ --certificate-identity-regexp 'xyz@nosuchprovider.com' --certificate-oidc-issuer-regexp '.*' \ - --ca-roots ca-roots.pem --timestamp-certificate-chain $TIMESTAMP_CHAIN_FILE "${!IMG}" + --ca-roots ca-roots.pem --ca-intermediates ca-intermediates.pem --timestamp-certificate-chain $TIMESTAMP_CHAIN_FILE "${!IMG}" done # cleanup -rm -fr ca-key*.pem ca-roots.pem cacert*.pem cert*.pem timestamp-chain /tmp/timestamp-authority -pkill -f 'timestamp-server' +rm -fr *.pem timestamp-chain diff --git a/test/e2e_tsa_mtls.sh b/test/e2e_tsa_mtls.sh index adb0d062b77..cc06eb04e5b 100755 --- a/test/e2e_tsa_mtls.sh +++ b/test/e2e_tsa_mtls.sh @@ -83,7 +83,7 @@ rm -f *.pem import-cosign.* && go run test/gencert/main.go && COSIGN_PASSWORD="$ 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 + --upload=true --tlog-upload=false --key import-cosign.key --certificate-chain ca-root.pem --cert cert.pem $IMG # key is now longer needed rm -f key.pem import-cosign.* @@ -91,8 +91,12 @@ rm -f key.pem import-cosign.* echo "cosign verify:" $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 --timestamp-certificate-chain $TIMESTAMP_CHAIN_FILE $IMG + --certificate-chain ca-root.pem --timestamp-certificate-chain $TIMESTAMP_CHAIN_FILE $IMG +# alternative invocation of 'cosign verify' using '--ca-roots' instead of '--certificate-chain' +$COSIGN_CLI verify --insecure-ignore-tlog --insecure-ignore-sct --check-claims=true \ + --certificate-identity-regexp 'xyz@nosuchprovider.com' --certificate-oidc-issuer-regexp '.*' \ + --ca-roots ca-root.pem --timestamp-certificate-chain $TIMESTAMP_CHAIN_FILE $IMG # cleanup -rm -fr ca-key.pem cacert.pem cert.pem timestamp-chain /tmp/timestamp-authority +rm -fr ca-key.pem ca-root.pem cert.pem timestamp-chain /tmp/timestamp-authority pkill -f 'timestamp-server' diff --git a/test/gencert/main.go b/test/gencert/main.go index 6ba336f13b0..76730241e35 100644 --- a/test/gencert/main.go +++ b/test/gencert/main.go @@ -15,6 +15,9 @@ // code is based on the snippet https://gist.github.com/shaneutt/5e1995295cff6721c89a71d13a71c251 // with a permissive (Public Domain) License. +// TODO(dmitris) - refactor to move the code generating certificates and writing to files +// to a separate function. + package main import ( @@ -23,6 +26,7 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/pem" + "flag" "log" "math/big" "net" @@ -31,16 +35,21 @@ import ( ) func main() { + var genIntermediate = flag.Bool("intermediate", false, "generate intermediate CA") + var outputSuffix = flag.String("output-suffix", "", "suffix to append to generated files before the extension") + flag.Parse() + // set up our CA certificate ca := &x509.Certificate{ SerialNumber: big.NewInt(2019), Subject: pkix.Name{ - Organization: []string{"CA Company, INC."}, - Country: []string{"US"}, - Province: []string{""}, - Locality: []string{"San Francisco"}, - StreetAddress: []string{"Golden Gate Bridge"}, - PostalCode: []string{"94016"}, + Organization: []string{"CA Company, INC."}, + OrganizationalUnit: []string{"CA Root Team"}, + Country: []string{"US"}, + Province: []string{""}, + Locality: []string{"San Francisco"}, + StreetAddress: []string{"Golden Gate Bridge"}, + PostalCode: []string{"94016"}, }, NotBefore: time.Now(), NotAfter: time.Now().AddDate(10, 0, 0), @@ -64,19 +73,20 @@ func main() { } // pem encode - caCertFile, err := os.OpenFile("cacert.pem", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) + fname := "ca-root" + *outputSuffix + ".pem" + caCertFile, err := os.OpenFile(fname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) if err != nil { - log.Fatalf("unable to write to cacert.pem: %v", err) + log.Fatalf("unable to write to %s: %v", fname, err) } err = pem.Encode(caCertFile, &pem.Block{ Type: "CERTIFICATE", Bytes: caBytes, }) if err != nil { - log.Fatalf("unable to write to cacert.pem: %v", err) + log.Fatalf("unable to write to %s: %v", fname, err) } - - caPrivKeyFile, err := os.OpenFile("ca-key.pem", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0400) + fname = "ca-key" + *outputSuffix + ".pem" + caPrivKeyFile, err := os.OpenFile(fname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0400) if err != nil { log.Fatal(err) } @@ -86,19 +96,82 @@ func main() { Bytes: x509.MarshalPKCS1PrivateKey(caPrivKey), }) if err != nil { - log.Fatalf("unable to create to ca-key.pem: %v", err) //nolint:gocritic + log.Fatalf("unable to create to %s: %v", fname, err) //nolint:gocritic } + // generate intermediate CA if requested + var caIntermediate *x509.Certificate + var caIntermediatePrivKey *rsa.PrivateKey + if *genIntermediate { + caIntermediate = &x509.Certificate{ + SerialNumber: big.NewInt(2019), + Subject: pkix.Name{ + Organization: []string{"CA Company, INC."}, + OrganizationalUnit: []string{"CA Intermediate Team"}, + Country: []string{"US"}, + Province: []string{""}, + Locality: []string{"San Francisco"}, + StreetAddress: []string{"Golden Gate Bridge"}, + PostalCode: []string{"94016"}, + }, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(10, 0, 0), + IsCA: true, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning /*, x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth */}, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + BasicConstraintsValid: true, + EmailAddresses: []string{"ca@example.com"}, + } + // create our private and public key + caIntermediatePrivKey, err = rsa.GenerateKey(rand.Reader, 4096) + if err != nil { + log.Fatal(err) + } + + // create the Intermediate CA + caIntermediateBytes, err := x509.CreateCertificate(rand.Reader, caIntermediate, ca, &caIntermediatePrivKey.PublicKey, caPrivKey) + if err != nil { + log.Fatal(err) + } + + // pem encode + fname = "ca-intermediate" + *outputSuffix + ".pem" + caIntermediateCertFile, err := os.OpenFile(fname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + log.Fatalf("unable to write to %s: %v", fname, err) + } + err = pem.Encode(caIntermediateCertFile, &pem.Block{ + Type: "CERTIFICATE", + Bytes: caIntermediateBytes, + }) + if err != nil { + log.Fatalf("unable to write to %s: %v", fname, err) + } + fname = "ca-intermediate-key" + *outputSuffix + ".pem" + caIntermediatePrivKeyFile, err := os.OpenFile(fname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0400) + if err != nil { + log.Fatal(err) + } + defer caIntermediatePrivKeyFile.Close() + err = pem.Encode(caIntermediatePrivKeyFile, &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(caIntermediatePrivKey), + }) + if err != nil { + log.Fatalf("unable to create to %s: %v", fname, err) //nolint:gocritic + } + } // set up our server certificate cert := &x509.Certificate{ SerialNumber: big.NewInt(2019), Subject: pkix.Name{ - Organization: []string{"Company, INC."}, - Country: []string{"US"}, - Province: []string{""}, - Locality: []string{"San Francisco"}, - StreetAddress: []string{"Golden Gate Bridge"}, - PostalCode: []string{"94016"}, + Organization: []string{"End User"}, + OrganizationalUnit: []string{"End Node Team"}, + Country: []string{"US"}, + Province: []string{""}, + Locality: []string{"San Francisco"}, + StreetAddress: []string{"Golden Gate Bridge"}, + PostalCode: []string{"94016"}, }, IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback}, NotBefore: time.Now(), @@ -115,12 +188,18 @@ func main() { log.Fatal(err) } - certBytes, err := x509.CreateCertificate(rand.Reader, cert, ca, &certPrivKey.PublicKey, caPrivKey) + var certBytes []byte + if !*genIntermediate { + certBytes, err = x509.CreateCertificate(rand.Reader, cert, ca, &certPrivKey.PublicKey, caPrivKey) + } else { + certBytes, err = x509.CreateCertificate(rand.Reader, cert, caIntermediate, &caIntermediatePrivKey.PublicKey, caIntermediatePrivKey) + } if err != nil { log.Fatal(err) } - certFile, err := os.OpenFile("cert.pem", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) + fname = "cert" + *outputSuffix + ".pem" + certFile, err := os.OpenFile(fname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) if err != nil { log.Fatal(err) } @@ -133,7 +212,8 @@ func main() { log.Fatalf("failed to encode cert: %v", err) } - keyFile, err := os.OpenFile("key.pem", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0400) + fname = "key" + *outputSuffix + ".pem" + keyFile, err := os.OpenFile(fname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0400) if err != nil { log.Fatal(err) } @@ -143,6 +223,6 @@ func main() { Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey), }) if err != nil { - log.Fatalf("failed to encode private key: %v", err) + log.Fatalf("failed to encode private key to %s: %v", fname, err) } }