Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Small ca refactor #569

Merged
merged 2 commits into from
May 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions pkg/ca/ca.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// 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 ca

import (
"context"

"github.com/sigstore/fulcio/pkg/challenges"
)

// CertificateAuthority implements certificate creation with a detached SCT and
// fetching the CA trust bundle.
type CertificateAuthority interface {
CreateCertificate(ctx context.Context, challenge *challenges.ChallengeResult) (*CodeSigningCertificate, error)
Root(ctx context.Context) ([]byte, error)
}
34 changes: 1 addition & 33 deletions pkg/ca/interface.go → pkg/ca/csc.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2021 The Sigstore Authors.
// Copyright 2022 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.
Expand All @@ -11,18 +11,13 @@
// 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 ca

import (
"context"
"crypto"
"crypto/x509"
"strings"

ct "github.com/google/certificate-transparency-go"
"github.com/sigstore/fulcio/pkg/challenges"
"github.com/sigstore/sigstore/pkg/cryptoutils"
)

Expand All @@ -33,17 +28,6 @@ type CodeSigningCertificate struct {
finalChainPEM []string
}

// CodeSigningPreCertificate holds a precertificate and chain.
type CodeSigningPreCertificate struct {
// PreCert contains the precertificate. Not a valid certificate due to a critical poison extension.
PreCert *x509.Certificate
// CertChain contains the certificate chain to verify the precertificate.
CertChain []*x509.Certificate
// PrivateKey contains the signing key used to sign the precertificate. Will be used to sign the certificate.
// Included in case the signing key is rotated in between precertificate generation and final issuance.
PrivateKey crypto.Signer
}

func CreateCSCFromPEM(cert string, chain []string) (*CodeSigningCertificate, error) {
var c CodeSigningCertificate

Expand Down Expand Up @@ -112,19 +96,3 @@ func (c *CodeSigningCertificate) ChainPEM() ([]string, error) {
}
return c.finalChainPEM, nil
}

// CertificateAuthority implements certificate creation with a detached SCT and fetching the CA trust bundle.
type CertificateAuthority interface {
CreateCertificate(ctx context.Context, challenge *challenges.ChallengeResult) (*CodeSigningCertificate, error)
Root(ctx context.Context) ([]byte, error)
}

// EmbeddedSCTCA implements precertificate and certificate issuance. Certificates will contain an embedded SCT.
type EmbeddedSCTCA interface {
CreatePrecertificate(ctx context.Context, challenge *challenges.ChallengeResult) (*CodeSigningPreCertificate, error)
IssueFinalCertificate(ctx context.Context, precert *CodeSigningPreCertificate, sct *ct.SignedCertificateTimestamp) (*CodeSigningCertificate, error)
}

// ValidationError indicates that there is an issue with the content in the HTTP Request that
// should result in an HTTP 400 Bad Request error being returned to the client
type ValidationError error
220 changes: 220 additions & 0 deletions pkg/ca/csc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
// Copyright 2022 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 ca

import (
"encoding/pem"
"strings"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/sigstore/sigstore/pkg/cryptoutils"
)

