Skip to content

Commit

Permalink
fileca: add support for intermedate CA
Browse files Browse the repository at this point in the history
Signed-off-by: Nathan Smith <nathan@nfsmith.ca>
  • Loading branch information
nsmith5 committed Jan 8, 2022
1 parent b3cf14f commit 44ae553
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 29 deletions.
32 changes: 22 additions & 10 deletions pkg/ca/fileca/fileca.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package fileca

import (
"bytes"
"context"
"crypto"
"crypto/rand"
Expand All @@ -32,8 +33,8 @@ import (
type fileCA struct {
sync.RWMutex

cert *x509.Certificate
key crypto.Signer
certs []*x509.Certificate
key crypto.Signer
}

// NewFileCA returns a file backed certificate authority. Expects paths to a
Expand All @@ -43,7 +44,7 @@ func NewFileCA(certPath, keyPath, keyPass string, watch bool) (ca.CertificateAut
var fca fileCA

var err error
fca.cert, fca.key, err = loadKeyPair(certPath, keyPath, keyPass)
fca.certs, fca.key, err = loadKeyPair(certPath, keyPath, keyPass)
if err != nil {
return nil, err
}
Expand All @@ -68,21 +69,21 @@ func NewFileCA(certPath, keyPath, keyPass string, watch bool) (ca.CertificateAut
return &fca, err
}

func (fca *fileCA) updateX509KeyPair(cert *x509.Certificate, key crypto.Signer) {
func (fca *fileCA) updateX509KeyPair(certs []*x509.Certificate, key crypto.Signer) {
fca.Lock()
defer fca.Unlock()

// NB: We use the RWLock to unsure a reading thread can't get a mismatching
// cert / key pair by reading the attributes halfway through the update
// below.
fca.cert = cert
fca.certs = certs
fca.key = key
}

func (fca *fileCA) getX509KeyPair() (*x509.Certificate, crypto.Signer) {
fca.RLock()
defer fca.RUnlock()
return fca.cert, fca.key
return fca.certs[0], fca.key
}

// CreateCertificate issues code signing certificates
Expand All @@ -103,8 +104,19 @@ func (fca *fileCA) CreateCertificate(_ context.Context, subject *challenges.Chal
}

func (fca *fileCA) Root(ctx context.Context) ([]byte, error) {
return pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: fca.cert.Raw,
}), nil
fca.RLock()
defer fca.RUnlock()

buf := new(bytes.Buffer)
for _, cert := range fca.certs {
err := pem.Encode(buf, &pem.Block{
Type: "CERTIFICATE",
Bytes: cert.Raw,
})
if err != nil {
return nil, err
}
}

return buf.Bytes(), nil
}
33 changes: 23 additions & 10 deletions pkg/ca/fileca/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,34 @@ import (
"go.step.sm/crypto/pemutil"
)

func loadKeyPair(certPath, keyPath, keyPass string) (*x509.Certificate, crypto.Signer, error) {
func loadKeyPair(certPath, keyPath, keyPass string) ([]*x509.Certificate, crypto.Signer, error) {

var (
cert *x509.Certificate
err error
key crypto.Signer
certs []*x509.Certificate
err error
key crypto.Signer
)

// TODO: Load chain of certs (intermediates and root) instead of just one
// certificate.
cert, err = pemutil.ReadCertificate(certPath)
certs, err = pemutil.ReadCertificateBundle(certPath)
if err != nil {
return nil, nil, err
}

// Verify certificate chain
{
roots := x509.NewCertPool()
for _, cert := range certs {
roots.AddCert(cert)
}

opts := x509.VerifyOptions{
Roots: roots,
}
if _, err := certs[0].Verify(opts); err != nil {
return nil, nil, err
}
}

{
opaqueKey, err := pemutil.Read(keyPath, pemutil.WithPassword([]byte(keyPass)))
if err != nil {
Expand All @@ -55,15 +68,15 @@ func loadKeyPair(certPath, keyPath, keyPass string) (*x509.Certificate, crypto.S
}
}

if !valid(cert, key) {
if !valid(certs[0], key) {
return nil, nil, errors.New(`fileca: certificate public key and private key don't match`)
}

if !cert.IsCA {
if !certs[0].IsCA {
return nil, nil, errors.New(`fileca: certificate is not a CA`)
}

return cert, key, nil
return certs, key, nil
}

func valid(cert *x509.Certificate, key crypto.Signer) bool {
Expand Down
1 change: 1 addition & 0 deletions pkg/ca/fileca/load_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func TestValidLoadKeyPair(t *testing.T) {
"ecdsa",
"ed25519",
"rsa4096",
"intermediate",
}

for _, keypair := range keypairs {
Expand Down
6 changes: 3 additions & 3 deletions pkg/ca/fileca/watch.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,18 @@ import (
"github.com/fsnotify/fsnotify"
)

func ioWatch(certPath, keyPath, keyPass string, watcher *fsnotify.Watcher, callback func(*x509.Certificate, crypto.Signer)) {
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 {
cert, key, err := loadKeyPair(certPath, keyPath, keyPass)
certs, key, 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(cert, key)
callback(certs, key)
}
}
}
12 changes: 6 additions & 6 deletions pkg/ca/fileca/watch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,14 @@ func TestIOWatch(t *testing.T) {

// Set up callback trap
var received []struct {
cert *x509.Certificate
key crypto.Signer
certs []*x509.Certificate
key crypto.Signer
}
callback := func(cert *x509.Certificate, key crypto.Signer) {
callback := func(certs []*x509.Certificate, key crypto.Signer) {
received = append(received, struct {
cert *x509.Certificate
key crypto.Signer
}{cert, key})
certs []*x509.Certificate
key crypto.Signer
}{certs, key})
}

// Set up watcher
Expand Down

0 comments on commit 44ae553

Please sign in to comment.