Skip to content

Commit e7153a9

Browse files
committed
caddypki: Add support for multiple intermediates in signing chain
1 parent 092913a commit e7153a9

File tree

8 files changed

+538
-31
lines changed

8 files changed

+538
-31
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ require (
3434
go.opentelemetry.io/otel v1.31.0
3535
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0
3636
go.opentelemetry.io/otel/sdk v1.31.0
37+
go.step.sm/crypto v0.45.0
3738
go.uber.org/automaxprocs v1.6.0
3839
go.uber.org/zap v1.27.0
3940
go.uber.org/zap/exp v0.3.0
@@ -145,7 +146,6 @@ require (
145146
go.opentelemetry.io/otel/trace v1.31.0
146147
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
147148
go.step.sm/cli-utils v0.9.0 // indirect
148-
go.step.sm/crypto v0.45.0
149149
go.step.sm/linkedca v0.20.1 // indirect
150150
go.uber.org/multierr v1.11.0 // indirect
151151
golang.org/x/mod v0.24.0 // indirect

modules/caddypki/adminapi.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -222,10 +222,15 @@ func rootAndIntermediatePEM(ca *CA) (root, inter []byte, err error) {
222222
if err != nil {
223223
return
224224
}
225-
inter, err = pemEncodeCert(ca.IntermediateCertificate().Raw)
226-
if err != nil {
227-
return
225+
226+
for _, interCert := range ca.IntermediateCertificateChain() {
227+
pemBytes, err := pemEncodeCert(interCert.Raw)
228+
if err != nil {
229+
return nil, nil, err
230+
}
231+
inter = append(inter, pemBytes...)
228232
}
233+
229234
return
230235
}
231236

modules/caddypki/ca.go

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,11 @@ type CA struct {
7575
// and module provisioning.
7676
ID string `json:"-"`
7777

78-
storage certmagic.Storage
79-
root, inter *x509.Certificate
80-
interKey any // TODO: should we just store these as crypto.Signer?
81-
mu *sync.RWMutex
78+
storage certmagic.Storage
79+
root *x509.Certificate
80+
interChain []*x509.Certificate
81+
interKey any // TODO: should we just store these as crypto.Signer?
82+
mu *sync.RWMutex
8283

8384
rootCertPath string // mainly used for logging purposes if trusting
8485
log *zap.Logger
@@ -129,14 +130,16 @@ func (ca *CA) Provision(ctx caddy.Context, id string, log *zap.Logger) error {
129130
}
130131

131132
// load the certs and key that will be used for signing
132-
var rootCert, interCert *x509.Certificate
133+
var rootCert *x509.Certificate
134+
var rootCertChain, interCertChain []*x509.Certificate
133135
var rootKey, interKey crypto.Signer
134136
var err error
135137
if ca.Root != nil {
136138
if ca.Root.Format == "" || ca.Root.Format == "pem_file" {
137139
ca.rootCertPath = ca.Root.Certificate
138140
}
139-
rootCert, rootKey, err = ca.Root.Load()
141+
rootCertChain, rootKey, err = ca.Root.Load()
142+
rootCert = rootCertChain[0]
140143
} else {
141144
ca.rootCertPath = "storage:" + ca.storageKeyRootCert()
142145
rootCert, rootKey, err = ca.loadOrGenRoot()
@@ -145,16 +148,16 @@ func (ca *CA) Provision(ctx caddy.Context, id string, log *zap.Logger) error {
145148
return err
146149
}
147150
if ca.Intermediate != nil {
148-
interCert, interKey, err = ca.Intermediate.Load()
151+
interCertChain, interKey, err = ca.Intermediate.Load()
149152
} else {
150-
interCert, interKey, err = ca.loadOrGenIntermediate(rootCert, rootKey)
153+
interCertChain, interKey, err = ca.loadOrGenIntermediate(rootCert, rootKey)
151154
}
152155
if err != nil {
153156
return err
154157
}
155158

156159
ca.mu.Lock()
157-
ca.root, ca.inter, ca.interKey = rootCert, interCert, interKey
160+
ca.root, ca.interChain, ca.interKey = rootCert, interCertChain, interKey
158161
ca.mu.Unlock()
159162

160163
return nil
@@ -180,7 +183,15 @@ func (ca CA) RootKey() (any, error) {
180183
func (ca CA) IntermediateCertificate() *x509.Certificate {
181184
ca.mu.RLock()
182185
defer ca.mu.RUnlock()
183-
return ca.inter
186+
return ca.interChain[0]
187+
}
188+
189+
// IntermediateCertificateChain returns the CA's intermediate
190+
// certificate chain.
191+
func (ca CA) IntermediateCertificateChain() []*x509.Certificate {
192+
ca.mu.RLock()
193+
defer ca.mu.RUnlock()
194+
return ca.interChain
184195
}
185196

186197
// IntermediateKey returns the CA's intermediate private key.
@@ -218,13 +229,14 @@ func (ca *CA) NewAuthority(authorityConfig AuthorityConfig) (*authority.Authorit
218229
// sure it's always fresh, because the intermediate may
219230
// renew while Caddy is running (medium lifetime)
220231
signerOption = authority.WithX509SignerFunc(func() ([]*x509.Certificate, crypto.Signer, error) {
221-
issuerCert := ca.IntermediateCertificate()
232+
issuerChain := ca.IntermediateCertificateChain()
233+
issuerCert := issuerChain[0]
222234
issuerKey := ca.IntermediateKey().(crypto.Signer)
223235
ca.log.Debug("using intermediate signer",
224236
zap.String("serial", issuerCert.SerialNumber.String()),
225237
zap.String("not_before", issuerCert.NotBefore.String()),
226238
zap.String("not_after", issuerCert.NotAfter.String()))
227-
return []*x509.Certificate{issuerCert}, issuerKey, nil
239+
return issuerChain, issuerKey, nil
228240
})
229241
}
230242

@@ -250,7 +262,11 @@ func (ca *CA) NewAuthority(authorityConfig AuthorityConfig) (*authority.Authorit
250262

251263
func (ca CA) loadOrGenRoot() (rootCert *x509.Certificate, rootKey crypto.Signer, err error) {
252264
if ca.Root != nil {
253-
return ca.Root.Load()
265+
rootChain, rootSigner, err := ca.Root.Load()
266+
if err != nil {
267+
return nil, nil, err
268+
}
269+
return rootChain[0], rootSigner, nil
254270
}
255271
rootCertPEM, err := ca.storage.Load(ca.ctx, ca.storageKeyRootCert())
256272
if err != nil {
@@ -312,7 +328,8 @@ func (ca CA) genRoot() (rootCert *x509.Certificate, rootKey crypto.Signer, err e
312328
return rootCert, rootKey, nil
313329
}
314330

315-
func (ca CA) loadOrGenIntermediate(rootCert *x509.Certificate, rootKey crypto.Signer) (interCert *x509.Certificate, interKey crypto.Signer, err error) {
331+
func (ca CA) loadOrGenIntermediate(rootCert *x509.Certificate, rootKey crypto.Signer) (interCertChain []*x509.Certificate, interKey crypto.Signer, err error) {
332+
var interCert *x509.Certificate
316333
interCertPEM, err := ca.storage.Load(ca.ctx, ca.storageKeyIntermediateCert())
317334
if err != nil {
318335
if !errors.Is(err, fs.ErrNotExist) {
@@ -324,10 +341,12 @@ func (ca CA) loadOrGenIntermediate(rootCert *x509.Certificate, rootKey crypto.Si
324341
if err != nil {
325342
return nil, nil, fmt.Errorf("generating new intermediate cert: %v", err)
326343
}
344+
345+
interCertChain = append(interCertChain, interCert)
327346
}
328347

329-
if interCert == nil {
330-
interCert, err = pemDecodeSingleCert(interCertPEM)
348+
if len(interCertChain) == 0 {
349+
interCertChain, err = pemDecodeCertificateChain(interCertPEM)
331350
if err != nil {
332351
return nil, nil, fmt.Errorf("decoding intermediate certificate PEM: %v", err)
333352
}
@@ -344,7 +363,7 @@ func (ca CA) loadOrGenIntermediate(rootCert *x509.Certificate, rootKey crypto.Si
344363
}
345364
}
346365

347-
return interCert, interKey, nil
366+
return interCertChain, interKey, nil
348367
}
349368

350369
func (ca CA) genIntermediate(rootCert *x509.Certificate, rootKey crypto.Signer) (interCert *x509.Certificate, interKey crypto.Signer, err error) {

modules/caddypki/crypto.go

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,17 @@ package caddypki
1717
import (
1818
"bytes"
1919
"crypto"
20+
"crypto/ecdsa"
21+
"crypto/ed25519"
22+
"crypto/rsa"
2023
"crypto/x509"
2124
"encoding/pem"
25+
"errors"
2226
"fmt"
2327
"os"
2428

29+
"go.step.sm/crypto/pemutil"
30+
2531
"github.com/caddyserver/certmagic"
2632
)
2733

@@ -39,6 +45,15 @@ func pemDecodeSingleCert(pemDER []byte) (*x509.Certificate, error) {
3945
return x509.ParseCertificate(pemBlock.Bytes)
4046
}
4147

48+
func pemDecodeCertificateChain(pemDER []byte) ([]*x509.Certificate, error) {
49+
chain, err := pemutil.ParseCertificateBundle(pemDER)
50+
if err != nil {
51+
return nil, fmt.Errorf("failed parsing certificate chain: %w", err)
52+
}
53+
54+
return chain, nil
55+
}
56+
4257
func pemEncodeCert(der []byte) ([]byte, error) {
4358
return pemEncode("CERTIFICATE", der)
4459
}
@@ -71,14 +86,14 @@ type KeyPair struct {
7186
}
7287

7388
// Load loads the certificate and key.
74-
func (kp KeyPair) Load() (*x509.Certificate, crypto.Signer, error) {
89+
func (kp KeyPair) Load() ([]*x509.Certificate, crypto.Signer, error) {
7590
switch kp.Format {
7691
case "", "pem_file":
7792
certData, err := os.ReadFile(kp.Certificate)
7893
if err != nil {
7994
return nil, nil, err
8095
}
81-
cert, err := pemDecodeSingleCert(certData)
96+
chain, err := pemDecodeCertificateChain(certData)
8297
if err != nil {
8398
return nil, nil, err
8499
}
@@ -93,11 +108,49 @@ func (kp KeyPair) Load() (*x509.Certificate, crypto.Signer, error) {
93108
if err != nil {
94109
return nil, nil, err
95110
}
111+
if err := verifyKeysMatch(chain[0], key); err != nil {
112+
return nil, nil, err
113+
}
96114
}
97115

98-
return cert, key, nil
116+
return chain, key, nil
99117

100118
default:
101119
return nil, nil, fmt.Errorf("unsupported format: %s", kp.Format)
102120
}
103121
}
122+
123+
// verifyKeysMatch verifies that the public key in the [x509.Certificate] matches
124+
// the public key of the [crypto.Signer].
125+
func verifyKeysMatch(crt *x509.Certificate, signer crypto.Signer) error {
126+
switch pub := crt.PublicKey.(type) {
127+
case *rsa.PublicKey:
128+
pk, ok := signer.Public().(*rsa.PublicKey)
129+
if !ok {
130+
return fmt.Errorf("private key type %T does not match issuer public key type %T", signer.Public(), pub)
131+
}
132+
if !pub.Equal(pk) {
133+
return errors.New("private key does not match issuer public key")
134+
}
135+
case *ecdsa.PublicKey:
136+
pk, ok := signer.Public().(*ecdsa.PublicKey)
137+
if !ok {
138+
return fmt.Errorf("private key type %T does not match issuer public key type %T", signer.Public(), pub)
139+
}
140+
if !pub.Equal(pk) {
141+
return errors.New("private key does not match issuer public key")
142+
}
143+
case ed25519.PublicKey:
144+
pk, ok := signer.Public().(ed25519.PublicKey)
145+
if !ok {
146+
return fmt.Errorf("private key type %T does not match issuer public key type %T", signer.Public(), pub)
147+
}
148+
if !pub.Equal(pk) {
149+
return errors.New("private key does not match issuer public key")
150+
}
151+
default:
152+
return fmt.Errorf("unsupported key type: %T", pub)
153+
}
154+
155+
return nil
156+
}

0 commit comments

Comments
 (0)