func TestCreateCSCFromDER(t *testing.T) {
tests := map[string]struct {
CertificatePEM string
ChainPEM []string
WantErr bool
}{
"Good certificate chain should parse without error": {
CertificatePEM: `-----BEGIN CERTIFICATE-----
MIICFDCCAZmgAwIBAgIUAPsd9CUVr9TNG8nRzYHJrC/ZjtowCgYIKoZIzj0EAwMw
KjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y
MjA1MTExNjI4MjRaFw0yMjA1MTExNjM4MjNaMAAwWTATBgcqhkjOPQIBBggqhkjO
PQMBBwNCAATGZIJr9odbCYVecVDp9LB1Ye9ehw7tCvPphaKQY832ftnRYFluAb6G
TtsmHqms4TXsTbvKHFJ9IxtvS6m2uJ6ao4HGMIHDMA4GA1UdDwEB/wQEAwIHgDAT
BgNVHSUEDDAKBggrBgEFBQcDAzAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBTAROUd
EzzWfUH12GTQKrm84cGngTAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQKsXF
+jAjBgNVHREBAf8EGTAXgRVuYXRoYW5AY2hhaW5ndWFyZC5kZXYwKQYKKwYBBAGD
vzABAQQbaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tMAoGCCqGSM49BAMDA2kA
MGYCMQDUnEwydrGWXaMdhE4JXvNAxAPI6iZzDFZAmqTsOyeSV1LWeFQIgrOGHQwB
ObpE85YCMQCNhS9zht0xv7j2FGuLshR3aLMTzY3UFBC3pEcI+yy4hI12MHh4laKT
yhW8MpHgDWs=
-----END CERTIFICATE-----`,
ChainPEM: []string{
`-----BEGIN CERTIFICATE-----
MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMw
KjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y
MTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3Jl
LmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7
XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxex
X69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92j
YzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRY
wB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQ
KsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCM
WP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9
TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ
-----END CERTIFICATE-----`,
},
WantErr: false,
},
}

for name, test := range tests {
t.Run(name, func(t *testing.T) {
var certDer []byte
{
buf := []byte(test.CertificatePEM)
block, _ := pem.Decode(buf)
if block == nil {
t.Fatal("bad certificate format")
}
certDer = block.Bytes
}

chainBytes := []byte(strings.Join(test.ChainPEM, ""))
chain, err := cryptoutils.UnmarshalCertificatesFromPEM(chainBytes)
if err != nil {
t.Fatal("bad ca chain format")
}

csc, err := CreateCSCFromDER(certDer, chain)
if err != nil {
if !test.WantErr {
t.Error(err)
}
return
}

gotCert, err := csc.CertPEM()
if err != nil {
t.Error(err)
}
if diff := cmp.Diff(gotCert, test.CertificatePEM); diff != "" {
t.Error(diff)
}

gotChain, err := csc.ChainPEM()
if err != nil {
t.Error(err)
}
if diff := cmp.Diff(gotChain, test.ChainPEM); diff != "" {
t.Error(diff)
}
})
}
}
func TestCreateCSCFromPEM(t *testing.T) {
tests := map[string]struct {
CertificatePEM string
ChainPEM []string
WantErr bool
}{
"Good certificate chain should parse without error": {
CertificatePEM: `-----BEGIN CERTIFICATE-----
MIICFDCCAZmgAwIBAgIUAPsd9CUVr9TNG8nRzYHJrC/ZjtowCgYIKoZIzj0EAwMw
KjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y
MjA1MTExNjI4MjRaFw0yMjA1MTExNjM4MjNaMAAwWTATBgcqhkjOPQIBBggqhkjO
PQMBBwNCAATGZIJr9odbCYVecVDp9LB1Ye9ehw7tCvPphaKQY832ftnRYFluAb6G
TtsmHqms4TXsTbvKHFJ9IxtvS6m2uJ6ao4HGMIHDMA4GA1UdDwEB/wQEAwIHgDAT
BgNVHSUEDDAKBggrBgEFBQcDAzAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBTAROUd
EzzWfUH12GTQKrm84cGngTAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQKsXF
+jAjBgNVHREBAf8EGTAXgRVuYXRoYW5AY2hhaW5ndWFyZC5kZXYwKQYKKwYBBAGD
vzABAQQbaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tMAoGCCqGSM49BAMDA2kA
MGYCMQDUnEwydrGWXaMdhE4JXvNAxAPI6iZzDFZAmqTsOyeSV1LWeFQIgrOGHQwB
ObpE85YCMQCNhS9zht0xv7j2FGuLshR3aLMTzY3UFBC3pEcI+yy4hI12MHh4laKT
yhW8MpHgDWs=
-----END CERTIFICATE-----`,
ChainPEM: []string{
`-----BEGIN CERTIFICATE-----
MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMw
KjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y
MTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3Jl
LmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7
XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxex
X69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92j
YzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRY
wB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQ
KsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCM
WP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9
TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ
-----END CERTIFICATE-----`,
},
WantErr: false,
},
"Bad leaf certificate format should error": {
CertificatePEM: `-----BEGIN CERTIFICATE-----
BOO!
-----END CERTIFICATE-----`,
ChainPEM: []string{
`-----BEGIN CERTIFICATE-----
MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMw
KjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y
MTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3Jl
LmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7
XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxex
X69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92j
YzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRY
wB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQ
KsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCM
WP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9
TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ
-----END CERTIFICATE-----`,
},
WantErr: true,
},
"Bad chain certificate format should error": {
CertificatePEM: `-----BEGIN CERTIFICATE-----
MIICFDCCAZmgAwIBAgIUAPsd9CUVr9TNG8nRzYHJrC/ZjtowCgYIKoZIzj0EAwMw
KjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y
MjA1MTExNjI4MjRaFw0yMjA1MTExNjM4MjNaMAAwWTATBgcqhkjOPQIBBggqhkjO
PQMBBwNCAATGZIJr9odbCYVecVDp9LB1Ye9ehw7tCvPphaKQY832ftnRYFluAb6G
TtsmHqms4TXsTbvKHFJ9IxtvS6m2uJ6ao4HGMIHDMA4GA1UdDwEB/wQEAwIHgDAT
BgNVHSUEDDAKBggrBgEFBQcDAzAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBTAROUd
EzzWfUH12GTQKrm84cGngTAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQKsXF
+jAjBgNVHREBAf8EGTAXgRVuYXRoYW5AY2hhaW5ndWFyZC5kZXYwKQYKKwYBBAGD
vzABAQQbaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tMAoGCCqGSM49BAMDA2kA
MGYCMQDUnEwydrGWXaMdhE4JXvNAxAPI6iZzDFZAmqTsOyeSV1LWeFQIgrOGHQwB
ObpE85YCMQCNhS9zht0xv7j2FGuLshR3aLMTzY3UFBC3pEcI+yy4hI12MHh4laKT
yhW8MpHgDWs=
-----END CERTIFICATE-----`,
ChainPEM: []string{
`-----BEGIN CERTIFICATE-----
BOO!
-----END CERTIFICATE-----`,
},
WantErr: true,
},
}

for name, test := range tests {
t.Run(name, func(t *testing.T) {
csc, err := CreateCSCFromPEM(test.CertificatePEM, test.ChainPEM)
if err != nil {
if !test.WantErr {
t.Error(err)
}
return
}

gotCert, err := csc.CertPEM()
if err != nil {
t.Error(err)
}
if diff := cmp.Diff(gotCert, test.CertificatePEM); diff != "" {
t.Error(diff)
}

gotChain, err := csc.ChainPEM()
if err != nil {
t.Error(err)
}
if diff := cmp.Diff(gotChain, test.ChainPEM); diff != "" {
t.Error(diff)
}
})
}
}
32 changes: 32 additions & 0 deletions pkg/ca/cspc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// 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 ca

