...

Source file src/github.com/sigstore/timestamp-authority/cmd/fetch-tsa-certs/fetch_tsa_certs.go

Documentation: github.com/sigstore/timestamp-authority/cmd/fetch-tsa-certs

     1  // Copyright 2022 The Sigstore Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  
    16  package main
    17  
    18  import (
    19  	"context"
    20  	"crypto"
    21  	"crypto/rand"
    22  	"crypto/x509"
    23  	"crypto/x509/pkix"
    24  	"encoding/asn1"
    25  	"errors"
    26  	"flag"
    27  	"fmt"
    28  	"log"
    29  	"os"
    30  	"path/filepath"
    31  	"time"
    32  
    33  	privateca "cloud.google.com/go/security/privateca/apiv1"
    34  	"cloud.google.com/go/security/privateca/apiv1/privatecapb"
    35  	"github.com/google/tink/go/keyset"
    36  	"github.com/sigstore/sigstore/pkg/cryptoutils"
    37  	"github.com/sigstore/timestamp-authority/pkg/signer"
    38  	tsx509 "github.com/sigstore/timestamp-authority/pkg/x509"
    39  	"google.golang.org/protobuf/types/known/durationpb"
    40  
    41  	// Register the provider-specific plugins
    42  	"github.com/sigstore/sigstore/pkg/signature/kms"
    43  	_ "github.com/sigstore/sigstore/pkg/signature/kms/aws"
    44  	_ "github.com/sigstore/sigstore/pkg/signature/kms/azure"
    45  	_ "github.com/sigstore/sigstore/pkg/signature/kms/gcp"
    46  	_ "github.com/sigstore/sigstore/pkg/signature/kms/hashivault"
    47  )
    48  
    49  /*
    50  To run:
    51  go run cmd/fetch-tsa-certs/fetch_tsa_certs.go \
    52    --intermediate-kms-resource="gcpkms://projects/<project>/locations/<region>/keyRings/<key-ring>/cryptoKeys/<key>/versions/1" \
    53    --leaf-kms-resource="gcpkms://projects/<project>/locations/<region>/keyRings/<leaf-key-ring>/cryptoKeys/<key>/versions/1" \
    54    --gcp-ca-parent="projects/<project>/locations/<region>/caPools/<ca-pool>" \
    55    --output="chain.crt.pem"
    56  
    57  go run cmd/fetch-tsa-certs/fetch_tsa_certs.go \
    58    --intermediate-kms-resource="gcpkms://projects/<project>/locations/<region>/keyRings/<key-ring>/cryptoKeys/<key>/versions/1" \
    59    --tink-kms-resource="gcp-kms://projects/<project>/locations/<region>/keyRings/<key-ring>/cryptoKeys/<key>" \
    60    --tink-keyset-path="enc-keyset.cfg" \
    61    --gcp-ca-parent="projects/<project>/locations/<region>/caPools/<ca-pool>" \
    62    --output="chain.crt.pem"
    63  
    64  You must have the permissions to read, sign with, and decrypt with the KMS keys, and create a certificate in the CA pool.
    65  
    66  You can create a GCP KMS encrypted Tink keyset with tinkey (changing the key template as needed):
    67  tinkey create-keyset --key-template ECDSA_P384 --out enc-keyset.cfg --master-key-uri gcp-kms://projects/<project>/locations/<region>/keyRings/<key-ring>/cryptoKeys/<key>
    68  */
    69  
    70  var (
    71  	// likely the root CA
    72  	gcpCaParent = flag.String("gcp-ca-parent", "", "Resource path to GCP CA Service CA")
    73  	// key only used for fetching intermediate certificate from root and signing leaf certificate
    74  	intermediateKMSKey = flag.String("intermediate-kms-resource", "", "Resource path to the asymmetric signing KMS key for the intermediate CA, starting with gcpkms://, awskms://, azurekms:// or hashivault://")
    75  	// leafKMSKey or Tink flags required
    76  	leafKMSKey     = flag.String("leaf-kms-resource", "", "Resource path to the asymmetric signing KMS key for the leaf, starting with gcpkms://, awskms://, azurekms:// or hashivault://")
    77  	tinkKeysetPath = flag.String("tink-keyset-path", "", "Path to Tink keyset")
    78  	tinkKmsKey     = flag.String("tink-kms-resource", "", "Resource path to symmetric encryption KMS key to decrypt Tink keyset, starting with gcp-kms:// or aws-kms://")
    79  	outputPath     = flag.String("output", "", "Path to the output file")
    80  )
    81  
    82  func fetchCertificateChain(ctx context.Context, parent, intermediateKMSKey, leafKMSKey, tinkKeysetPath, tinkKmsKey string,
    83  	client *privateca.CertificateAuthorityClient) ([]*x509.Certificate, error) {
    84  	intermediateKMSSigner, err := kms.Get(ctx, intermediateKMSKey, crypto.SHA256)
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  	intermediateSigner, _, err := intermediateKMSSigner.CryptoSigner(ctx, func(err error) {})
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  
    93  	pemPubKey, err := cryptoutils.MarshalPublicKeyToPEM(intermediateSigner.Public())
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  
    98  	// OID for Extended Key Usage Timestamping
    99  	timestampExt, err := asn1.Marshal([]asn1.ObjectIdentifier{tsx509.EKUTimestampingOID})
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  	additionalExtensions := []*privatecapb.X509Extension{{
   104  		ObjectId: &privatecapb.ObjectId{ObjectIdPath: []int32{2, 5, 29, 37}},
   105  		Critical: true,
   106  		Value:    timestampExt,
   107  	}}
   108  
   109  	isCa := true
   110  	// default value of 0 for int32
   111  	var maxIssuerPathLength int32
   112  
   113  	csr := &privatecapb.CreateCertificateRequest{
   114  		Parent: parent,
   115  		Certificate: &privatecapb.Certificate{
   116  			// Default to a very large lifetime - CA Service will truncate the
   117  			// lifetime to be no longer than the root's lifetime.
   118  			// 20 years (24 hours * 365 days * 20)
   119  			Lifetime: durationpb.New(time.Hour * 24 * 365 * 20),
   120  			CertificateConfig: &privatecapb.Certificate_Config{
   121  				Config: &privatecapb.CertificateConfig{
   122  					PublicKey: &privatecapb.PublicKey{
   123  						Format: privatecapb.PublicKey_PEM,
   124  						Key:    pemPubKey,
   125  					},
   126  					X509Config: &privatecapb.X509Parameters{
   127  						KeyUsage: &privatecapb.KeyUsage{
   128  							BaseKeyUsage: &privatecapb.KeyUsage_KeyUsageOptions{
   129  								CertSign: true,
   130  								CrlSign:  true,
   131  							},
   132  						},
   133  						CaOptions: &privatecapb.X509Parameters_CaOptions{
   134  							IsCa:                &isCa,
   135  							MaxIssuerPathLength: &maxIssuerPathLength,
   136  						},
   137  						AdditionalExtensions: additionalExtensions,
   138  					},
   139  					SubjectConfig: &privatecapb.CertificateConfig_SubjectConfig{
   140  						Subject: &privatecapb.Subject{
   141  							CommonName:   "sigstore-tsa-intermediate",
   142  							Organization: "sigstore.dev",
   143  						},
   144  					},
   145  				},
   146  			},
   147  		},
   148  	}
   149  
   150  	resp, err := client.CreateCertificate(ctx, csr)
   151  	if err != nil {
   152  		return nil, err
   153  	}
   154  
   155  	var pemCerts []string
   156  	pemCerts = append(pemCerts, resp.PemCertificate)
   157  	pemCerts = append(pemCerts, resp.PemCertificateChain...)
   158  
   159  	var parsedCerts []*x509.Certificate
   160  	for _, c := range pemCerts {
   161  		certs, err := cryptoutils.UnmarshalCertificatesFromPEM([]byte(c))
   162  		if err != nil {
   163  			return nil, err
   164  		}
   165  		if len(certs) != 1 {
   166  			return nil, errors.New("unexpected number of certificates returned")
   167  		}
   168  		parsedCerts = append(parsedCerts, certs[0])
   169  	}
   170  	intermediate := parsedCerts[0]
   171  
   172  	// generate leaf certificate
   173  	var leafKMSSigner crypto.Signer
   174  	if len(leafKMSKey) > 0 {
   175  		kmsSigner, err := kms.Get(ctx, leafKMSKey, crypto.SHA256)
   176  		if err != nil {
   177  			return nil, err
   178  		}
   179  		leafKMSSigner, _, err = kmsSigner.CryptoSigner(ctx, func(err error) {})
   180  		if err != nil {
   181  			return nil, err
   182  		}
   183  	} else {
   184  		primaryKey, err := signer.GetPrimaryKey(ctx, tinkKmsKey, "")
   185  		if err != nil {
   186  			return nil, err
   187  		}
   188  		f, err := os.Open(filepath.Clean(tinkKeysetPath))
   189  		if err != nil {
   190  			return nil, err
   191  		}
   192  		defer f.Close()
   193  
   194  		kh, err := keyset.Read(keyset.NewJSONReader(f), primaryKey)
   195  		if err != nil {
   196  			return nil, err
   197  		}
   198  		leafKMSSigner, err = signer.KeyHandleToSigner(kh)
   199  		if err != nil {
   200  			return nil, err
   201  		}
   202  	}
   203  
   204  	leafPubKey := leafKMSSigner.Public()
   205  
   206  	sn, err := cryptoutils.GenerateSerialNumber()
   207  	if err != nil {
   208  		return nil, fmt.Errorf("generating serial number: %w", err)
   209  	}
   210  
   211  	skid, err := cryptoutils.SKID(leafPubKey)
   212  	if err != nil {
   213  		return nil, err
   214  	}
   215  
   216  	cert := &x509.Certificate{
   217  		SerialNumber: sn,
   218  		Subject: pkix.Name{
   219  			CommonName:   "sigstore-tsa",
   220  			Organization: []string{"sigstore.dev"},
   221  		},
   222  		SubjectKeyId: skid,
   223  		NotBefore:    intermediate.NotBefore,
   224  		NotAfter:     intermediate.NotAfter,
   225  		IsCA:         false,
   226  		KeyUsage:     x509.KeyUsageDigitalSignature,
   227  		// set EKU to x509.ExtKeyUsageTimeStamping but with a critical bit
   228  		ExtraExtensions: []pkix.Extension{
   229  			{
   230  				Id:       asn1.ObjectIdentifier{2, 5, 29, 37},
   231  				Critical: true,
   232  				Value:    timestampExt,
   233  			},
   234  		},
   235  	}
   236  	certDER, err := x509.CreateCertificate(rand.Reader, cert, intermediate, leafPubKey, intermediateSigner)
   237  	if err != nil {
   238  		return nil, fmt.Errorf("creating tsa certificate: %w", err)
   239  	}
   240  	leafCert, err := x509.ParseCertificate(certDER)
   241  	if err != nil {
   242  		return nil, fmt.Errorf("parsing leaf certificate: %w", err)
   243  	}
   244  	parsedCerts = append([]*x509.Certificate{leafCert}, parsedCerts...)
   245  
   246  	return parsedCerts, nil
   247  }
   248  
   249  func main() {
   250  	flag.Parse()
   251  
   252  	if *gcpCaParent == "" {
   253  		log.Fatal("gcp-ca-parent must be set")
   254  	}
   255  	if *intermediateKMSKey == "" {
   256  		log.Fatal("intermediate-kms-resource must be set")
   257  	}
   258  	if *leafKMSKey == "" && *tinkKeysetPath == "" {
   259  		log.Fatal("either leaf-kms-resource or tink-keyset-path must be set")
   260  	}
   261  	if *tinkKeysetPath != "" && *tinkKmsKey == "" {
   262  		log.Fatal("tink-keyset-path must be set with tink-kms-resource must be set")
   263  	}
   264  	if *outputPath == "" {
   265  		log.Fatal("output must be set")
   266  	}
   267  
   268  	client, err := privateca.NewCertificateAuthorityClient(context.Background())
   269  	if err != nil {
   270  		log.Fatal(err)
   271  	}
   272  	parsedCerts, err := fetchCertificateChain(context.Background(), *gcpCaParent, *intermediateKMSKey, *leafKMSKey, *tinkKeysetPath, *tinkKmsKey, client)
   273  	if err != nil {
   274  		log.Fatal(err)
   275  	}
   276  	pemCerts, err := cryptoutils.MarshalCertificatesToPEM(parsedCerts)
   277  	if err != nil {
   278  		log.Fatal(err)
   279  	}
   280  
   281  	err = os.WriteFile(*outputPath, pemCerts, 0600)
   282  	if err != nil {
   283  		log.Fatal(err)
   284  	}
   285  }
   286  

View as plain text