Skip to content

Commit

Permalink
feat(keyless): signing use case
Browse files Browse the repository at this point in the history
Signed-off-by: Jose I. Paris <[email protected]>
  • Loading branch information
jiparis committed May 31, 2024
1 parent f81a43e commit 4737f65
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 2 deletions.
1 change: 1 addition & 0 deletions app/controlplane/internal/biz/biz.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ var ProviderSet = wire.NewSet(
NewAPITokenUseCase,
NewAPITokenSyncerUseCase,
NewAttestationStateUseCase,
NewChainloopSigningUseCase,
wire.Struct(new(NewIntegrationUseCaseOpts), "*"),
wire.Struct(new(NewUserUseCaseParams), "*"),
)
Expand Down
111 changes: 111 additions & 0 deletions app/controlplane/internal/biz/signing.go
Original file line number Diff line number Diff line change
@@ -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
}
98 changes: 98 additions & 0 deletions app/controlplane/internal/biz/signing_test.go
Original file line number Diff line number Diff line change
@@ -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
}
8 changes: 7 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -118,14 +119,17 @@ 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
github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect
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
Expand Down Expand Up @@ -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
)
Expand Down
15 changes: 14 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand All @@ -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=
Expand All @@ -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=
Expand Down Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down

0 comments on commit 4737f65

Please sign in to comment.