import (
"crypto"
"crypto/x509"
)

// CodeSigningPreCertificate holds a precertificate and chain.
type CodeSigningPreCertificate struct {
// PreCert contains the precertificate. Not a valid certificate due to a critical poison extension.
PreCert *x509.Certificate
// CertChain contains the certificate chain to verify the precertificate.
CertChain []*x509.Certificate
// PrivateKey contains the signing key used to sign the precertificate. Will be used to sign the certificate.
// Included in case the signing key is rotated in between precertificate generation and final issuance.
PrivateKey crypto.Signer
}
29 changes: 29 additions & 0 deletions pkg/ca/embeddedca.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// 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 ca

import (
"context"

ct "github.com/google/certificate-transparency-go"
"github.com/sigstore/fulcio/pkg/challenges"
)

// EmbeddedSCTCA implements precertificate and certificate issuance. Certificates will contain an embedded SCT.
type EmbeddedSCTCA interface {
CreatePrecertificate(ctx context.Context, challenge *challenges.ChallengeResult) (*CodeSigningPreCertificate, error)
IssueFinalCertificate(ctx context.Context, precert *CodeSigningPreCertificate, sct *ct.SignedCertificateTimestamp) (*CodeSigningCertificate, error)
}
20 changes: 20 additions & 0 deletions pkg/ca/error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// 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 ca

// ValidationError indicates that there is an issue with the content in the HTTP Request that
// should result in an HTTP 400 Bad Request error being returned to the client
type ValidationError error