From 4737f655189fcbc814409eeb01add45ac9e3befb Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Fri, 31 May 2024 09:37:32 +0200 Subject: [PATCH 1/5] feat(keyless): signing use case Signed-off-by: Jose I. Paris --- app/controlplane/internal/biz/biz.go | 1 + app/controlplane/internal/biz/signing.go | 111 ++++++++++++++++++ app/controlplane/internal/biz/signing_test.go | 98 ++++++++++++++++ go.mod | 8 +- go.sum | 15 ++- 5 files changed, 231 insertions(+), 2 deletions(-) create mode 100644 app/controlplane/internal/biz/signing.go create mode 100644 app/controlplane/internal/biz/signing_test.go diff --git a/app/controlplane/internal/biz/biz.go b/app/controlplane/internal/biz/biz.go index be0ebca8c..1f63efa4b 100644 --- a/app/controlplane/internal/biz/biz.go +++ b/app/controlplane/internal/biz/biz.go @@ -48,6 +48,7 @@ var ProviderSet = wire.NewSet( NewAPITokenUseCase, NewAPITokenSyncerUseCase, NewAttestationStateUseCase, + NewChainloopSigningUseCase, wire.Struct(new(NewIntegrationUseCaseOpts), "*"), wire.Struct(new(NewUserUseCaseParams), "*"), ) diff --git a/app/controlplane/internal/biz/signing.go b/app/controlplane/internal/biz/signing.go new file mode 100644 index 000000000..681313297 --- /dev/null +++ b/app/controlplane/internal/biz/signing.go @@ -0,0 +1,111 @@ +// +// Copyright 2024 The Chainloop 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 biz + +import ( + "context" + "crypto" + "crypto/x509" + "crypto/x509/pkix" + "errors" + + "github.com/sigstore/fulcio/pkg/ca" + "github.com/sigstore/fulcio/pkg/identity" + "github.com/sigstore/sigstore/pkg/cryptoutils" +) + +type SigningCertCreator interface { + // CreateSigningCert creates a signing certificate from and returns the certificate chain + CreateSigningCert(context.Context, string, []byte) ([]string, error) +} + +type SigningUseCase struct { + CA ca.CertificateAuthority +} + +var _ SigningCertCreator = (*SigningUseCase)(nil) + +func NewChainloopSigningUseCase(ca ca.CertificateAuthority) *SigningUseCase { + return &SigningUseCase{CA: ca} +} + +func (s *SigningUseCase) CreateSigningCert(ctx context.Context, orgId string, csrRaw []byte) ([]string, error) { + var publicKey crypto.PublicKey + + if len(csrRaw) == 0 { + return nil, errors.New("csr cannot be empty") + } + + // Parse CSR + csr, err := cryptoutils.ParseCSR(csrRaw) + if err != nil { + return nil, err + } + + // Parse public key and check for weak key parameters + publicKey = csr.PublicKey + if err := cryptoutils.ValidatePubKey(publicKey); err != nil { + return nil, err + } + + // Check the CSR signature is valid + if err := csr.CheckSignature(); err != nil { + return nil, err + } + + // Create certificate from CA provider (no Signed Certificate Timestamps for now) + csc, err := s.CA.CreateCertificate(ctx, NewChainloopPrincipal(orgId), publicKey) + if err != nil { + return nil, err + } + + // Generated certificate + finalPEM, err := csc.CertPEM() + if err != nil { + return nil, err + } + + // Certificate chain + finalChainPEM, err := csc.ChainPEM() + if err != nil { + return nil, err + } + + return append([]string{finalPEM}, finalChainPEM...), nil +} + +type ChainloopPrincipal struct { + orgId string +} + +var _ identity.Principal = (*ChainloopPrincipal)(nil) + +func NewChainloopPrincipal(orgId string) *ChainloopPrincipal { + return &ChainloopPrincipal{orgId: orgId} +} + +func (p *ChainloopPrincipal) Name(_ context.Context) string { + return p.orgId +} + +func (p *ChainloopPrincipal) Embed(_ context.Context, cert *x509.Certificate) error { + // no op. + // TODO: Chainloop might have their own private enterprise number with the Internet Assigned Numbers Authority + // to embed its own identity information in the resulting certificate + cert.Subject = pkix.Name{Organization: []string{p.orgId}} + + return nil +} diff --git a/app/controlplane/internal/biz/signing_test.go b/app/controlplane/internal/biz/signing_test.go new file mode 100644 index 000000000..97a95a21f --- /dev/null +++ b/app/controlplane/internal/biz/signing_test.go @@ -0,0 +1,98 @@ +// +// Copyright 2024 The Chainloop 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 biz_test + +import ( + "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "testing" + + "github.com/chainloop-dev/chainloop/app/controlplane/internal/biz" + "github.com/sigstore/fulcio/pkg/ca/ephemeralca" + "github.com/sigstore/sigstore/pkg/cryptoutils" + "github.com/stretchr/testify/suite" +) + +type signingUseCaseTestSuite struct { + suite.Suite + uc *biz.SigningUseCase + csr []byte +} + +func (s *signingUseCaseTestSuite) TestSigningUseCase_CreateSigningCert() { + s.Run("with empty certificate", func() { + _, err := s.uc.CreateSigningCert(context.TODO(), "myorgid", make([]byte, 0)) + s.Error(err) + }) + + s.Run("with certificate request", func() { + certChain, err := s.uc.CreateSigningCert(context.TODO(), "myorgid", s.csr) + s.NoError(err) + + // assert 2 certificates: signing certificate + chain (only one) + s.Assert().Len(certChain, 2) + + // check cert contents + cert, err := cryptoutils.UnmarshalCertificatesFromPEM([]byte(certChain[0])) + s.NoError(err) + s.Assert().Len(cert, 1) + s.Assert().Equal("myorgid", cert[0].Subject.Organization[0]) + }) +} + +func TestSuite(t *testing.T) { + suite.Run(t, new(signingUseCaseTestSuite)) +} + +func (s *signingUseCaseTestSuite) SetupTest() { + csr, err := createCSR() + if err != nil { + panic(err) + } + s.csr = csr + + ca, err := ephemeralca.NewEphemeralCA() + if err != nil { + s.T().Fatal(err) + } + s.uc = &biz.SigningUseCase{CA: ca} +} + +func createCSR() ([]byte, error) { + priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, fmt.Errorf("generating cert: %w", err) + } + csrTmpl := &x509.CertificateRequest{Subject: pkix.Name{CommonName: "ephemeral certificate"}} + derCSR, err := x509.CreateCertificateRequest(rand.Reader, csrTmpl, priv) + if err != nil { + return nil, fmt.Errorf("generating certificate request: %w", err) + } + + // Encode CSR to PEM + pemCSR := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE REQUEST", + Bytes: derCSR, + }) + + return pemCSR, nil +} diff --git a/go.mod b/go.mod index 7c7e8002a..6a92cc262 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( github.com/google/wire v0.6.0 github.com/googleapis/gax-go/v2 v2.12.3 github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 - github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 + github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20210315223345-82c243799c99 github.com/hashicorp/vault/api v1.12.2 github.com/hedwigz/entviz v0.0.0-20221011080911-9d47f6f1d818 github.com/improbable-eng/grpc-web v0.15.0 @@ -80,6 +80,7 @@ require ( github.com/openvex/go-vex v0.2.5 github.com/posthog/posthog-go v0.0.0-20240327112532-87b23fe11103 github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 + github.com/sigstore/fulcio v1.4.5 github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.3 github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.3 github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.3 @@ -118,6 +119,7 @@ require ( github.com/go-jose/go-jose/v4 v4.0.1 // indirect github.com/go-playground/assert/v2 v2.2.0 // indirect github.com/go-sql-driver/mysql v1.8.1 // indirect + github.com/goadesign/goa v2.2.5+incompatible // indirect github.com/gobwas/ws v1.2.1 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/google/cel-go v0.20.1 // indirect @@ -125,7 +127,9 @@ require ( github.com/google/go-github/v55 v55.0.0 // indirect github.com/google/renameio/v2 v2.0.0 // indirect github.com/gorilla/handlers v1.5.1 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect github.com/hashicorp/go-hclog v1.5.0 // indirect + github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect github.com/jackc/pgconn v1.14.3 // indirect @@ -155,11 +159,13 @@ require ( github.com/sergi/go-diff v1.3.1 // indirect github.com/skeema/knownhosts v1.2.1 // indirect github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spiffe/go-spiffe/v2 v2.2.0 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect + goa.design/goa v2.2.5+incompatible // indirect gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect ) diff --git a/go.sum b/go.sum index 4734b006f..05cca597b 100644 --- a/go.sum +++ b/go.sum @@ -557,6 +557,8 @@ github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4 github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/goadesign/goa v2.2.5+incompatible h1:SLgzk0V+QfFs7MVz9sbDHelbTDI9B/d4W7Hl5udTynY= +github.com/goadesign/goa v2.2.5+incompatible/go.mod h1:d/9lpuZBK7HFi/7O0oXfwvdoIl+nx2bwKqctZe/lQao= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= @@ -732,11 +734,14 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDa github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0/go.mod h1:XKMd7iuf/RGPSMJ/U4HP0zS2Z9Fh8Ps9a+6X26m/tmI= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20210315223345-82c243799c99 h1:JYghRBlGCZyCF2wNUJ8W0cwaQdtpcssJ4CgC406g+WU= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20210315223345-82c243799c99/go.mod h1:3bDW6wMZJB7tiONtC/1Xpicra6Wp5GgbTbQWCbI5fkc= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= @@ -787,6 +792,8 @@ github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.1-vault-5 h1:kI3hhbbyzr4dldA8UdTb7ZlVVlI2DACdCfz31RPDgJM= github.com/hashicorp/hcl v1.0.1-vault-5/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= @@ -1172,6 +1179,7 @@ github.com/posthog/posthog-go v0.0.0-20240327112532-87b23fe11103/go.mod h1:Qjlpr github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= @@ -1189,6 +1197,7 @@ github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6T github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= @@ -1199,6 +1208,7 @@ github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16 github.com/prometheus/common v0.51.1 h1:eIjN50Bwglz6a/c3hAgSMcofL3nD+nFQkV6Dd4DsQCw= github.com/prometheus/common v0.51.1/go.mod h1:lrWtQx+iDfn2mbH5GUzlH9TSHyfZpHkSiG1W7y3sF2Q= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -1482,6 +1492,8 @@ go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +goa.design/goa v2.2.5+incompatible h1:mjAtiy7ZdZIkj974hpFxCR6bL69qprfV00Veu3Vybts= +goa.design/goa v2.2.5+incompatible/go.mod h1:NnzBwdNktihbNek+pPiFMQP9PPFsUt8MMPPyo9opDSo= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -1992,6 +2004,7 @@ google.golang.org/genproto/googleapis/bytestream v0.0.0-20240318140521-94a12d6c2 google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4= google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= From 12af82d0ebf096a52bb0e3a62e962ba207ba21e6 Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Fri, 31 May 2024 10:09:41 +0200 Subject: [PATCH 2/5] lint Signed-off-by: Jose I. Paris --- app/controlplane/internal/biz/signing.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/controlplane/internal/biz/signing.go b/app/controlplane/internal/biz/signing.go index 681313297..c9be48096 100644 --- a/app/controlplane/internal/biz/signing.go +++ b/app/controlplane/internal/biz/signing.go @@ -42,7 +42,7 @@ func NewChainloopSigningUseCase(ca ca.CertificateAuthority) *SigningUseCase { return &SigningUseCase{CA: ca} } -func (s *SigningUseCase) CreateSigningCert(ctx context.Context, orgId string, csrRaw []byte) ([]string, error) { +func (s *SigningUseCase) CreateSigningCert(ctx context.Context, orgID string, csrRaw []byte) ([]string, error) { var publicKey crypto.PublicKey if len(csrRaw) == 0 { @@ -67,7 +67,7 @@ func (s *SigningUseCase) CreateSigningCert(ctx context.Context, orgId string, cs } // Create certificate from CA provider (no Signed Certificate Timestamps for now) - csc, err := s.CA.CreateCertificate(ctx, NewChainloopPrincipal(orgId), publicKey) + csc, err := s.CA.CreateCertificate(ctx, NewChainloopPrincipal(orgID), publicKey) if err != nil { return nil, err } @@ -88,24 +88,24 @@ func (s *SigningUseCase) CreateSigningCert(ctx context.Context, orgId string, cs } type ChainloopPrincipal struct { - orgId string + orgID string } var _ identity.Principal = (*ChainloopPrincipal)(nil) -func NewChainloopPrincipal(orgId string) *ChainloopPrincipal { - return &ChainloopPrincipal{orgId: orgId} +func NewChainloopPrincipal(orgID string) *ChainloopPrincipal { + return &ChainloopPrincipal{orgID: orgID} } func (p *ChainloopPrincipal) Name(_ context.Context) string { - return p.orgId + return p.orgID } func (p *ChainloopPrincipal) Embed(_ context.Context, cert *x509.Certificate) error { // no op. // TODO: Chainloop might have their own private enterprise number with the Internet Assigned Numbers Authority // to embed its own identity information in the resulting certificate - cert.Subject = pkix.Name{Organization: []string{p.orgId}} + cert.Subject = pkix.Name{Organization: []string{p.orgID}} return nil } From 1e583bb67b5479ea6cfb328390ed9915e6df69b0 Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Fri, 31 May 2024 17:40:18 +0200 Subject: [PATCH 3/5] address sugestions Signed-off-by: Jose I. Paris --- app/controlplane/internal/biz/signing.go | 35 ++++++++----------- app/controlplane/internal/biz/signing_test.go | 6 ++-- 2 files changed, 18 insertions(+), 23 deletions(-) diff --git a/app/controlplane/internal/biz/signing.go b/app/controlplane/internal/biz/signing.go index c9be48096..51be9908c 100644 --- a/app/controlplane/internal/biz/signing.go +++ b/app/controlplane/internal/biz/signing.go @@ -21,27 +21,22 @@ import ( "crypto/x509" "crypto/x509/pkix" "errors" + "fmt" "github.com/sigstore/fulcio/pkg/ca" "github.com/sigstore/fulcio/pkg/identity" "github.com/sigstore/sigstore/pkg/cryptoutils" ) -type SigningCertCreator interface { - // CreateSigningCert creates a signing certificate from and returns the certificate chain - CreateSigningCert(context.Context, string, []byte) ([]string, error) -} - type SigningUseCase struct { CA ca.CertificateAuthority } -var _ SigningCertCreator = (*SigningUseCase)(nil) - func NewChainloopSigningUseCase(ca ca.CertificateAuthority) *SigningUseCase { return &SigningUseCase{CA: ca} } +// CreateSigningCert signs a certificate request with a configured CA, and returns the full certificate chain func (s *SigningUseCase) CreateSigningCert(ctx context.Context, orgID string, csrRaw []byte) ([]string, error) { var publicKey crypto.PublicKey @@ -52,56 +47,56 @@ func (s *SigningUseCase) CreateSigningCert(ctx context.Context, orgID string, cs // Parse CSR csr, err := cryptoutils.ParseCSR(csrRaw) if err != nil { - return nil, err + return nil, fmt.Errorf("parsing csr: %w", err) } // Parse public key and check for weak key parameters publicKey = csr.PublicKey if err := cryptoutils.ValidatePubKey(publicKey); err != nil { - return nil, err + return nil, fmt.Errorf("invalid public key: %w", err) } // Check the CSR signature is valid if err := csr.CheckSignature(); err != nil { - return nil, err + return nil, fmt.Errorf("invalid signature: %w", err) } // Create certificate from CA provider (no Signed Certificate Timestamps for now) - csc, err := s.CA.CreateCertificate(ctx, NewChainloopPrincipal(orgID), publicKey) + csc, err := s.CA.CreateCertificate(ctx, newChainloopPrincipal(orgID), publicKey) if err != nil { - return nil, err + return nil, fmt.Errorf("creating certificate: %w", err) } // Generated certificate finalPEM, err := csc.CertPEM() if err != nil { - return nil, err + return nil, fmt.Errorf("marshaling certificate to PEM: %w", err) } // Certificate chain finalChainPEM, err := csc.ChainPEM() if err != nil { - return nil, err + return nil, fmt.Errorf("marshaling chain to PEM: %w", err) } return append([]string{finalPEM}, finalChainPEM...), nil } -type ChainloopPrincipal struct { +type chainloopPrincipal struct { orgID string } -var _ identity.Principal = (*ChainloopPrincipal)(nil) +var _ identity.Principal = (*chainloopPrincipal)(nil) -func NewChainloopPrincipal(orgID string) *ChainloopPrincipal { - return &ChainloopPrincipal{orgID: orgID} +func newChainloopPrincipal(orgID string) *chainloopPrincipal { + return &chainloopPrincipal{orgID: orgID} } -func (p *ChainloopPrincipal) Name(_ context.Context) string { +func (p *chainloopPrincipal) Name(_ context.Context) string { return p.orgID } -func (p *ChainloopPrincipal) Embed(_ context.Context, cert *x509.Certificate) error { +func (p *chainloopPrincipal) Embed(_ context.Context, cert *x509.Certificate) error { // no op. // TODO: Chainloop might have their own private enterprise number with the Internet Assigned Numbers Authority // to embed its own identity information in the resulting certificate diff --git a/app/controlplane/internal/biz/signing_test.go b/app/controlplane/internal/biz/signing_test.go index 97a95a21f..fe92759a1 100644 --- a/app/controlplane/internal/biz/signing_test.go +++ b/app/controlplane/internal/biz/signing_test.go @@ -49,7 +49,7 @@ func (s *signingUseCaseTestSuite) TestSigningUseCase_CreateSigningCert() { s.NoError(err) // assert 2 certificates: signing certificate + chain (only one) - s.Assert().Len(certChain, 2) + s.Len(certChain, 2) // check cert contents cert, err := cryptoutils.UnmarshalCertificatesFromPEM([]byte(certChain[0])) @@ -66,13 +66,13 @@ func TestSuite(t *testing.T) { func (s *signingUseCaseTestSuite) SetupTest() { csr, err := createCSR() if err != nil { - panic(err) + s.Require().NoError(err) } s.csr = csr ca, err := ephemeralca.NewEphemeralCA() if err != nil { - s.T().Fatal(err) + s.Require().NoError(err) } s.uc = &biz.SigningUseCase{CA: ca} } From 3bf7c1135f4f66c304d2c3510a5abb827516702c Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Fri, 31 May 2024 17:55:49 +0200 Subject: [PATCH 4/5] remove unneeded "Assert" Signed-off-by: Jose I. Paris --- app/controlplane/internal/biz/signing_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controlplane/internal/biz/signing_test.go b/app/controlplane/internal/biz/signing_test.go index fe92759a1..07460b431 100644 --- a/app/controlplane/internal/biz/signing_test.go +++ b/app/controlplane/internal/biz/signing_test.go @@ -54,8 +54,8 @@ func (s *signingUseCaseTestSuite) TestSigningUseCase_CreateSigningCert() { // check cert contents cert, err := cryptoutils.UnmarshalCertificatesFromPEM([]byte(certChain[0])) s.NoError(err) - s.Assert().Len(cert, 1) - s.Assert().Equal("myorgid", cert[0].Subject.Organization[0]) + s.Len(cert, 1) + s.Equal("myorgid", cert[0].Subject.Organization[0]) }) } From 81eaa4b57f90ecafc91ba272fcdcaf9271c12aa2 Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Fri, 31 May 2024 17:56:53 +0200 Subject: [PATCH 5/5] remove nil check Signed-off-by: Jose I. Paris --- app/controlplane/internal/biz/signing_test.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/app/controlplane/internal/biz/signing_test.go b/app/controlplane/internal/biz/signing_test.go index 07460b431..f29417c94 100644 --- a/app/controlplane/internal/biz/signing_test.go +++ b/app/controlplane/internal/biz/signing_test.go @@ -65,15 +65,11 @@ func TestSuite(t *testing.T) { func (s *signingUseCaseTestSuite) SetupTest() { csr, err := createCSR() - if err != nil { - s.Require().NoError(err) - } + s.Require().NoError(err) s.csr = csr ca, err := ephemeralca.NewEphemeralCA() - if err != nil { - s.Require().NoError(err) - } + s.Require().NoError(err) s.uc = &biz.SigningUseCase{CA: ca} }