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

Add interface for certs/signer fetching to remove mutex #643

Merged
merged 1 commit into from
Jun 15, 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
13 changes: 7 additions & 6 deletions pkg/ca/fileca/fileca.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func NewFileCA(certPath, keyPath, keyPass string, watch bool) (ca.CertificateAut
var fca fileCA

var err error
fca.Certs, fca.Signer, err = loadKeyPair(certPath, keyPath, keyPass)
fca.SignerWithChain, err = loadKeyPair(certPath, keyPath, keyPass)
if err != nil {
return nil, err
}
Expand All @@ -61,12 +61,13 @@ func NewFileCA(certPath, keyPath, keyPass string, watch bool) (ca.CertificateAut
}

func (fca *fileCA) updateX509KeyPair(certs []*x509.Certificate, signer crypto.Signer) {
fca.Lock()
defer fca.Unlock()
scm := fca.SignerWithChain.(*ca.SignerCertsMutex)
scm.Lock()
defer scm.Unlock()

// NB: We use the RWLock to unsure a reading thread can't get a mismatching
// NB: We use a lock to ensure a reading thread can't get a mismatching
// cert / key pair by reading the attributes halfway through the update
// below.
fca.Certs = certs
fca.Signer = signer
scm.Certs = certs
scm.Signer = signer
}
9 changes: 5 additions & 4 deletions pkg/ca/fileca/fileca_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,19 @@ func TestCertUpdate(t *testing.T) {
t.Fatal(`Bad CA type`)
}

key := fca.Signer
_, key := fca.GetSignerWithChain()

if _, ok = key.(ed25519.PrivateKey); !ok {
t.Error(`first key should have been an ed25519 key`)
}

cert, key, err := loadKeyPair(newCert, newKey, testKeyPass)
signerWithMutex, err := loadKeyPair(newCert, newKey, testKeyPass)
if err != nil {
t.Fatal(`Failed to load new keypair`)
}

fca.updateX509KeyPair(cert, key)
key = fca.Signer
fca.updateX509KeyPair(signerWithMutex.Certs, signerWithMutex.Signer)
_, key = fca.GetSignerWithChain()

if _, ok = key.(*ecdsa.PrivateKey); !ok {
t.Fatal(`file CA should have been updated with ecdsa key`)
Expand Down
15 changes: 8 additions & 7 deletions pkg/ca/fileca/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,13 @@ import (
"os"
"path/filepath"

"github.com/sigstore/fulcio/pkg/ca"
"github.com/sigstore/fulcio/pkg/ca/intermediateca"
"github.com/sigstore/sigstore/pkg/cryptoutils"
"go.step.sm/crypto/pemutil"
)

func loadKeyPair(certPath, keyPath, keyPass string) ([]*x509.Certificate, crypto.Signer, error) {
func loadKeyPair(certPath, keyPath, keyPass string) (*ca.SignerCertsMutex, error) {
var (
certs []*x509.Certificate
err error
Expand All @@ -37,29 +38,29 @@ func loadKeyPair(certPath, keyPath, keyPass string) ([]*x509.Certificate, crypto

data, err := os.ReadFile(filepath.Clean(certPath))
if err != nil {
return nil, nil, err
return nil, err
}
certs, err = cryptoutils.LoadCertificatesFromPEM(bytes.NewReader(data))
if err != nil {
return nil, nil, err
return nil, err
}

{
opaqueKey, err := pemutil.Read(keyPath, pemutil.WithPassword([]byte(keyPass)))
if err != nil {
return nil, nil, err
return nil, err
}

var ok bool
key, ok = opaqueKey.(crypto.Signer)
if !ok {
return nil, nil, errors.New(`fileca: loaded private key can't be used to sign`)
return nil, errors.New(`fileca: loaded private key can't be used to sign`)
}
}

if err := intermediateca.VerifyCertChain(certs, key); err != nil {
return nil, nil, err
return nil, err
}

return certs, key, nil
return &ca.SignerCertsMutex{Certs: certs, Signer: key}, nil
}
6 changes: 3 additions & 3 deletions pkg/ca/fileca/load_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ func TestValidLoadKeyPair(t *testing.T) {
keyPath := fmt.Sprintf("testdata/%s-key.pem", keypair)
certPath := fmt.Sprintf("testdata/%s-cert.pem", keypair)

_, _, err := loadKeyPair(certPath, keyPath, testKeyPass)
_, err := loadKeyPair(certPath, keyPath, testKeyPass)
if err != nil {
t.Errorf("Failed to load key pair of type %s", keypair)
t.Errorf("Failed to load key pair of type %s: %v", keypair, err)
}
}
}
Expand All @@ -52,7 +52,7 @@ func TestInvalidLoadKeyPair(t *testing.T) {
keyPath := fmt.Sprintf("testdata/%s-key.pem", keypair)
certPath := fmt.Sprintf("testdata/%s-cert.pem", keypair)

_, _, err := loadKeyPair(certPath, keyPath, testKeyPass)
_, err := loadKeyPair(certPath, keyPath, testKeyPass)
if err == nil {
t.Errorf("Expected invalid key pair of type %s to fail to load", keypair)
}
Expand Down
15 changes: 7 additions & 8 deletions pkg/ca/fileca/testdata/openssl-cert.pem
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
-----BEGIN CERTIFICATE-----
MIIBTzCCAQGgAwIBAgIUBO1MTUDAijUkpFiDr53xJJmPxLAwBQYDK2VwMBIxEDAO
BgNVBAMMB29wZW5zc2wwIBcNMjIwMTE4MjAzOTU1WhgPMjEyMTEyMjUyMDM5NTVa
MBIxEDAOBgNVBAMMB29wZW5zc2wwKjAFBgMrZXADIQBCrnxleddPY3TkNV8DtEWJ
9Avw/w17xtYhgVqzBOdOTaNnMGUwHQYDVR0OBBYEFA9ssW0mYnwHXMmdlADKaJaY
Ie7TMB8GA1UdIwQYMBaAFA9ssW0mYnwHXMmdlADKaJaYIe7TMA8GA1UdEwEB/wQF
MAMBAf8wEgYDVR0TAQH/BAgwBgEB/wIBATAFBgMrZXADQQCo2r09KAdu78YhQF1m
SVgxm1jgsoA/tAjKmzLi6sAuOV57WDd1vmMpu1ggqeaeQhzXNH4Qm76c+XUPBY2n
fkUB
MIIBPTCB8KADAgECAhQOFCGq1F/UFACHhW2z51EE+dodDTAFBgMrZXAwEjEQMA4G
A1UEAwwHb3BlbnNzbDAgFw0yMjA2MTAwNDUzMzZaGA8yMTIyMDUxNzA0NTMzNlow
EjEQMA4GA1UEAwwHb3BlbnNzbDAqMAUGAytlcAMhAHoP6VgvmjkF7TQktmsqA2WD
FKuEus/Uf1IV+heG91lQo1YwVDAdBgNVHQ4EFgQUZx7Tvdvg0FtL4NwBHyq+vEdA
KUswHwYDVR0jBBgwFoAUZx7Tvdvg0FtL4NwBHyq+vEdAKUswEgYDVR0TAQH/BAgw
BgEB/wIBATAFBgMrZXADQQDWdhDWFYcX5dDmHuPi3MpgX5lyR+7yOA5keUVWQU8U
62DPFRRsfcpmWELx/RNQD/OgdIUZ9/YTPgFBoTngeD4G
-----END CERTIFICATE-----
6 changes: 3 additions & 3 deletions pkg/ca/fileca/testdata/openssl-key.pem
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIGKME4GCSqGSIb3DQEFDTBBMCkGCSqGSIb3DQEFDDAcBAifqugsEv+5eQICCAAw
DAYIKoZIhvcNAgkFADAUBggqhkiG9w0DBwQIsETz73+d0CkEOEfccSS2FSSCYchJ
61nyishJ4/AFxpsG5935bt+UvfcaUALH1RKQkwNvoGbry6afYY+qvW+LNy6g
MIGKME4GCSqGSIb3DQEFDTBBMCkGCSqGSIb3DQEFDDAcBAjrbQwLQsaVYgICCAAw
DAYIKoZIhvcNAgkFADAUBggqhkiG9w0DBwQIo5IbVAAU0f8EOEZix/CL4zM7FCfN
RlNM1GD99ZEouGO5jEae7q0medijaG1IbD4y4B90nLuQfc4aDlIMG5AyA8wP
-----END ENCRYPTED PRIVATE KEY-----
4 changes: 2 additions & 2 deletions pkg/ca/fileca/watch.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ import (
func ioWatch(certPath, keyPath, keyPass string, watcher *fsnotify.Watcher, callback func([]*x509.Certificate, crypto.Signer)) {
for event := range watcher.Events {
if event.Op&fsnotify.Write == fsnotify.Write {
certs, key, err := loadKeyPair(certPath, keyPath, keyPass)
signerWithMutex, err := loadKeyPair(certPath, keyPath, keyPass)
if err != nil {
// Don't sweat it if this errors out. One file might
// have updated and the other isn't causing a key-pair
// mismatch
continue
}

callback(certs, key)
callback(signerWithMutex.Certs, signerWithMutex.Signer)
}
}
}
25 changes: 6 additions & 19 deletions pkg/ca/intermediateca/intermediateca.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
"crypto/x509/pkix"
"encoding/asn1"
"errors"
"sync"

ct "github.com/google/certificate-transparency-go"
cttls "github.com/google/certificate-transparency-go/tls"
Expand All @@ -42,11 +41,8 @@ var (
)

type IntermediateCA struct {
sync.RWMutex

// certs is a chain of certificates from intermediate to root
Certs []*x509.Certificate
Signer crypto.Signer
// contains the chain of certificates and signer
ca.SignerWithChain
}

func (ica *IntermediateCA) CreatePrecertificate(ctx context.Context, principal identity.Principal, publicKey crypto.PublicKey) (*ca.CodeSigningPreCertificate, error) {
Expand All @@ -55,7 +51,7 @@ func (ica *IntermediateCA) CreatePrecertificate(ctx context.Context, principal i
return nil, err
}

certChain, privateKey := ica.getX509KeyPair()
certChain, privateKey := ica.GetSignerWithChain()

// Append poison extension
cert.ExtraExtensions = append(cert.ExtraExtensions, pkix.Extension{
Expand Down Expand Up @@ -136,7 +132,7 @@ func (ica *IntermediateCA) CreateCertificate(ctx context.Context, principal iden
return nil, err
}

certChain, privateKey := ica.getX509KeyPair()
certChain, privateKey := ica.GetSignerWithChain()

finalCertBytes, err := x509.CreateCertificate(rand.Reader, cert, certChain[0], publicKey, privateKey)
if err != nil {
Expand All @@ -147,17 +143,8 @@ func (ica *IntermediateCA) CreateCertificate(ctx context.Context, principal iden
}

func (ica *IntermediateCA) Root(ctx context.Context) ([]byte, error) {
ica.RLock()
defer ica.RUnlock()

return cryptoutils.MarshalCertificatesToPEM(ica.Certs)
}

func (ica *IntermediateCA) getX509KeyPair() ([]*x509.Certificate, crypto.Signer) {
ica.RLock()
defer ica.RUnlock()

return ica.Certs, ica.Signer
certs, _ := ica.GetSignerWithChain()
return cryptoutils.MarshalCertificatesToPEM(certs)
}

func VerifyCertChain(certs []*x509.Certificate, signer crypto.Signer) error {
Expand Down
15 changes: 8 additions & 7 deletions pkg/ca/intermediateca/intermediateca_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"testing"

ct "github.com/google/certificate-transparency-go"
"github.com/sigstore/fulcio/pkg/ca"
"github.com/sigstore/fulcio/pkg/ca/x509ca"
"github.com/sigstore/fulcio/pkg/test"
"github.com/sigstore/sigstore/pkg/cryptoutils"
Expand All @@ -48,8 +49,7 @@ func TestIntermediateCARoot(t *testing.T) {
}

ica := IntermediateCA{
Certs: certChain,
Signer: signer,
SignerWithChain: &ca.SignerCerts{Certs: certChain, Signer: signer},
}

rootBytes, err := ica.Root(context.TODO())
Expand All @@ -62,7 +62,7 @@ func TestIntermediateCARoot(t *testing.T) {
}
}

func TestIntermediateCAGetX509KeyPair(t *testing.T) {
func TestIntermediateCAGetSignerWithChain(t *testing.T) {
signer, _, err := signature.NewDefaultECDSASignerVerifier()
if err != nil {
t.Fatalf("unexpected error generating signer: %v", err)
Expand All @@ -73,11 +73,10 @@ func TestIntermediateCAGetX509KeyPair(t *testing.T) {
certChain := []*x509.Certificate{subCert, rootCert}

ica := IntermediateCA{
Certs: certChain,
Signer: signer,
SignerWithChain: &ca.SignerCerts{Certs: certChain, Signer: signer},
}

foundCertChain, foundSigner := ica.getX509KeyPair()
foundCertChain, foundSigner := ica.GetSignerWithChain()

if !reflect.DeepEqual(certChain, foundCertChain) {
t.Fatal("expected cert chains to be equivalent")
Expand Down Expand Up @@ -177,7 +176,9 @@ func TestCreatePrecertificateAndIssueFinalCertificate(t *testing.T) {
priv, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
certChain := []*x509.Certificate{subCert, rootCert}

ica := IntermediateCA{Certs: certChain, Signer: subKey}
ica := IntermediateCA{
SignerWithChain: &ca.SignerCerts{Certs: certChain, Signer: subKey},
}

precsc, err := ica.CreatePrecertificate(context.TODO(), testPrincipal{}, priv.Public())

Expand Down
10 changes: 7 additions & 3 deletions pkg/ca/kmsca/kmsca.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,21 @@ func NewKMSCA(ctx context.Context, kmsKey, certPath string) (ca.CertificateAutho
if err != nil {
return nil, err
}
ica.Signer = signer

sc := ca.SignerCerts{}
ica.SignerWithChain = &sc

sc.Signer = signer

data, err := os.ReadFile(filepath.Clean(certPath))
if err != nil {
return nil, err
}
ica.Certs, err = cryptoutils.LoadCertificatesFromPEM(bytes.NewReader(data))
sc.Certs, err = cryptoutils.LoadCertificatesFromPEM(bytes.NewReader(data))
if err != nil {
return nil, err
}
if err := intermediateca.VerifyCertChain(ica.Certs, ica.Signer); err != nil {
if err := intermediateca.VerifyCertChain(sc.Certs, sc.Signer); err != nil {
return nil, err
}

Expand Down
7 changes: 4 additions & 3 deletions pkg/ca/kmsca/kmsca_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,12 @@ func TestNewKMSCA(t *testing.T) {

// Expect signer and certificate's public keys match
ica := ca.(*kmsCA)
if err := cryptoutils.EqualKeys(ica.Signer.Public(), subKey.Public()); err != nil {
certs, signer := ica.GetSignerWithChain()
if err := cryptoutils.EqualKeys(signer.Public(), subKey.Public()); err != nil {
t.Fatalf("keys between CA and signer do not match: %v", err)
}
if !reflect.DeepEqual(ica.Certs, []*x509.Certificate{subCert, rootCert}) {
t.Fatalf("expected certificate chains to match: %v", err)
if !reflect.DeepEqual(certs, []*x509.Certificate{subCert, rootCert}) {
t.Fatalf("expected certificate chains to match")
}

// Failure: Mismatch between signer and certificate key
Expand Down
58 changes: 58 additions & 0 deletions pkg/ca/signercerts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// 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 (
"crypto"
"crypto/x509"
"sync"
)

// SignerWithChain provides a getter for a CA's certificate chain and signing key.
type SignerWithChain interface {
GetSignerWithChain() ([]*x509.Certificate, crypto.Signer)
}

// SignerCerts holds a certificate chain and signer.
type SignerCerts struct {
// Signer signs issued certificates
Signer crypto.Signer
// Certs contains the chain of certificates from intermediate to root
Certs []*x509.Certificate
}

func (s *SignerCerts) GetSignerWithChain() ([]*x509.Certificate, crypto.Signer) {
return s.Certs, s.Signer
}

// SignerCertsMutex holds a certificate chain and signer, and holds a reader lock
// when accessing the chain and signer. Use if a separate thread can concurrently
// update the chain and signer.
type SignerCertsMutex struct {
sync.RWMutex

// Certs contains the chain of certificates from intermediate to root
Certs []*x509.Certificate
// Signer signs issued certificates
Signer crypto.Signer
}

func (s *SignerCertsMutex) GetSignerWithChain() ([]*x509.Certificate, crypto.Signer) {
s.RLock()
defer s.RUnlock()

return s.Certs, s.Signer
}
Loading