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 15, 2022
1 parent 12a9942 commit 11a37a2
Show file tree
Hide file tree
Showing 23 changed files with 630 additions and 299 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
}
65 changes: 52 additions & 13 deletions pkg/ca/fileca/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,64 @@ 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)
// NB: certs are ordered from leaf at certs[0] to root at
// certs[len(certs)-1]
certs, err = pemutil.ReadCertificateBundle(certPath)
if err != nil {
return nil, nil, err
}

// Verify certificate chain
{
roots := x509.NewCertPool()
roots.AddCert(certs[len(certs)-1])

intermediates := x509.NewCertPool()
if len(certs) > 2 {
for _, intermediate := range certs[1 : len(certs)-1] {
intermediates.AddCert(intermediate)
}
}

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

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

// If using an intermediate, verify that code signing extended key
// usage is set to satify extended key usage chainging
if len(certs) > 1 {
var hasExtKeyUsageCodeSigning bool
for _, extKeyUsage := range certs[0].ExtKeyUsage {
if extKeyUsage == x509.ExtKeyUsageCodeSigning {
hasExtKeyUsageCodeSigning = true
break
}
}
if !hasExtKeyUsageCodeSigning {
return nil, nil, errors.New(`fileca: certificate must have extended key usage code signing set to sign code signing certificates`)
}
}
}

