Skip to content

Commit 041449d

Browse files
authored
Added certificate chains to end-entity certificates (#21)
1 parent f9367b8 commit 041449d

File tree

2 files changed

+89
-15
lines changed

2 files changed

+89
-15
lines changed

certificate.go

+36-15
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ const (
104104
)
105105

106106
// TLSCertificate returns the Certificate as tls.Certificate.
107+
// Complete certificate chain (up to but not including root) is included for end-entity certificates.
107108
// A key pair and certificate will be generated at first call of any Certificate functions.
108109
// Error is not nil if generation fails.
109110
func (c *Certificate) TLSCertificate() (tls.Certificate, error) {
@@ -149,6 +150,7 @@ func (c *Certificate) PrivateKey() (crypto.Signer, error) {
149150
}
150151

151152
// PEM returns the Certificate as certificate and private key PEM buffers.
153+
// Complete certificate chain (up to but not including root) is included for end-entity certificates.
152154
// A key pair and certificate will be generated at first call of any Certificate functions.
153155
// Error is not nil if generation fails.
154156
func (c *Certificate) PEM() (cert []byte, key []byte, err error) {
@@ -157,38 +159,28 @@ func (c *Certificate) PEM() (cert []byte, key []byte, err error) {
157159
return
158160
}
159161

160-
var buf bytes.Buffer
161-
162-
err = pem.Encode(&buf, &pem.Block{
163-
Type: "CERTIFICATE",
164-
Bytes: c.GeneratedCert.Certificate[0],
165-
})
162+
cert, err = encodeToPEMBlocks("CERTIFICATE", c.GeneratedCert.Certificate)
166163
if err != nil {
167164
return
168165
}
169-
cert = append(cert, buf.Bytes()...) // Create copy of underlying buf.
170-
171-
buf.Reset()
172166

173167
k, err := x509.MarshalPKCS8PrivateKey(c.GeneratedCert.PrivateKey)
174168
if err != nil {
175169
cert = nil
176170
return
177171
}
178-
err = pem.Encode(&buf, &pem.Block{
179-
Type: "PRIVATE KEY",
180-
Bytes: k,
181-
})
172+
173+
key, err = encodeToPEMBlocks("PRIVATE KEY", [][]byte{k})
182174
if err != nil {
183175
cert = nil
184176
return
185177
}
186-
key = append(key, buf.Bytes()...) // Create copy of underlying buf.
187178

188179
return
189180
}
190181

191182
// WritePEM writes the Certificate as certificate and private key PEM files.
183+
// Complete certificate chain (up to but not including root) is included for end-entity certificates.
192184
// A key pair and certificate will be generated at first call of any Certificate functions.
193185
// Error is not nil if generation fails.
194186
func (c *Certificate) WritePEM(certFile, keyFile string) error {
@@ -357,13 +349,25 @@ func (c *Certificate) Generate() error {
357349

358350
var issuerCert *x509.Certificate
359351
var issuerKey crypto.Signer
352+
var chain [][]byte
360353
if c.Issuer != nil {
361354
issuerCert, err = x509.ParseCertificate(c.Issuer.GeneratedCert.Certificate[0])
362355
if err != nil {
363356
return nil
364357
}
365358
issuerKey = c.Issuer.GeneratedCert.PrivateKey.(crypto.Signer)
366359

360+
// Add certificate chain to end-entity certificates.
361+
if !*c.IsCA {
362+
issuer := c.Issuer
363+
for issuer != nil {
364+
// Add issuer to chain unless it is root certificate.
365+
if issuer.Issuer != nil {
366+
chain = append(chain, issuer.GeneratedCert.Certificate[0])
367+
}
368+
issuer = issuer.Issuer
369+
}
370+
}
367371
} else {
368372
// create self-signed certificate
369373
issuerCert = template
@@ -377,9 +381,26 @@ func (c *Certificate) Generate() error {
377381
}
378382

379383
c.GeneratedCert = &tls.Certificate{
380-
Certificate: [][]byte{cert},
384+
Certificate: append([][]byte{cert}, chain...),
381385
PrivateKey: key,
382386
}
383387

384388
return nil
385389
}
390+
391+
func encodeToPEMBlocks(blockType string, blocks [][]byte) ([]byte, error) {
392+
var buf bytes.Buffer
393+
394+
for _, b := range blocks {
395+
err := pem.Encode(&buf, &pem.Block{
396+
Type: blockType,
397+
Bytes: b,
398+
})
399+
if err != nil {
400+
return nil, err
401+
}
402+
403+
}
404+
405+
return buf.Bytes(), nil
406+
}

certificate_test.go

+53
Original file line numberDiff line numberDiff line change
@@ -349,3 +349,56 @@ func TestSerial(t *testing.T) {
349349
assert.Nil(t, err)
350350
assert.NotEqual(t, got1.SerialNumber, got2.SerialNumber)
351351
}
352+
353+
func TestCertificateChain(t *testing.T) {
354+
isCA := true
355+
rootCA := Certificate{Subject: "CN=ca"}
356+
subCA1 := Certificate{Subject: "CN=sub-ca-1", Issuer: &rootCA, IsCA: &isCA}
357+
subCA2 := Certificate{Subject: "CN=sub-ca-2", Issuer: &subCA1, IsCA: &isCA}
358+
endEntity := Certificate{Subject: "CN=end-entity", Issuer: &subCA2}
359+
360+
// End-entity certificates have certificate chains appended.
361+
got, err := endEntity.TLSCertificate()
362+
assert.Nil(t, err)
363+
assert.Equal(t, 3, len(got.Certificate))
364+
assert.Equal(t, endEntity.GeneratedCert.Certificate[0], got.Certificate[0])
365+
assert.Equal(t, subCA2.GeneratedCert.Certificate[0], got.Certificate[1])
366+
assert.Equal(t, subCA1.GeneratedCert.Certificate[0], got.Certificate[2])
367+
368+
// CA certificates do not have chains appended.
369+
got, err = subCA2.TLSCertificate()
370+
assert.Nil(t, err)
371+
assert.Equal(t, 1, len(got.Certificate))
372+
}
373+
374+
func TestCertificateChainInPEM(t *testing.T) {
375+
isCA := true
376+
rootCA := Certificate{Subject: "CN=ca"}
377+
subCA1 := Certificate{Subject: "CN=sub-ca-1", Issuer: &rootCA, IsCA: &isCA}
378+
subCA2 := Certificate{Subject: "CN=sub-ca-2", Issuer: &subCA1, IsCA: &isCA}
379+
endEntity := Certificate{Subject: "CN=end-entity", Issuer: &subCA2}
380+
381+
// End-entity certificates have certificate chains appended.
382+
got, _, err := endEntity.PEM()
383+
assert.Nil(t, err)
384+
385+
block, rest := pem.Decode(got)
386+
assert.NotNil(t, block)
387+
cert, err := x509.ParseCertificate(block.Bytes)
388+
assert.Nil(t, err)
389+
assert.Equal(t, "CN=end-entity", cert.Subject.String())
390+
391+
block, rest = pem.Decode(rest)
392+
assert.NotNil(t, block)
393+
cert, err = x509.ParseCertificate(block.Bytes)
394+
assert.Nil(t, err)
395+
assert.Equal(t, "CN=sub-ca-2", cert.Subject.String())
396+
397+
block, rest = pem.Decode(rest)
398+
assert.NotNil(t, block)
399+
cert, err = x509.ParseCertificate(block.Bytes)
400+
assert.Nil(t, err)
401+
assert.Equal(t, "CN=sub-ca-1", cert.Subject.String())
402+
403+
assert.Empty(t, rest)
404+
}

0 commit comments

Comments
 (0)