From 117abc4973327d8a8520ec38f34a3e419a84bdf6 Mon Sep 17 00:00:00 2001 From: Riccardo Schirone Date: Thu, 30 Jan 2025 11:49:49 +0100 Subject: [PATCH 1/3] Allow configurable client signing algorithms Co-authored-by: Alex Cameron Co-authored-by: Riccardo Schirone Signed-off-by: Alex Cameron Signed-off-by: Riccardo Schirone --- cmd/app/grpc.go | 6 +- cmd/app/http_test.go | 14 +- cmd/app/serve.go | 43 +++- cmd/app/serve_test.go | 8 +- go.mod | 5 +- go.sum | 6 +- pkg/server/grpc_server.go | 60 +++++- pkg/server/grpc_server_test.go | 362 +++++++++++++++++++++++++++++++-- 8 files changed, 464 insertions(+), 40 deletions(-) diff --git a/cmd/app/grpc.go b/cmd/app/grpc.go index c2f5358f6..4ec902fe1 100644 --- a/cmd/app/grpc.go +++ b/cmd/app/grpc.go @@ -26,6 +26,8 @@ import ( "sync" "syscall" + "github.com/sigstore/sigstore/pkg/signature" + "github.com/fsnotify/fsnotify" "github.com/goadesign/goa/grpc/middleware" ctclient "github.com/google/certificate-transparency-go/client" @@ -155,7 +157,7 @@ func (c *cachedTLSCert) GRPCCreds() grpc.ServerOption { })) } -func createGRPCServer(cfg *config.FulcioConfig, ctClient *ctclient.LogClient, baseca ca.CertificateAuthority, ip identity.IssuerPool) (*grpcServer, error) { +func createGRPCServer(cfg *config.FulcioConfig, ctClient *ctclient.LogClient, baseca ca.CertificateAuthority, algorithmRegistry *signature.AlgorithmRegistryConfig, ip identity.IssuerPool) (*grpcServer, error) { logger, opts := log.SetupGRPCLogging() serverOpts := []grpc.ServerOption{ @@ -186,7 +188,7 @@ func createGRPCServer(cfg *config.FulcioConfig, ctClient *ctclient.LogClient, ba myServer := grpc.NewServer(serverOpts...) - grpcCAServer := server.NewGRPCCAServer(ctClient, baseca, ip) + grpcCAServer := server.NewGRPCCAServer(ctClient, baseca, algorithmRegistry, ip) health.RegisterHealthServer(myServer, grpcCAServer) // Register your gRPC service implementations. diff --git a/cmd/app/http_test.go b/cmd/app/http_test.go index b93b71be5..196446133 100644 --- a/cmd/app/http_test.go +++ b/cmd/app/http_test.go @@ -32,6 +32,8 @@ import ( "github.com/sigstore/fulcio/pkg/ca" "github.com/sigstore/fulcio/pkg/identity" + v1 "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1" + "github.com/sigstore/sigstore/pkg/signature" "github.com/spf13/viper" "google.golang.org/grpc" @@ -47,7 +49,11 @@ func setupHTTPServer(t *testing.T) (httpServer, string) { viper.Set("grpc-host", "") viper.Set("grpc-port", 0) - grpcServer, err := createGRPCServer(nil, nil, &TrivialCertificateAuthority{}, nil) + algorithmRegistry, err := signature.NewAlgorithmRegistryConfig([]v1.KnownSignatureAlgorithm{}) + if err != nil { + t.Error(err) + } + grpcServer, err := createGRPCServer(nil, nil, &TrivialCertificateAuthority{}, algorithmRegistry, nil) if err != nil { t.Error(err) } @@ -93,7 +99,11 @@ func setupHTTPServerWithGRPCTLS(t *testing.T) (httpServer, string) { viper.Set("grpc-host", "") viper.Set("grpc-port", 0) - grpcServer, err := createGRPCServer(nil, nil, &TrivialCertificateAuthority{}, nil) + algorithmRegistry, err := signature.NewAlgorithmRegistryConfig([]v1.KnownSignatureAlgorithm{}) + if err != nil { + t.Error(err) + } + grpcServer, err := createGRPCServer(nil, nil, &TrivialCertificateAuthority{}, algorithmRegistry, nil) if err != nil { t.Error(err) } diff --git a/cmd/app/serve.go b/cmd/app/serve.go index ebec9d56c..cf75072fa 100644 --- a/cmd/app/serve.go +++ b/cmd/app/serve.go @@ -33,6 +33,9 @@ import ( "syscall" "time" + v1 "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1" + "github.com/sigstore/sigstore/pkg/signature" + "chainguard.dev/go-grpc-kit/pkg/duplex" "github.com/goadesign/goa/grpc/middleware" ctclient "github.com/google/certificate-transparency-go/client" @@ -113,6 +116,12 @@ func newServeCmd() *cobra.Command { cmd.Flags().String("grpc-tls-key", "", "the private key file to use for secure connections (without passphrase) - only applies to grpc-port") cmd.Flags().Duration("idle-connection-timeout", 30*time.Second, "The time allowed for connections (HTTP or gRPC) to go idle before being closed by the server") cmd.Flags().String("ct-log.tls-ca-cert", "", "Path to TLS CA certificate used to connect to ct-log") + cmd.Flags().StringSlice("client-signing-algorithms", buildDefaultClientSigningAlgorithms([]v1.PublicKeyDetails{ + v1.PublicKeyDetails_PKIX_ECDSA_P256_SHA_256, + v1.PublicKeyDetails_PKIX_ECDSA_P384_SHA_384, + v1.PublicKeyDetails_PKIX_ECDSA_P521_SHA_512, + v1.PublicKeyDetails_PKIX_ED25519, + }), "the list of allowed client signing algorithms") // convert "http-host" flag to "host" and "http-port" flag to be "port" cmd.Flags().SetNormalizeFunc(func(_ *pflag.FlagSet, name string) pflag.NormalizedName { @@ -211,6 +220,20 @@ func runServeCmd(cmd *cobra.Command, args []string) { //nolint: revive // Setup the logger to dev/prod log.ConfigureLogger(viper.GetString("log_type")) + algorithmStrings := viper.GetStringSlice("client-signing-algorithms") + var algorithmConfig []v1.PublicKeyDetails + for _, s := range algorithmStrings { + algorithmValue, err := signature.ParseSignatureAlgorithmFlag(s) + if err != nil { + log.Logger.Fatal(err) + } + algorithmConfig = append(algorithmConfig, algorithmValue) + } + algorithmRegistry, err := signature.NewAlgorithmRegistryConfig(algorithmConfig) + if err != nil { + log.Logger.Fatalf("error loading --client-signing-algorithms=%s: %v", algorithmConfig, err) + } + // from https://github.com/golang/glog/commit/fca8c8854093a154ff1eb580aae10276ad6b1b5f _ = flag.CommandLine.Parse([]string{}) @@ -324,7 +347,7 @@ func runServeCmd(cmd *cobra.Command, args []string) { //nolint: revive port := viper.GetInt("port") metricsPort := viper.GetInt("metrics-port") // StartDuplexServer will always return an error, log fatally if it's non-nil - if err := StartDuplexServer(ctx, cfg, ctClient, baseca, viper.GetString("host"), port, metricsPort, ip); err != http.ErrServerClosed { + if err := StartDuplexServer(ctx, cfg, ctClient, baseca, algorithmRegistry, viper.GetString("host"), port, metricsPort, ip); err != http.ErrServerClosed { log.Logger.Fatal(err) } return @@ -337,7 +360,7 @@ func runServeCmd(cmd *cobra.Command, args []string) { //nolint: revive reg := prometheus.NewRegistry() - grpcServer, err := createGRPCServer(cfg, ctClient, baseca, ip) + grpcServer, err := createGRPCServer(cfg, ctClient, baseca, algorithmRegistry, ip) if err != nil { log.Logger.Fatal(err) } @@ -415,7 +438,7 @@ func checkServeCmdConfigFile() error { return nil } -func StartDuplexServer(ctx context.Context, cfg *config.FulcioConfig, ctClient *ctclient.LogClient, baseca certauth.CertificateAuthority, host string, port, metricsPort int, ip identity.IssuerPool) error { +func StartDuplexServer(ctx context.Context, cfg *config.FulcioConfig, ctClient *ctclient.LogClient, baseca certauth.CertificateAuthority, algorithmRegistry *signature.AlgorithmRegistryConfig, host string, port, metricsPort int, ip identity.IssuerPool) error { logger, opts := log.SetupGRPCLogging() d := duplex.New( @@ -437,7 +460,7 @@ func StartDuplexServer(ctx context.Context, cfg *config.FulcioConfig, ctClient * ) // GRPC server - grpcCAServer := server.NewGRPCCAServer(ctClient, baseca, ip) + grpcCAServer := server.NewGRPCCAServer(ctClient, baseca, algorithmRegistry, ip) protobuf.RegisterCAServer(d.Server, grpcCAServer) if err := d.RegisterHandler(ctx, protobuf.RegisterCAHandlerFromEndpoint); err != nil { return fmt.Errorf("registering grpc ca handler: %w", err) @@ -470,3 +493,15 @@ func StartDuplexServer(ctx context.Context, cfg *config.FulcioConfig, ctClient * } return nil } + +func buildDefaultClientSigningAlgorithms(allowedAlgorithms []v1.PublicKeyDetails) []string { + var algorithmStrings []string + for _, algorithm := range allowedAlgorithms { + algorithmString, err := signature.FormatSignatureAlgorithmFlag(algorithm) + if err != nil { + log.Logger.Fatal(err) + } + algorithmStrings = append(algorithmStrings, algorithmString) + } + return algorithmStrings +} diff --git a/cmd/app/serve_test.go b/cmd/app/serve_test.go index fe1f6b854..4160bddc2 100644 --- a/cmd/app/serve_test.go +++ b/cmd/app/serve_test.go @@ -31,6 +31,8 @@ import ( "github.com/sigstore/fulcio/pkg/ca/ephemeralca" "github.com/sigstore/fulcio/pkg/config" "github.com/sigstore/fulcio/pkg/generated/protobuf" + v1 "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1" + "github.com/sigstore/sigstore/pkg/signature" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" ) @@ -48,9 +50,13 @@ func TestDuplex(t *testing.T) { t.Fatal(err) } metricsPort := 2114 + algorithmRegistry, err := signature.NewAlgorithmRegistryConfig([]v1.KnownSignatureAlgorithm{}) + if err != nil { + t.Error(err) + } go func() { - if err := StartDuplexServer(ctx, config.DefaultConfig, nil, ca, "localhost", port, metricsPort, nil); err != nil { + if err := StartDuplexServer(ctx, config.DefaultConfig, nil, ca, algorithmRegistry, "localhost", port, metricsPort, nil); err != nil { log.Fatalf("error starting duplex server: %v", err) } }() diff --git a/go.mod b/go.mod index 38f1abfe2..c90350831 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,6 @@ require ( github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d github.com/coreos/go-oidc/v3 v3.12.0 github.com/fsnotify/fsnotify v1.8.0 - github.com/go-jose/go-jose/v4 v4.0.4 github.com/goadesign/goa v2.2.5+incompatible github.com/google/certificate-transparency-go v1.3.1 github.com/google/go-cmp v0.6.0 @@ -26,6 +25,7 @@ require ( github.com/prometheus/client_model v0.6.1 github.com/prometheus/common v0.62.0 github.com/rs/cors v1.11.1 + github.com/sigstore/protobuf-specs v0.3.3 github.com/sigstore/sigstore v1.8.12 github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.12 github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.12 @@ -92,6 +92,7 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-jose/go-jose/v3 v3.0.3 // indirect + github.com/go-jose/go-jose/v4 v4.0.4 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect @@ -168,3 +169,5 @@ require ( gopkg.in/ini.v1 v1.67.0 // indirect k8s.io/klog/v2 v2.130.1 // indirect ) + +replace github.com/sigstore/sigstore => github.com/trail-of-forks/sigstore v0.0.0-20250129154658-d51867d5be49 diff --git a/go.sum b/go.sum index bf2336349..7707f8ffc 100644 --- a/go.sum +++ b/go.sum @@ -307,8 +307,8 @@ github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= -github.com/sigstore/sigstore v1.8.12 h1:S8xMVZbE2z9ZBuQUEG737pxdLjnbOIcFi5v9UFfkJFc= -github.com/sigstore/sigstore v1.8.12/go.mod h1:+PYQAa8rfw0QdPpBcT+Gl3egKD9c+TUgAlF12H3Nmjo= +github.com/sigstore/protobuf-specs v0.3.3 h1:RMZQgXTD/pF7KW6b5NaRLYxFYZ/wzx44PQFXN2PEo5g= +github.com/sigstore/protobuf-specs v0.3.3/go.mod h1:vIhZ6Uor1a38+wvRrKcqL2PtYNlgoIW9lhzYzkyy4EU= github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.12 h1:EC3UmIaa7nV9sCgSpVevmvgvTYTkMqyrRbj5ojPp7tE= github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.12/go.mod h1:aw60vs3crnQdM/DYH+yF2P0MVKtItwAX34nuaMrY7Lk= github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.12 h1:FPpliDTywSy0woLHMAdmTSZ5IS/lVBZ0dY0I+2HmnSY= @@ -363,6 +363,8 @@ github.com/tink-crypto/tink-go/v2 v2.3.0 h1:4/TA0lw0lA/iVKBL9f8R5eP7397bfc4antAM github.com/tink-crypto/tink-go/v2 v2.3.0/go.mod h1:kfPOtXIadHlekBTeBtJrHWqoGL+Fm3JQg0wtltPuxLU= github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C1wj2THlRK+oAhjeS/TRQwMfkIuet3w0= github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs= +github.com/trail-of-forks/sigstore v0.0.0-20250129154658-d51867d5be49 h1:SMp4GM0QEmE50JorRByHxTCvX6HyxelazUXbTKyYYsY= +github.com/trail-of-forks/sigstore v0.0.0-20250129154658-d51867d5be49/go.mod h1:Uqpph/R+dKBUYE1FgpD1CVjkSSorfm4/KT1CAtjBtR8= github.com/ysmood/fetchup v0.2.3 h1:ulX+SonA0Vma5zUFXtv52Kzip/xe7aj4vqT5AJwQ+ZQ= github.com/ysmood/fetchup v0.2.3/go.mod h1:xhibcRKziSvol0H1/pj33dnKrYyI2ebIvz5cOOkYGns= github.com/ysmood/goob v0.4.0 h1:HsxXhyLBeGzWXnqVKtmT9qM7EuVs/XOgkX7T6r1o1AQ= diff --git a/pkg/server/grpc_server.go b/pkg/server/grpc_server.go index 9b0cb5991..d569d0e0d 100644 --- a/pkg/server/grpc_server.go +++ b/pkg/server/grpc_server.go @@ -18,10 +18,14 @@ package server import ( "context" "crypto" + "crypto/ed25519" + "crypto/x509" "encoding/json" "errors" "fmt" + "github.com/sigstore/sigstore/pkg/signature" + ctclient "github.com/google/certificate-transparency-go/client" health "google.golang.org/grpc/health/grpc_health_v1" @@ -44,11 +48,12 @@ type GRPCCAServer interface { health.HealthServer } -func NewGRPCCAServer(ct *ctclient.LogClient, ca certauth.CertificateAuthority, ip identity.IssuerPool) GRPCCAServer { +func NewGRPCCAServer(ct *ctclient.LogClient, ca certauth.CertificateAuthority, algorithmRegistry *signature.AlgorithmRegistryConfig, ip identity.IssuerPool) GRPCCAServer { return &grpcaCAServer{ - ct: ct, - ca: ca, - IssuerPool: ip, + ct: ct, + ca: ca, + algorithmRegistry: algorithmRegistry, + IssuerPool: ip, } } @@ -58,8 +63,9 @@ const ( type grpcaCAServer struct { fulciogrpc.UnimplementedCAServer - ct *ctclient.LogClient - ca certauth.CertificateAuthority + ct *ctclient.LogClient + ca certauth.CertificateAuthority + algorithmRegistry *signature.AlgorithmRegistryConfig identity.IssuerPool } @@ -88,6 +94,7 @@ func (g *grpcaCAServer) CreateSigningCertificate(ctx context.Context, request *f } var publicKey crypto.PublicKey + var hashFunc crypto.Hash // Verify caller is in possession of their private key and extract // public key from request. if len(request.GetCertificateSigningRequest()) > 0 { @@ -106,6 +113,11 @@ func (g *grpcaCAServer) CreateSigningCertificate(ctx context.Context, request *f if err := csr.CheckSignature(); err != nil { return nil, handleFulcioGRPCError(ctx, codes.InvalidArgument, err, invalidSignature) } + + hashFunc, err = getHashFuncForSignatureAlgorithm(csr.SignatureAlgorithm) + if err != nil { + return nil, handleFulcioGRPCError(ctx, codes.InvalidArgument, err, err.Error()) + } } else { // Option 2: Check the signature for proof of possession of a private key var ( @@ -133,6 +145,22 @@ func (g *grpcaCAServer) CreateSigningCertificate(ctx context.Context, request *f if err := challenges.CheckSignature(publicKey, proofOfPossession, principal.Name(ctx)); err != nil { return nil, handleFulcioGRPCError(ctx, codes.InvalidArgument, err, invalidSignature) } + + // The proof of possession signature always uses SHA-256, unless the key algorithm is ED25519 + hashFunc = crypto.SHA256 + if _, ok := publicKey.(ed25519.PublicKey); ok { + hashFunc = crypto.Hash(0) + } + } + + // Check whether the public-key/hash algorithm combination is allowed + isPermitted, err := g.algorithmRegistry.IsAlgorithmPermitted(publicKey, hashFunc) + if err != nil { + return nil, handleFulcioGRPCError(ctx, codes.InvalidArgument, err, err.Error()) + } + if !isPermitted { + err = fmt.Errorf("Signing algorithm not permitted: %T, %s", publicKey, hashFunc) + return nil, handleFulcioGRPCError(ctx, codes.InvalidArgument, err, err.Error()) } var csc *certauth.CodeSigningCertificate @@ -280,3 +308,23 @@ func (g *grpcaCAServer) Check(_ context.Context, _ *health.HealthCheckRequest) ( func (g *grpcaCAServer) Watch(_ *health.HealthCheckRequest, _ health.Health_WatchServer) error { return status.Error(codes.Unimplemented, "unimplemented") } + +func getHashFuncForSignatureAlgorithm(signatureAlgorithm x509.SignatureAlgorithm) (crypto.Hash, error) { + switch signatureAlgorithm { + case x509.ECDSAWithSHA256: + return crypto.SHA256, nil + case x509.ECDSAWithSHA384: + return crypto.SHA384, nil + case x509.ECDSAWithSHA512: + return crypto.SHA512, nil + case x509.SHA256WithRSA: + return crypto.SHA256, nil + case x509.SHA384WithRSA: + return crypto.SHA384, nil + case x509.SHA512WithRSA: + return crypto.SHA512, nil + case x509.PureEd25519: + return crypto.Hash(0), nil + } + return crypto.Hash(0), fmt.Errorf("unrecognized signature algorithm: %s", signatureAlgorithm) +} diff --git a/pkg/server/grpc_server_test.go b/pkg/server/grpc_server_test.go index 56f05b71a..f8077b28d 100644 --- a/pkg/server/grpc_server_test.go +++ b/pkg/server/grpc_server_test.go @@ -23,27 +23,14 @@ import ( "crypto/rand" "crypto/rsa" "crypto/sha256" - "crypto/x509" - "crypto/x509/pkix" - "encoding/asn1" - "encoding/json" - "encoding/pem" - "errors" - "fmt" - "net" - "net/http" - "net/http/httptest" - "net/url" - "reflect" - "strings" - "testing" - "time" - - "chainguard.dev/sdk/uidp" - "github.com/go-jose/go-jose/v4" - "github.com/go-jose/go-jose/v4/jwt" - ctclient "github.com/google/certificate-transparency-go/client" - "github.com/google/certificate-transparency-go/jsonclient" + + "github.com/sigstore/fulcio/pkg/ca/ephemeralca" + "github.com/sigstore/fulcio/pkg/config" + "github.com/sigstore/fulcio/pkg/generated/protobuf" + "github.com/sigstore/fulcio/pkg/identity" + v1 "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1" + "github.com/sigstore/sigstore/pkg/cryptoutils" + "github.com/sigstore/sigstore/pkg/signature" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -93,7 +80,11 @@ func setupGRPCForTest(t *testing.T, cfg *config.FulcioConfig, ctl *ctclient.LogC lis = bufconn.Listen(bufSize) s := grpc.NewServer(grpc.UnaryInterceptor(passFulcioConfigThruContext(cfg))) ip := NewIssuerPool(cfg) - protobuf.RegisterCAServer(s, NewGRPCCAServer(ctl, ca, ip)) + algorithmRegistry, err := signature.NewAlgorithmRegistryConfig([]v1.KnownSignatureAlgorithm{v1.KnownSignatureAlgorithm_ECDSA_SHA2_256_NISTP256, v1.KnownSignatureAlgorithm_RSA_SIGN_PKCS1_2048_SHA256}) + if err != nil { + t.Error(err) + } + protobuf.RegisterCAServer(s, NewGRPCCAServer(ctl, ca, algorithmRegistry, ip)) go func() { if err := s.Serve(lis); err != nil && !errors.Is(err, grpc.ErrServerStopped) { t.Errorf("Server exited with error: %v", err) @@ -1791,6 +1782,94 @@ func TestAPIWithIssuerClaimConfig(t *testing.T) { } } +// Tests API with an RSA key +func TestAPIWithRSA(t *testing.T) { + emailSigner, emailIssuer := newOIDCIssuer(t) + + // Create a FulcioConfig that supports these issuers. + cfg, err := config.Read([]byte(fmt.Sprintf(`{ + "OIDCIssuers": { + %q: { + "IssuerURL": %q, + "ClientID": "sigstore", + "Type": "email" + } + } + }`, emailIssuer, emailIssuer))) + if err != nil { + t.Fatalf("config.Read() = %v", err) + } + + emailSubject := "foo@example.com" + + // Create an OIDC token using this issuer's signer. + tok, err := jwt.Signed(emailSigner).Claims(jwt.Claims{ + Issuer: emailIssuer, + IssuedAt: jwt.NewNumericDate(time.Now()), + Expiry: jwt.NewNumericDate(time.Now().Add(30 * time.Minute)), + Subject: emailSubject, + Audience: jwt.Audience{"sigstore"}, + }).Claims(customClaims{Email: emailSubject, EmailVerified: true}).CompactSerialize() + if err != nil { + t.Fatalf("CompactSerialize() = %v", err) + } + + ctClient, eca := createCA(cfg, t) + ctx := context.Background() + server, conn := setupGRPCForTest(ctx, t, cfg, ctClient, eca) + defer func() { + server.Stop() + conn.Close() + }() + + client := protobuf.NewCAClient(conn) + + priv, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + t.Fatalf("GenerateKey() = %v", err) + } + pubBytes, err := x509.MarshalPKIXPublicKey(&priv.PublicKey) + if err != nil { + t.Fatalf("x509.MarshalPKIXPublicKey() = %v", err) + } + hash := sha256.Sum256([]byte(emailSubject)) + proof, err := rsa.SignPKCS1v15(rand.Reader, priv, crypto.SHA256, hash[:]) + if err != nil { + t.Fatalf("SignPKCS1v15() = %v", err) + } + pemBytes := string(cryptoutils.PEMEncode(cryptoutils.PublicKeyPEMType, pubBytes)) + + // Hit the API to have it sign our certificate. + resp, err := client.CreateSigningCertificate(ctx, &protobuf.CreateSigningCertificateRequest{ + Credentials: &protobuf.Credentials{ + Credentials: &protobuf.Credentials_OidcIdentityToken{ + OidcIdentityToken: tok, + }, + }, + Key: &protobuf.CreateSigningCertificateRequest_PublicKeyRequest{ + PublicKeyRequest: &protobuf.PublicKeyRequest{ + PublicKey: &protobuf.PublicKey{ + Content: pemBytes, + }, + ProofOfPossession: proof, + }, + }, + }) + if err != nil { + t.Fatalf("SigningCert() = %v", err) + } + + leafCert := verifyResponse(resp, eca, emailIssuer, t) + + // Expect email subject + if len(leafCert.EmailAddresses) != 1 { + t.Fatalf("unexpected length of leaf certificate URIs, expected 1, got %d", len(leafCert.URIs)) + } + if leafCert.EmailAddresses[0] != emailSubject { + t.Fatalf("subjects do not match: Expected %v, got %v", emailSubject, leafCert.EmailAddresses[0]) + } +} + // Tests API with challenge sent as CSR func TestAPIWithCSRChallenge(t *testing.T) { emailSigner, emailIssuer := newOIDCIssuer(t) @@ -1873,6 +1952,89 @@ func TestAPIWithCSRChallenge(t *testing.T) { } } +// Tests API with challenge sent as CSR with an RSA key +func TestAPIWithCSRChallengeRSA(t *testing.T) { + emailSigner, emailIssuer := newOIDCIssuer(t) + + // Create a FulcioConfig that supports these issuers. + cfg, err := config.Read([]byte(fmt.Sprintf(`{ + "OIDCIssuers": { + %q: { + "IssuerURL": %q, + "ClientID": "sigstore", + "Type": "email" + } + } + }`, emailIssuer, emailIssuer))) + if err != nil { + t.Fatalf("config.Read() = %v", err) + } + + emailSubject := "foo@example.com" + + // Create an OIDC token using this issuer's signer. + tok, err := jwt.Signed(emailSigner).Claims(jwt.Claims{ + Issuer: emailIssuer, + IssuedAt: jwt.NewNumericDate(time.Now()), + Expiry: jwt.NewNumericDate(time.Now().Add(30 * time.Minute)), + Subject: emailSubject, + Audience: jwt.Audience{"sigstore"}, + }).Claims(customClaims{Email: emailSubject, EmailVerified: true}).CompactSerialize() + if err != nil { + t.Fatalf("CompactSerialize() = %v", err) + } + + ctClient, eca := createCA(cfg, t) + ctx := context.Background() + server, conn := setupGRPCForTest(ctx, t, cfg, ctClient, eca) + defer func() { + server.Stop() + conn.Close() + }() + + client := protobuf.NewCAClient(conn) + + priv, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + t.Fatalf("GenerateKey() = %v", err) + } + csrTmpl := &x509.CertificateRequest{Subject: pkix.Name{CommonName: "test"}} + derCSR, err := x509.CreateCertificateRequest(rand.Reader, csrTmpl, priv) + if err != nil { + t.Fatalf("error creating CSR: %v", err) + } + pemCSR := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE REQUEST", + Bytes: derCSR, + }) + + // Hit the API to have it sign our certificate. + // Hit the API to have it sign our certificate. + resp, err := client.CreateSigningCertificate(ctx, &protobuf.CreateSigningCertificateRequest{ + Credentials: &protobuf.Credentials{ + Credentials: &protobuf.Credentials_OidcIdentityToken{ + OidcIdentityToken: tok, + }, + }, + Key: &protobuf.CreateSigningCertificateRequest_CertificateSigningRequest{ + CertificateSigningRequest: pemCSR, + }, + }) + if err != nil { + t.Fatalf("SigningCert() = %v", err) + } + + leafCert := verifyResponse(resp, eca, emailIssuer, t) + + // Expect email subject + if len(leafCert.EmailAddresses) != 1 { + t.Fatalf("unexpected length of leaf certificate URIs, expected 1, got %d", len(leafCert.URIs)) + } + if leafCert.EmailAddresses[0] != emailSubject { + t.Fatalf("subjects do not match: Expected %v, got %v", emailSubject, leafCert.EmailAddresses[0]) + } +} + // Tests API with insecure pub key func TestAPIWithInsecurePublicKey(t *testing.T) { emailSigner, emailIssuer := newOIDCIssuer(t) @@ -2092,6 +2254,87 @@ func TestAPIWithInvalidChallenge(t *testing.T) { } } +// Tests API with an ECDSA key with an unpermitted curve +func TestAPIWithInvalidPublicKey(t *testing.T) { + emailSigner, emailIssuer := newOIDCIssuer(t) + + // Create a FulcioConfig that supports these issuers. + cfg, err := config.Read([]byte(fmt.Sprintf(`{ + "OIDCIssuers": { + %q: { + "IssuerURL": %q, + "ClientID": "sigstore", + "Type": "email" + } + } + }`, emailIssuer, emailIssuer))) + if err != nil { + t.Fatalf("config.Read() = %v", err) + } + + emailSubject := "foo@example.com" + + // Create an OIDC token using this issuer's signer. + tok, err := jwt.Signed(emailSigner).Claims(jwt.Claims{ + Issuer: emailIssuer, + IssuedAt: jwt.NewNumericDate(time.Now()), + Expiry: jwt.NewNumericDate(time.Now().Add(30 * time.Minute)), + Subject: emailSubject, + Audience: jwt.Audience{"sigstore"}, + }).Claims(customClaims{Email: emailSubject, EmailVerified: true}).CompactSerialize() + if err != nil { + t.Fatalf("CompactSerialize() = %v", err) + } + + ctClient, eca := createCA(cfg, t) + ctx := context.Background() + server, conn := setupGRPCForTest(ctx, t, cfg, ctClient, eca) + defer func() { + server.Stop() + conn.Close() + }() + + client := protobuf.NewCAClient(conn) + + // Generate an ECDSA key with an unpermitted curve + priv, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) + if err != nil { + t.Fatalf("GenerateKey() = %v", err) + } + pubBytes, err := x509.MarshalPKIXPublicKey(&priv.PublicKey) + if err != nil { + t.Fatalf("x509.MarshalPKIXPublicKey() = %v", err) + } + hash := sha256.Sum256([]byte(emailSubject)) + proof, err := ecdsa.SignASN1(rand.Reader, priv, hash[:]) + if err != nil { + t.Fatalf("SignASN1() = %v", err) + } + pemBytes := string(cryptoutils.PEMEncode(cryptoutils.PublicKeyPEMType, pubBytes)) + + _, err = client.CreateSigningCertificate(ctx, &protobuf.CreateSigningCertificateRequest{ + Credentials: &protobuf.Credentials{ + Credentials: &protobuf.Credentials_OidcIdentityToken{ + OidcIdentityToken: tok, + }, + }, + Key: &protobuf.CreateSigningCertificateRequest_PublicKeyRequest{ + PublicKeyRequest: &protobuf.PublicKeyRequest{ + PublicKey: &protobuf.PublicKey{ + Content: pemBytes, + }, + ProofOfPossession: proof, + }, + }, + }) + if err == nil || !strings.Contains(err.Error(), "Signing algorithm not permitted") { + t.Fatalf("expected signing algorithm not permitted, got %v", err) + } + if status.Code(err) != codes.InvalidArgument { + t.Fatalf("expected invalid argument, got %v", status.Code(err)) + } +} + // Tests API with an invalid CSR. func TestAPIWithInvalidCSR(t *testing.T) { emailSigner, emailIssuer := newOIDCIssuer(t) @@ -2231,6 +2474,81 @@ func TestAPIWithInvalidCSRSignature(t *testing.T) { } } +// Tests API with CSR, containing ECDSA key with unpermitted curve +func TestAPIWithInvalidCSRPublicKey(t *testing.T) { + emailSigner, emailIssuer := newOIDCIssuer(t) + + // Create a FulcioConfig that supports this issuer. + cfg, err := config.Read([]byte(fmt.Sprintf(`{ + "OIDCIssuers": { + %q: { + "IssuerURL": %q, + "ClientID": "sigstore", + "Type": "email" + } + } + }`, emailIssuer, emailIssuer))) + if err != nil { + t.Fatalf("config.Read() = %v", err) + } + + emailSubject := "foo@example.com" + + // Create an OIDC token using this issuer's signer. + tok, err := jwt.Signed(emailSigner).Claims(jwt.Claims{ + Issuer: emailIssuer, + IssuedAt: jwt.NewNumericDate(time.Now()), + Expiry: jwt.NewNumericDate(time.Now().Add(30 * time.Minute)), + Subject: emailSubject, + Audience: jwt.Audience{"sigstore"}, + }).Claims(customClaims{Email: emailSubject, EmailVerified: true}).CompactSerialize() + if err != nil { + t.Fatalf("CompactSerialize() = %v", err) + } + + ctClient, eca := createCA(cfg, t) + ctx := context.Background() + server, conn := setupGRPCForTest(ctx, t, cfg, ctClient, eca) + defer func() { + server.Stop() + conn.Close() + }() + + client := protobuf.NewCAClient(conn) + + priv, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) + if err != nil { + t.Fatalf("error generating private key: %v", err) + } + csrTmpl := &x509.CertificateRequest{Subject: pkix.Name{CommonName: "test"}} + derCSR, err := x509.CreateCertificateRequest(rand.Reader, csrTmpl, priv) + if err != nil { + t.Fatalf("error creating CSR: %v", err) + } + pemCSR := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE REQUEST", + Bytes: derCSR, + }) + + // Hit the API to have it sign our certificate. + _, err = client.CreateSigningCertificate(ctx, &protobuf.CreateSigningCertificateRequest{ + Credentials: &protobuf.Credentials{ + Credentials: &protobuf.Credentials_OidcIdentityToken{ + OidcIdentityToken: tok, + }, + }, + Key: &protobuf.CreateSigningCertificateRequest_CertificateSigningRequest{ + CertificateSigningRequest: pemCSR, + }, + }) + if err == nil || !strings.Contains(err.Error(), "Signing algorithm not permitted") { + t.Fatalf("expected signing algorithm not permitted, got %v", err) + } + if status.Code(err) != codes.InvalidArgument { + t.Fatalf("expected invalid argument, got %v", status.Code(err)) + } +} + // Stand up a very simple OIDC endpoint. func newOIDCIssuer(t *testing.T) (jose.Signer, string) { t.Helper() From 7c64410dc7a1133bb2582e39f50ed7c1d8fbb1ae Mon Sep 17 00:00:00 2001 From: Riccardo Schirone Date: Wed, 5 Feb 2025 17:39:22 +0100 Subject: [PATCH 2/3] update go.mod Signed-off-by: Riccardo Schirone --- go.mod | 8 +++----- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index c90350831..7d38945af 100644 --- a/go.mod +++ b/go.mod @@ -25,8 +25,8 @@ require ( github.com/prometheus/client_model v0.6.1 github.com/prometheus/common v0.62.0 github.com/rs/cors v1.11.1 - github.com/sigstore/protobuf-specs v0.3.3 - github.com/sigstore/sigstore v1.8.12 + github.com/sigstore/protobuf-specs v0.4.0 + github.com/sigstore/sigstore v1.8.13-0.20250204232249-4b62818325b7 github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.12 github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.12 github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.12 @@ -44,7 +44,7 @@ require ( google.golang.org/api v0.218.0 google.golang.org/genproto/googleapis/api v0.0.0-20241219192143-6b3ec007d9bb google.golang.org/grpc v1.70.0 - google.golang.org/protobuf v1.36.3 + google.golang.org/protobuf v1.36.4 gopkg.in/yaml.v3 v3.0.1 sigs.k8s.io/release-utils v0.9.0 ) @@ -169,5 +169,3 @@ require ( gopkg.in/ini.v1 v1.67.0 // indirect k8s.io/klog/v2 v2.130.1 // indirect ) - -replace github.com/sigstore/sigstore => github.com/trail-of-forks/sigstore v0.0.0-20250129154658-d51867d5be49 diff --git a/go.sum b/go.sum index 7707f8ffc..0c7d2fe76 100644 --- a/go.sum +++ b/go.sum @@ -307,8 +307,10 @@ github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= -github.com/sigstore/protobuf-specs v0.3.3 h1:RMZQgXTD/pF7KW6b5NaRLYxFYZ/wzx44PQFXN2PEo5g= -github.com/sigstore/protobuf-specs v0.3.3/go.mod h1:vIhZ6Uor1a38+wvRrKcqL2PtYNlgoIW9lhzYzkyy4EU= +github.com/sigstore/protobuf-specs v0.4.0 h1:yoZbdh0kZYKOSiVbYyA8J3f2wLh5aUk2SQB7LgAfIdU= +github.com/sigstore/protobuf-specs v0.4.0/go.mod h1:FKW5NYhnnFQ/Vb9RKtQk91iYd0MKJ9AxyqInEwU6+OI= +github.com/sigstore/sigstore v1.8.13-0.20250204232249-4b62818325b7 h1:dKgngAj90pDWGKB/yBt/AmSvNFzLOPvYGfEgEKRQWc4= +github.com/sigstore/sigstore v1.8.13-0.20250204232249-4b62818325b7/go.mod h1:2lXojNsjZjkqu1//FWxq7qUcPB8Lq1KsR5hc+GkcC/4= github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.12 h1:EC3UmIaa7nV9sCgSpVevmvgvTYTkMqyrRbj5ojPp7tE= github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.12/go.mod h1:aw60vs3crnQdM/DYH+yF2P0MVKtItwAX34nuaMrY7Lk= github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.12 h1:FPpliDTywSy0woLHMAdmTSZ5IS/lVBZ0dY0I+2HmnSY= @@ -363,8 +365,6 @@ github.com/tink-crypto/tink-go/v2 v2.3.0 h1:4/TA0lw0lA/iVKBL9f8R5eP7397bfc4antAM github.com/tink-crypto/tink-go/v2 v2.3.0/go.mod h1:kfPOtXIadHlekBTeBtJrHWqoGL+Fm3JQg0wtltPuxLU= github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C1wj2THlRK+oAhjeS/TRQwMfkIuet3w0= github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs= -github.com/trail-of-forks/sigstore v0.0.0-20250129154658-d51867d5be49 h1:SMp4GM0QEmE50JorRByHxTCvX6HyxelazUXbTKyYYsY= -github.com/trail-of-forks/sigstore v0.0.0-20250129154658-d51867d5be49/go.mod h1:Uqpph/R+dKBUYE1FgpD1CVjkSSorfm4/KT1CAtjBtR8= github.com/ysmood/fetchup v0.2.3 h1:ulX+SonA0Vma5zUFXtv52Kzip/xe7aj4vqT5AJwQ+ZQ= github.com/ysmood/fetchup v0.2.3/go.mod h1:xhibcRKziSvol0H1/pj33dnKrYyI2ebIvz5cOOkYGns= github.com/ysmood/goob v0.4.0 h1:HsxXhyLBeGzWXnqVKtmT9qM7EuVs/XOgkX7T6r1o1AQ= @@ -530,8 +530,8 @@ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8 google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= -google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= -google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= +google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= From e65897c7049fc1c751658dfbf531798a07f451b1 Mon Sep 17 00:00:00 2001 From: Riccardo Schirone Date: Wed, 5 Feb 2025 17:44:18 +0100 Subject: [PATCH 3/3] grpc_server_test.go: remove duplicated comment Signed-off-by: Riccardo Schirone --- pkg/server/grpc_server_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/server/grpc_server_test.go b/pkg/server/grpc_server_test.go index f8077b28d..97e493f93 100644 --- a/pkg/server/grpc_server_test.go +++ b/pkg/server/grpc_server_test.go @@ -2008,7 +2008,6 @@ func TestAPIWithCSRChallengeRSA(t *testing.T) { Bytes: derCSR, }) - // Hit the API to have it sign our certificate. // Hit the API to have it sign our certificate. resp, err := client.CreateSigningCertificate(ctx, &protobuf.CreateSigningCertificateRequest{ Credentials: &protobuf.Credentials{