diff --git a/README.md b/README.md index edcddba02..1e47cb131 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ The public Fulcio root CA is currently running on [GCP CA Service](https://cloud You can also run Fulcio with your own CA on CA Service by passing in a parent and specifying Google as the CA: ``` -go run main.go serve --ca googleca --gcp_private_ca_parent=projects/myproject/locations/us-central1/caPools/mypool --gcp_private_ca_version=v1 +go run main.go serve --ca googleca --gcp_private_ca_parent=projects/myproject/locations/us-central1/caPools/mypool ``` ### PKCS11CA diff --git a/cmd/app/serve.go b/cmd/app/serve.go index adac2b329..1c7f8af48 100644 --- a/cmd/app/serve.go +++ b/cmd/app/serve.go @@ -31,7 +31,6 @@ import ( "github.com/sigstore/fulcio/pkg/ca/ephemeralca" "github.com/sigstore/fulcio/pkg/ca/fileca" googlecav1 "github.com/sigstore/fulcio/pkg/ca/googleca/v1" - googlecav1beta1 "github.com/sigstore/fulcio/pkg/ca/googleca/v1beta1" "github.com/sigstore/fulcio/pkg/ca/x509ca" "github.com/sigstore/fulcio/pkg/config" "github.com/sigstore/fulcio/pkg/log" @@ -56,7 +55,6 @@ func newServeCmd() *cobra.Command { cmd.Flags().String("ca", "", "googleca | pkcs11ca | fileca | ephemeralca (for testing)") cmd.Flags().String("aws-hsm-root-ca-path", "", "Path to root CA on disk (only used with AWS HSM)") cmd.Flags().String("gcp_private_ca_parent", "", "private ca parent: /projects//locations// (only used with --ca googleca)") - cmd.Flags().String("gcp_private_ca_version", "v1", "private ca version: [v1|v1beta1] (only used with --ca googleca)") cmd.Flags().String("hsm-caroot-id", "", "HSM ID for Root CA (only used with --ca pkcs11ca)") cmd.Flags().String("ct-log-url", "http://localhost:6962/test", "host and path (with log prefix at the end) to the ct log") cmd.Flags().String("config-path", "/etc/fulcio-config/config.json", "path to fulcio config json") @@ -98,6 +96,10 @@ func runServeCmd(cmd *cobra.Command, args []string) { if !viper.IsSet("gcp_private_ca_parent") { log.Logger.Fatal("gcp_private_ca_parent must be set when using googleca") } + if viper.IsSet("gcp_private_ca_version") { + // There's a MarkDeprecated function in cobra/pflags, but it doesn't use log.Logger + log.Logger.Warn("gcp_private_ca_version is deprecated and will soon be removed; please remove it") + } case "fileca": if !viper.IsSet("fileca-cert") { @@ -130,15 +132,7 @@ func runServeCmd(cmd *cobra.Command, args []string) { var baseca certauth.CertificateAuthority switch viper.GetString("ca") { case "googleca": - version := viper.GetString("gcp_private_ca_version") - switch version { - case "v1": - baseca, err = googlecav1.NewCertAuthorityService(cmd.Context(), viper.GetString("gcp_private_ca_parent")) - case "v1beta1": - baseca, err = googlecav1beta1.NewCertAuthorityService(cmd.Context(), viper.GetString("gcp_private_ca_parent")) - default: - err = fmt.Errorf("invalid value for gcp_private_ca_version: %v", version) - } + baseca, err = googlecav1.NewCertAuthorityService(cmd.Context(), viper.GetString("gcp_private_ca_parent")) case "pkcs11ca": params := x509ca.Params{ ConfigPath: viper.GetString("pkcs11-config-path"), diff --git a/config/fulcio-config.yaml b/config/fulcio-config.yaml index 257a0120c..1a02537b2 100644 --- a/config/fulcio-config.yaml +++ b/config/fulcio-config.yaml @@ -59,7 +59,6 @@ data: host: 0.0.0.0 port: 5555 ca: googleca - gcp_private_ca_version: v1 ct-log-url: http://ct-log/test log_type: prod kind: ConfigMap diff --git a/pkg/ca/googleca/v1beta1/googleca.go b/pkg/ca/googleca/v1beta1/googleca.go deleted file mode 100644 index 3d131de82..000000000 --- a/pkg/ca/googleca/v1beta1/googleca.go +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright 2021 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 v1beta1 - -import ( - "context" - "crypto/ecdsa" - "crypto/rsa" - "crypto/x509" - "encoding/asn1" - "encoding/pem" - "fmt" - "strings" - "sync" - "time" - - privateca "cloud.google.com/go/security/privateca/apiv1beta1" - "github.com/sigstore/fulcio/pkg/ca" - "github.com/sigstore/fulcio/pkg/ca/x509ca" - "github.com/sigstore/fulcio/pkg/challenges" - "github.com/sigstore/fulcio/pkg/log" - "github.com/sigstore/sigstore/pkg/cryptoutils" - "google.golang.org/api/iterator" - privatecapb "google.golang.org/genproto/googleapis/cloud/security/privateca/v1beta1" - "google.golang.org/protobuf/types/known/durationpb" -) - -type CertAuthorityService struct { - parent string - client *privateca.CertificateAuthorityClient - - // protected by once - cachedRoots []byte - cachedRootsOnce sync.Once -} - -func NewCertAuthorityService(ctx context.Context, parent string) (ca.CertificateAuthority, error) { - client, err := privateca.NewCertificateAuthorityClient(ctx) - if err != nil { - return nil, err - } - return &CertAuthorityService{ - parent: parent, - client: client, - }, nil -} - -// getPubKeyType Returns the PublicKey type required by gcp privateca (to handle both PEM_RSA_KEY / PEM_EC_KEY) -// https://pkg.go.dev/google.golang.org/genproto/googleapis/cloud/security/privateca/v1beta1#PublicKey_KeyType -func getPubKeyType(pemBytes []byte) (interface{}, error) { - block, _ := pem.Decode(pemBytes) - pub, err := x509.ParsePKIXPublicKey(block.Bytes) - if err != nil { - return nil, fmt.Errorf("failed to parse public key: %w", err) - } - switch pub := pub.(type) { - case *rsa.PublicKey: - return privatecapb.PublicKey_KeyType(1), nil - case *ecdsa.PublicKey: - return privatecapb.PublicKey_KeyType(2), nil - default: - return nil, fmt.Errorf("unknown public key type: %v", pub) - } -} - -func convertID(id asn1.ObjectIdentifier) []int32 { - nid := make([]int32, 0, len(id)) - for _, digit := range id { - nid = append(nid, int32(digit)) - } - return nid -} - -func Req(parent string, pemBytes []byte, cert *x509.Certificate) (*privatecapb.CreateCertificateRequest, error) { - // TODO, use the right fields :) - pubkeyType, err := getPubKeyType(pemBytes) - if err != nil { - return nil, err - } - - // Translate the x509 certificate's subject to Google proto. - subject := &privatecapb.CertificateConfig_SubjectConfig{ - Subject: &privatecapb.Subject{ - Organization: "sigstore", - }, - SubjectAltName: &privatecapb.SubjectAltNames{ - EmailAddresses: cert.EmailAddresses, - }, - } - for _, uri := range cert.URIs { - subject.SubjectAltName.Uris = append(subject.SubjectAltName.Uris, uri.String()) - } - - extensions := make([]*privatecapb.X509Extension, 0, len(cert.ExtraExtensions)) - for _, ext := range cert.ExtraExtensions { - extensions = append(extensions, &privatecapb.X509Extension{ - ObjectId: &privatecapb.ObjectId{ - ObjectIdPath: convertID(ext.Id), - }, - Value: ext.Value, - }) - } - - return &privatecapb.CreateCertificateRequest{ - Parent: parent, - Certificate: &privatecapb.Certificate{ - Lifetime: durationpb.New(time.Until(cert.NotAfter)), - CertificateConfig: &privatecapb.Certificate_Config{ - Config: &privatecapb.CertificateConfig{ - PublicKey: &privatecapb.PublicKey{ - Type: pubkeyType.(privatecapb.PublicKey_KeyType), - Key: pemBytes, - }, - ReusableConfig: &privatecapb.ReusableConfigWrapper{ - ConfigValues: &privatecapb.ReusableConfigWrapper_ReusableConfigValues{ - ReusableConfigValues: &privatecapb.ReusableConfigValues{ - KeyUsage: &privatecapb.KeyUsage{ - BaseKeyUsage: &privatecapb.KeyUsage_KeyUsageOptions{ - DigitalSignature: true, - }, - ExtendedKeyUsage: &privatecapb.KeyUsage_ExtendedKeyUsageOptions{ - CodeSigning: true, - }, - }, - AdditionalExtensions: extensions, - }, - }, - }, - SubjectConfig: subject, - }, - }, - }, - }, nil -} - -func (c *CertAuthorityService) Root(ctx context.Context) ([]byte, error) { - c.cachedRootsOnce.Do(func() { - var pems string - cas := c.client.ListCertificateAuthorities(ctx, &privatecapb.ListCertificateAuthoritiesRequest{ - Parent: c.parent, - }) - for { - c, done := cas.Next() - if done == iterator.Done { - break - } - if done != nil { - break - } - pems += strings.Join(c.PemCaCertificates, "") - } - c.cachedRoots = []byte(pems) - }) - if len(c.cachedRoots) == 0 { - return c.cachedRoots, fmt.Errorf("error fetching root certificates") - } - return c.cachedRoots, nil -} - -func (c *CertAuthorityService) CreateCertificate(ctx context.Context, subj *challenges.ChallengeResult) (*ca.CodeSigningCertificate, error) { - logger := log.ContextLogger(ctx) - - cert, err := x509ca.MakeX509(subj) - if err != nil { - return nil, ca.ValidationError(err) - } - - pubKeyBytes, err := cryptoutils.MarshalPublicKeyToPEM(subj.PublicKey) - if err != nil { - return nil, ca.ValidationError(err) - } - - req, err := Req(c.parent, pubKeyBytes, cert) - if err != nil { - return nil, ca.ValidationError(err) - } - logger.Infof("requesting cert from %s for %v", c.parent, subj.Value) - - resp, err := c.client.CreateCertificate(ctx, req) - if err != nil { - return nil, err - } - - return ca.CreateCSCFromPEM(subj, resp.PemCertificate, resp.PemCertificateChain) -} diff --git a/pkg/ca/googleca/v1beta1/googleca_test.go b/pkg/ca/googleca/v1beta1/googleca_test.go deleted file mode 100644 index 326b38146..000000000 --- a/pkg/ca/googleca/v1beta1/googleca_test.go +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2021 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 v1beta1 - -import ( - "crypto" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "crypto/rsa" - "crypto/sha256" - "testing" - - "github.com/sigstore/fulcio/pkg/challenges" -) - -func failErr(t *testing.T, err error) { - if err != nil { - t.Fatal(err) - } -} - -func TestCheckSignatureECDSA(t *testing.T) { - - priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - failErr(t, err) - - email := "test@gmail.com" - if err := challenges.CheckSignature(&priv.PublicKey, []byte("foo"), email); err == nil { - t.Fatal("check should have failed") - } - - h := sha256.Sum256([]byte(email)) - signature, err := priv.Sign(rand.Reader, h[:], crypto.SHA256) - failErr(t, err) - - if err := challenges.CheckSignature(&priv.PublicKey, signature, email); err != nil { - t.Fatal(err) - } - - // Try a bad email but "good" signature - if err := challenges.CheckSignature(&priv.PublicKey, signature, "bad@email.com"); err == nil { - t.Fatal("check should have failed") - } -} - -func TestCheckSignatureRSA(t *testing.T) { - priv, err := rsa.GenerateKey(rand.Reader, 2048) - failErr(t, err) - - email := "test@gmail.com" - if err := challenges.CheckSignature(&priv.PublicKey, []byte("foo"), email); err == nil { - t.Fatal("check should have failed") - } - - h := sha256.Sum256([]byte(email)) - signature, err := priv.Sign(rand.Reader, h[:], crypto.SHA256) - failErr(t, err) - - if err := challenges.CheckSignature(&priv.PublicKey, signature, email); err != nil { - t.Fatal(err) - } - - // Try a bad email but "good" signature - if err := challenges.CheckSignature(&priv.PublicKey, signature, "bad@email.com"); err == nil { - t.Fatal("check should have failed") - } -}