{
opaqueKey, err := pemutil.Read(keyPath, pemutil.WithPassword([]byte(keyPass)))
if err != nil {
Expand All @@ -55,15 +98,11 @@ 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 {
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
3 changes: 3 additions & 0 deletions pkg/ca/fileca/load_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ func TestValidLoadKeyPair(t *testing.T) {
"ecdsa",
"ed25519",
"rsa4096",
"openssl",
"intermediate",
}

for _, keypair := range keypairs {
Expand All @@ -42,6 +44,7 @@ func TestInvalidLoadKeyPair(t *testing.T) {
keypairs := []string{
"notca",
"mismatch",
"eku-chaining-violation",
}

for _, keypair := range keypairs {
Expand Down
19 changes: 9 additions & 10 deletions pkg/ca/fileca/testdata/ecdsa-cert.pem
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
-----BEGIN CERTIFICATE-----
MIIBxzCCAU6gAwIBAgIUbQqE8rDPWDqJexmvpaeamgZe/HIwCgYIKoZIzj0EAwIw
EDEOMAwGA1UEAwwFZWNkc2EwIBcNMjExMjIxMTkxMzI2WhgPMjEyMTExMjcxOTEz
MjZaMBAxDjAMBgNVBAMMBWVjZHNhMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEMV9i
0e3Ld1eQy9UXII5MOymw2IFBo288zuOMeH+7w0ejJlY0PFowY4rItKIhqRIWOqFA
luSaVC59sKsqjsiLdQQW2CV19eYFhVvYQS1S2QaROpFA5Zt8ALOACyp5s+6+o2cw
ZTAdBgNVHQ4EFgQU3QF9mKDrefmeiE3lqC46PSmhEOkwHwYDVR0jBBgwFoAU3QF9
mKDrefmeiE3lqC46PSmhEOkwDwYDVR0TAQH/BAUwAwEB/zASBgNVHRMBAf8ECDAG
AQH/AgEBMAoGCCqGSM49BAMCA2cAMGQCMAQ/g18eRvqITDZEKdzf4bI4qKF/ZbVL
GTZ+2HHZYwDvsuHeznTl1Uq1stzmySi4owIwV1jCF8f4gikxT0XCF+u1CJlVYiZP
tyRnLdZaKl/seNUmBO0RRR72tsRd/X1QR3NK
MIIBojCCASigAwIBAgIQdN97RoNpSe/j6zMPBRiEjTAKBggqhkjOPQQDAzAQMQ4w
DAYDVQQDEwVlY2RzYTAgFw0yMjAxMTUyMTM5NDZaGA8yMTIxMTIyMjIxMzk0Nlow
EDEOMAwGA1UEAxMFZWNkc2EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQkL81PCeoj
XHCTnkv5DtkFiTR14i9ud+xMTCURLDlxBXk1fBywg1o3P6vHzYZpyEVRligoxELH
cPyY1xx4B0es6PJKlN95WoLYZXW10Y5R1aahTxVYSv+kEztt8Syp3zGjRTBDMA4G
A1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBS+Jr+X
Ci1VSjK6/sMMTsQcX6K1MDAKBggqhkjOPQQDAwNoADBlAjEA8swYU8cQcmTLa7Y7
/yyzpQ6Z+Xwts5d2U9jxvsUchietQfMAjTTZfqF/WZRMLRvwAjBciWw9cL9PUzG3
1RD+fH+Z8RKcxs2A1NboUcfVEiYbQ3XSZlDX0GhjguI4xgdL6DY=
-----END CERTIFICATE-----
17 changes: 9 additions & 8 deletions pkg/ca/fileca/testdata/ecdsa-key.pem
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIBEzBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIGCeFiJ2rs3wCAggA
MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECGZQSOBsvcjqBIHAGdU9wj136ikM
QUqJqDt5oSDKJAU2Yrv+pCRLz6VENGUvxFrPsn9fbjSt69fSZMm09mITfEg1eLO4
9LDl7PW1Bza803IXKQnZ0xnRUkkY1GQfDtQEGCYFaojRpYNLmmSiHpeFrqAPz83K
+oXsRTwuRkDrABNpwTCEXYVmcHUmk9NqC6E2qjmYOyDx0ktA4HG62H3/cpGZleBs
l58oyOg658erxF1rnASN15lw9/1g0lWACsXsMgbkjDnY51LU71pR
-----END ENCRYPTED PRIVATE KEY-----
-----BEGIN EC PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-256-CBC,4a2a1826bd853f1c2c0117902551c0a9

Flk9LwvoP6yjUQQlr57AMLN2NN6VKV1hnPQ8art2O2aXidLqwgi2ZcpfIuCGuAJn
zcOXJAiXmkvOzWnXkDRW5xmzsuwVp1RzwkF/DbmleO+Bw4SdylOB4OJj2Pu0huv2
hH+xcym5XdsclLn8gOZZBH01i23oH+O/poHE9JjxnxWX0wALovghbvGMoPiz9unn
JVH9OewvTL8JINsr6vmDMyOX0D+GIAvYmTF518U+ykA=
-----END EC PRIVATE KEY-----
15 changes: 7 additions & 8 deletions pkg/ca/fileca/testdata/ed25519-cert.pem
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
-----BEGIN CERTIFICATE-----
MIIBTzCCAQGgAwIBAgIUeObF4LopbObr0zVOX7BAZbvy4MswBQYDK2VwMBIxEDAO
BgNVBAMMB2VkMjU1MTkwIBcNMjExMjIxMTkxMzI2WhgPMjEyMTExMjcxOTEzMjZa
MBIxEDAOBgNVBAMMB2VkMjU1MTkwKjAFBgMrZXADIQBNNJP9Ys+Sx0Cx/c5pQNAF
cuECdESA0vB2IqXVAG5OiaNnMGUwHQYDVR0OBBYEFJEGm0OzRNsdBVLdDBCcx21i
nEySMB8GA1UdIwQYMBaAFJEGm0OzRNsdBVLdDBCcx21inEySMA8GA1UdEwEB/wQF
MAMBAf8wEgYDVR0TAQH/BAgwBgEB/wIBATAFBgMrZXADQQD6quk/tnnZpFgabR2Q
4WCweJfZ4NfrhMOVvAPdECW/P57NH0P2BUSOK+/DktOBFIjLUWG6ptRExHDcRsFm
WTsA
MIIBKTCB3KADAgECAhEAlbs/LTFVoOGgJ4IoVq5VbDAFBgMrZXAwEjEQMA4GA1UE
AxMHZWQyNTUxOTAgFw0yMjAxMTUyMTM5NDRaGA8yMTIxMTIyMjIxMzk0NFowEjEQ
MA4GA1UEAxMHZWQyNTUxOTAqMAUGAytlcAMhACrmjkFJH7p3ZV3xJhXAVZtUK4Wb
8SQI3xxkYf+zjdEzo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB
/wIBATAdBgNVHQ4EFgQUgjQqhKp/TfTOGOMJUUqd5j/SWRgwBQYDK2VwA0EAFJtY
O7Xb2hUvJOtimrx1Ag3l+aDcgSA8DHT+ibqInGAVhUghkyEmbWv055FQSiIX46Zt
lo9wHPvcSE7LMoa8AA==
-----END CERTIFICATE-----
7 changes: 4 additions & 3 deletions pkg/ca/fileca/testdata/ed25519-key.pem
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIGKME4GCSqGSIb3DQEFDTBBMCkGCSqGSIb3DQEFDDAcBAggWEDSFylYswICCAAw
DAYIKoZIhvcNAgkFADAUBggqhkiG9w0DBwQIaecFy/8IbAYEOHb3xUdAVad3ZcXk
dkwJjtPNP2t2PA/6ngVgfsx2dgqKBhjg9JXG98Yw2eeYqJsbZ4jrHAJK0l8E
MIGkMGAGCSqGSIb3DQEFDTBTMDIGCSqGSIb3DQEFDDAlBBC5ebV8jVzdeumTeg0K
1YR4AgMBhqAwDAYIKoZIhvcNAgkAADAdBglghkgBZQMEASoEEIN6WmojJPzZuNxj
mmET4yYEQGSmy/JMfleTGL9FHT/f6dvV2xcHAJ2aFaNy8/JPLbcV9QgHAc2ze2+e
CPumY7wCCcdvRsMXgxFSsivigqhiPIE=
-----END ENCRYPTED PRIVATE KEY-----
31 changes: 31 additions & 0 deletions pkg/ca/fileca/testdata/eku-chaining-violation-cert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
-----BEGIN CERTIFICATE-----
MIIBhDCCATagAwIBAgIRAJb66/TzcjqBXH201cLeUH8wBQYDK2VwMBgxFjAUBgNV
BAMTDWludGVybWVkaWF0ZTEwHhcNMjIwMTE1MjEzOTU1WhcNMjIwMTE2MjEzOTU1
WjAYMRYwFAYDVQQDEw1pbnRlcm1lZGlhdGUyMFkwEwYHKoZIzj0CAQYIKoZIzj0D
AQcDQgAEAJm0AEXqLVYA/iodsse5xVZTRs/gKtCbIFDAxN9bT2fGRod9HvgD+Gb/
H62ifOtbV7sQV/c3ZB669V5TRsryrqNmMGQwDgYDVR0PAQH/BAQDAgEGMBIGA1Ud
EwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFPfWO2KNhaRmhTGDYSGHqo9uNs1gMB8G
A1UdIwQYMBaAFAWos1G3TegCEAXva/JEqmnR9ziLMAUGAytlcANBAO3EIzuXOk6Q
77GtvF8B4t/LU6ezC7DJjxwDDQAXpAAYnWbftf15pOYBfulcjEd70yFc1QA1VdtV
V1YXsggiLAI=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIBcjCCARegAwIBAgIQJFdlkkJeEz/Nx4qDHVBpOTAKBggqhkjOPQQDAjAPMQ0w
CwYDVQQDEwRyb290MB4XDTIyMDExNTIxMzk1NVoXDTIyMDExNjIxMzk1NVowGDEW
MBQGA1UEAxMNaW50ZXJtZWRpYXRlMTAqMAUGAytlcAMhAPKV6IWCMdQ/n1VcdgSh
lC5MH/m7uilfelOMN9tDpYUZo3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAww
CgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUBaizUbdN
6AIQBe9r8kSqadH3OIswHwYDVR0jBBgwFoAUpzPc0nDf/4hdxMCkLvk6Y9KsJJQw
CgYIKoZIzj0EAwIDSQAwRgIhAJM0z+Z8fTtHz0M4nDaCDJsKRLCpfoSZ/i8TtD/I
J65qAiEA6UudGaQGnEPQjW4QSiFJkD/E2vHxAu0SkSxV1UjGv30=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIBYTCCAQagAwIBAgIPSynyoAutDD5fyCjqMj5xMAoGCCqGSM49BAMCMA8xDTAL
BgNVBAMTBHJvb3QwHhcNMjIwMTE1MjEzOTU1WhcNMjIwMTE2MjEzOTU1WjAPMQ0w
CwYDVQQDEwRyb290MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEOAKo0hjeKCzW
q3rW/g30/42ltirz39aKQElOe83sgJ8N63t3j7yvhSQ+5puIE83Q9Vb98fteVe+y
Izy9RoKyI6NFMEMwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQIw
HQYDVR0OBBYEFKcz3NJw3/+IXcTApC75OmPSrCSUMAoGCCqGSM49BAMCA0kAMEYC
IQCifL/yAa+bBBc04p5/nrp0riyVQTWT4l9YzWYZCTHrjAIhALemgiIT1bV3hRgN
iHHAj9HfVuFpQwTfwtlOyo5wvL5o
-----END CERTIFICATE-----
8 changes: 8 additions & 0 deletions pkg/ca/fileca/testdata/eku-chaining-violation-key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-----BEGIN EC PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-256-CBC,226887dd5435da96ca6b5563dc3a3a04

9Rb2H57DxtFouMfwLSJw1upKHnD44xorC1fj2CttqT3kYv7MX+O+8WqzDZHeY4Vh
DiWEhXt89FnH+JOkGY8t0WnrbIHxwzVlkA8Y8vaaC+hf2V8gYJJP4uVdeZAqQH6W
/ywHRU6k0Ok2W+nQcT+X/nupMN6gBf/4+dptYlwNPsk=
-----END EC PRIVATE KEY-----
Loading

0 comments on commit 11a37a2

Please sign in to comment.