1
+// Copyright 2015 Matthew Holt and The Caddy Authors
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License");
4
+// you may not use this file except in compliance with the License.
5
+// You may obtain a copy of the License at
6
+//
7
+// http://www.apache.org/licenses/LICENSE-2.0
8
+//
9
+// Unless required by applicable law or agreed to in writing, software
10
+// distributed under the License is distributed on an "AS IS" BASIS,
11
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+// See the License for the specific language governing permissions and
13
+// limitations under the License.
14
+
15
+package caddypki
16
+
17
+import (
18
+ "crypto/rand"
19
+ "crypto/x509"
20
+ "crypto/x509/pkix"
21
+ "encoding/pem"
22
+ "os"
23
+ "path/filepath"
24
+ "testing"
25
+ "time"
26
+
27
+ "go.step.sm/crypto/keyutil"
28
+ "go.step.sm/crypto/pemutil"
29
+)
30
+
31
+func TestKeyPair_Load(t *testing.T) {
32
+ rootSigner, err := keyutil.GenerateDefaultSigner()
33
+ if err != nil {
34
+ t.Fatalf("Failed creating signer: %v", err)
35
+ }
36
+
37
+ tmpl := &x509.Certificate{
38
+ Subject: pkix.Name{CommonName: "test-root"},
39
+ IsCA: true,
40
+ MaxPathLen: 3,
41
+ }
42
+ rootBytes, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, rootSigner.Public(), rootSigner)
43
+ if err != nil {
44
+ t.Fatalf("Creating root certificate failed: %v", err)
45
+ }
46
+
47
+ root, err := x509.ParseCertificate(rootBytes)
48
+ if err != nil {
49
+ t.Fatalf("Parsing root certificate failed: %v", err)
50
+ }
51
+
52
+ intermediateSigner, err := keyutil.GenerateDefaultSigner()
53
+ if err != nil {
54
+ t.Fatalf("Creating intermedaite signer failed: %v", err)
55
+ }
56
+
57
+ intermediateBytes, err := x509.CreateCertificate(rand.Reader, &x509.Certificate{
58
+ Subject: pkix.Name{CommonName: "test-first-intermediate"},
59
+ IsCA: true,
60
+ MaxPathLen: 2,
61
+ NotAfter: time.Now().Add(time.Hour),
62
+ }, root, intermediateSigner.Public(), rootSigner)
63
+ if err != nil {
64
+ t.Fatalf("Creating intermediate certificate failed: %v", err)
65
+ }
66
+
67
+ intermediate, err := x509.ParseCertificate(intermediateBytes)
68
+ if err != nil {
69
+ t.Fatalf("Parsing intermediate certificate failed: %v", err)
70
+ }
71
+
72
+ var chainContents []byte
73
+ chain := []*x509.Certificate{intermediate, root}
74
+ for _, cert := range chain {
75
+ b, err := pemutil.Serialize(cert)
76
+ if err != nil {
77
+ t.Fatalf("Failed serializing intermediate certificate: %v", err)
78
+ }
79
+ chainContents = append(chainContents, pem.EncodeToMemory(b)...)
80
+ }
81
+
82
+ dir := t.TempDir()
83
+ rootCertFile := filepath.Join(dir, "root.pem")
84
+ if _, err = pemutil.Serialize(root, pemutil.WithFilename(rootCertFile)); err != nil {
85
+ t.Fatalf("Failed serializing root certificate: %v", err)
86
+ }
87
+ rootKeyFile := filepath.Join(dir, "root.key")
88
+ if _, err = pemutil.Serialize(rootSigner, pemutil.WithFilename(rootKeyFile)); err != nil {
89
+ t.Fatalf("Failed serializing root key: %v", err)
90
+ }
91
+ intermediateCertFile := filepath.Join(dir, "intermediate.pem")
92
+ if _, err = pemutil.Serialize(intermediate, pemutil.WithFilename(intermediateCertFile)); err != nil {
93
+ t.Fatalf("Failed serializing intermediate certificate: %v", err)
94
+ }
95
+ intermediateKeyFile := filepath.Join(dir, "intermediate.key")
96
+ if _, err = pemutil.Serialize(intermediateSigner, pemutil.WithFilename(intermediateKeyFile)); err != nil {
97
+ t.Fatalf("Failed serializing intermediate key: %v", err)
98
+ }
99
+ chainFile := filepath.Join(dir, "chain.pem")
100
+ if err := os.WriteFile(chainFile, chainContents, 0644); err != nil {
101
+ t.Fatalf("Failed writing intermediate chain: %v", err)
102
+ }
103
+
104
+ t.Run("ok/single-certificate-without-signer", func(t *testing.T) {
105
+ kp := KeyPair{
106
+ Certificate: rootCertFile,
107
+ }
108
+ chain, signer, err := kp.Load()
109
+ if err != nil {
110
+ t.Fatalf("Failed loading KeyPair: %v", err)
111
+ }
112
+ if len(chain) != 1 {
113
+ t.Errorf("Expected 1 certificate in chain; got %d", len(chain))
114
+ }
115
+ if signer != nil {
116
+ t.Error("Expected no signer to be returned")
117
+ }
118
+ })
119
+
120
+ t.Run("ok/single-certificate-with-signer", func(t *testing.T) {
121
+ kp := KeyPair{
122
+ Certificate: rootCertFile,
123
+ PrivateKey: rootKeyFile,
124
+ }
125
+ chain, signer, err := kp.Load()
126
+ if err != nil {
127
+ t.Fatalf("Failed loading KeyPair: %v", err)
128
+ }
129
+ if len(chain) != 1 {
130
+ t.Errorf("Expected 1 certificate in chain; got %d", len(chain))
131
+ }
132
+ if signer == nil {
133
+ t.Error("Expected signer to be returned")
134
+ }
135
+ })
136
+
137
+ t.Run("ok/multiple-certificates-with-signer", func(t *testing.T) {
138
+ kp := KeyPair{
139
+ Certificate: chainFile,
140
+ PrivateKey: intermediateKeyFile,
141
+ }
142
+ chain, signer, err := kp.Load()
143
+ if err != nil {
144
+ t.Fatalf("Failed loading KeyPair: %v", err)
145
+ }
146
+ if len(chain) != 2 {
147
+ t.Errorf("Expected 2 certificates in chain; got %d", len(chain))
148
+ }
149
+ if signer == nil {
150
+ t.Error("Expected signer to be returned")
151
+ }
152
+ })
153
+
154
+ t.Run("fail/non-matching-public-key", func(t *testing.T) {
155
+ kp := KeyPair{
156
+ Certificate: intermediateCertFile,
157
+ PrivateKey: rootKeyFile,
158
+ }
159
+ chain, signer, err := kp.Load()
160
+ if err == nil {
161
+ t.Error("Expected loading KeyPair to return an error")
162
+ }
163
+ if chain != nil {
164
+ t.Error("Expected no chain to be returned")
165
+ }
166
+ if signer != nil {
167
+ t.Error("Expected no signer to be returned")
168
+ }
169
+ })
170
+}
171
+
172
+func Test_pemDecodeCertificate(t *testing.T) {
173
+ signer, err := keyutil.GenerateDefaultSigner()
174
+ if err != nil {
175
+ t.Fatalf("Failed creating signer: %v", err)
176
+ }
177
+
178
+ tmpl := &x509.Certificate{
179
+ Subject: pkix.Name{CommonName: "test-cert"},
180
+ IsCA: true,
181
+ MaxPathLen: 3,
182
+ }
183
+ derBytes, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, signer.Public(), signer)
184
+ if err != nil {
185
+ t.Fatalf("Creating root certificate failed: %v", err)
186
+ }
187
+ cert, err := x509.ParseCertificate(derBytes)
188
+ if err != nil {
189
+ t.Fatalf("Parsing root certificate failed: %v", err)
190
+ }
191
+
192
+ pemBlock, err := pemutil.Serialize(cert)
193
+ if err != nil {
194
+ t.Fatalf("Failed serializing certificate: %v", err)
195
+ }
196
+ pemData := pem.EncodeToMemory(pemBlock)
197
+
198
+ t.Run("ok", func(t *testing.T) {
199
+ cert, err := pemDecodeCertificate(pemData)
200
+ if err != nil {
201
+ t.Fatalf("Failed decoding PEM data: %v", err)
202
+ }
203
+ if cert == nil {
204
+ t.Errorf("Expected a certificate in PEM data")
205
+ }
206
+ })
207
+
208
+ t.Run("fail/no-pem-data", func(t *testing.T) {
209
+ cert, err := pemDecodeCertificate(nil)
210
+ if err == nil {
211
+ t.Fatalf("Expected pemDecodeCertificate to return an error")
212
+ }
213
+ if cert != nil {
214
+ t.Errorf("Expected pemDecodeCertificate to return nil")
215
+ }
216
+ })
217
+
218
+ t.Run("fail/multiple", func(t *testing.T) {
219
+ multiplePEMData := append(pemData, pemData...)
220
+ cert, err := pemDecodeCertificate(multiplePEMData)
221
+ if err == nil {
222
+ t.Fatalf("Expected pemDecodeCertificate to return an error")
223
+ }
224
+ if cert != nil {
225
+ t.Errorf("Expected pemDecodeCertificate to return nil")
226
+ }
227
+ })
228
+
229
+ t.Run("fail/no-pem-certificate", func(t *testing.T) {
230
+ pkData := pem.EncodeToMemory(&pem.Block{
231
+ Type: "PRIVATE KEY",
232
+ Bytes: []byte("some-bogus-private-key"),
233
+ })
234
+ cert, err := pemDecodeCertificate(pkData)
235
+ if err == nil {
236
+ t.Fatalf("Expected pemDecodeCertificate to return an error")
237
+ }
238
+ if cert != nil {
239
+ t.Errorf("Expected pemDecodeCertificate to return nil")
240
+ }
241
+ })
242
+}
243
+
244
+func Test_pemDecodeCertificateChain(t *testing.T) {
245
+ signer, err := keyutil.GenerateDefaultSigner()
246
+ if err != nil {
247
+ t.Fatalf("Failed creating signer: %v", err)
248
+ }
249
+
250
+ tmpl := &x509.Certificate{
251
+ Subject: pkix.Name{CommonName: "test-cert"},
252
+ IsCA: true,
253
+ MaxPathLen: 3,
254
+ }
255
+ derBytes, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, signer.Public(), signer)
256
+ if err != nil {
257
+ t.Fatalf("Creating root certificate failed: %v", err)
258
+ }
259
+ cert, err := x509.ParseCertificate(derBytes)
260
+ if err != nil {
261
+ t.Fatalf("Parsing root certificate failed: %v", err)
262
+ }
263
+
264
+ pemBlock, err := pemutil.Serialize(cert)
265
+ if err != nil {
266
+ t.Fatalf("Failed serializing certificate: %v", err)
267
+ }
268
+ pemData := pem.EncodeToMemory(pemBlock)
269
+
270
+ t.Run("ok/single", func(t *testing.T) {
271
+ certs, err := pemDecodeCertificateChain(pemData)
272
+ if err != nil {
273
+ t.Fatalf("Failed decoding PEM data: %v", err)
274
+ }
275
+ if len(certs) != 1 {
276
+ t.Errorf("Expected 1 certificate in PEM data; got %d", len(certs))
277
+ }
278
+ })
279
+
280
+ t.Run("ok/multiple", func(t *testing.T) {
281
+ multiplePEMData := append(pemData, pemData...)
282
+ certs, err := pemDecodeCertificateChain(multiplePEMData)
283
+ if err != nil {
284
+ t.Fatalf("Failed decoding PEM data: %v", err)
285
+ }
286
+ if len(certs) != 2 {
287
+ t.Errorf("Expected 2 certificates in PEM data; got %d", len(certs))
288
+ }
289
+ })
290
+
291
+ t.Run("fail/no-pem-certificate", func(t *testing.T) {
292
+ pkData := pem.EncodeToMemory(&pem.Block{
293
+ Type: "PRIVATE KEY",
294
+ Bytes: []byte("some-bogus-private-key"),
295
+ })
296
+ certs, err := pemDecodeCertificateChain(pkData)
297
+ if err == nil {
298
+ t.Fatalf("Expected pemDecodeCertificateChain to return an error")
299
+ }
300
+ if len(certs) != 0 {
301
+ t.Errorf("Expected 0 certificates in PEM data; got %d", len(certs))
302
+ }
303
+ })
304
+
305
+ t.Run("fail/no-der-certificate", func(t *testing.T) {
306
+ certs, err := pemDecodeCertificateChain([]byte("invalid-der-data"))
307
+ if err == nil {
308
+ t.Fatalf("Expected pemDecodeCertificateChain to return an error")
309
+ }
310
+ if len(certs) != 0 {
311
+ t.Errorf("Expected 0 certificates in PEM data; got %d", len(certs))
312
+ }
313
+ })
314
+}