1
16
17 package certs_test
18
19 import (
20 "crypto/x509"
21 "encoding/pem"
22 "math/big"
23 "net"
24 "sort"
25 "time"
26
27 . "github.com/onsi/ginkgo/v2"
28 . "github.com/onsi/gomega"
29 . "github.com/onsi/gomega/gstruct"
30
31 "sigs.k8s.io/controller-runtime/pkg/internal/testing/certs"
32 )
33
34 var _ = Describe("TinyCA", func() {
35 var ca *certs.TinyCA
36
37 BeforeEach(func() {
38 var err error
39 ca, err = certs.NewTinyCA()
40 Expect(err).NotTo(HaveOccurred(), "should be able to initialize the CA")
41 })
42
43 Describe("the CA certs themselves", func() {
44 It("should be retrievable as a cert pair", func() {
45 Expect(ca.CA.Key).NotTo(BeNil(), "should have a key")
46 Expect(ca.CA.Cert).NotTo(BeNil(), "should have a cert")
47 })
48
49 It("should be usable for signing & verifying", func() {
50 Expect(ca.CA.Cert.KeyUsage&x509.KeyUsageCertSign).NotTo(BeEquivalentTo(0), "should be usable for cert signing")
51 Expect(ca.CA.Cert.KeyUsage&x509.KeyUsageDigitalSignature).NotTo(BeEquivalentTo(0), "should be usable for signature verifying")
52 })
53 })
54
55 It("should produce unique serials among all generated certificates of all types", func() {
56 By("generating a few cert pairs for both serving and client auth")
57 firstCerts, err := ca.NewServingCert()
58 Expect(err).NotTo(HaveOccurred())
59 secondCerts, err := ca.NewClientCert(certs.ClientInfo{Name: "user"})
60 Expect(err).NotTo(HaveOccurred())
61 thirdCerts, err := ca.NewServingCert()
62 Expect(err).NotTo(HaveOccurred())
63
64 By("checking that they have different serials")
65 serials := []*big.Int{
66 firstCerts.Cert.SerialNumber,
67 secondCerts.Cert.SerialNumber,
68 thirdCerts.Cert.SerialNumber,
69 }
70
71 sort.Slice(serials, func(i, j int) bool {
72 return serials[i].Cmp(serials[j]) == -1
73 })
74 Expect(serials[1].Cmp(serials[0])).NotTo(Equal(0), "serials shouldn't be equal")
75 Expect(serials[2].Cmp(serials[1])).NotTo(Equal(0), "serials shouldn't be equal")
76 })
77
78 Describe("Generated serving certs", func() {
79 It("should be valid for short enough to avoid production usage, but long enough for long-running tests", func() {
80 cert, err := ca.NewServingCert()
81 Expect(err).NotTo(HaveOccurred(), "should be able to generate the serving certs")
82
83 duration := time.Until(cert.Cert.NotAfter)
84 Expect(duration).To(BeNumerically("<=", 168*time.Hour), "not-after should be short-ish (<= 1 week)")
85 Expect(duration).To(BeNumerically(">=", 2*time.Hour), "not-after should be enough for long tests (couple of hours)")
86 })
87
88 Context("when encoding names", func() {
89 var cert certs.CertPair
90 BeforeEach(func() {
91 By("generating a serving cert with IPv4 & IPv6 addresses, and DNS names")
92 var err error
93
94 cert, err = ca.NewServingCert("192.0.2.1", "localhost", "2001:db8::")
95 Expect(err).NotTo(HaveOccurred(), "should be able to create the serving certs")
96 })
97
98 It("should encode all non-IP names as DNS SANs", func() {
99 Expect(cert.Cert.DNSNames).To(ConsistOf("localhost"))
100 })
101
102 It("should encode all IP names as IP SANs", func() {
103
104
105 Expect(cert.Cert.IPAddresses).To(ContainElements(
106
107
108
109 WithTransform(net.IP.To16, Equal(net.ParseIP("192.0.2.1"))),
110 WithTransform(net.IP.To16, Equal(net.ParseIP("2001:db8::"))),
111 ))
112 })
113
114 It("should add the corresponding IP address(es) (as IP SANs) for DNS names", func() {
115
116
117
118
119
120 localhostAddrs, err := net.LookupHost("localhost")
121 Expect(err).NotTo(HaveOccurred(), "should be able to find IPs for localhost")
122 localhostIPs := make([]interface{}, len(localhostAddrs))
123 for i, addr := range localhostAddrs {
124
125
126
127 localhostIPs[i] = WithTransform(net.IP.To16, Equal(net.ParseIP(addr)))
128 }
129 Expect(cert.Cert.IPAddresses).To(ContainElements(localhostIPs...))
130 })
131 })
132
133 It("should assume a name of localhost (DNS SAN) if no names are given", func() {
134 cert, err := ca.NewServingCert()
135 Expect(err).NotTo(HaveOccurred(), "should be able to generate a serving cert with the default name")
136 Expect(cert.Cert.DNSNames).To(ConsistOf("localhost"), "the default DNS name should be localhost")
137
138 })
139
140 It("should be usable for server auth, verifying, and enciphering", func() {
141 cert, err := ca.NewServingCert()
142 Expect(err).NotTo(HaveOccurred(), "should be able to generate a serving cert")
143
144 Expect(cert.Cert.KeyUsage&x509.KeyUsageKeyEncipherment).NotTo(BeEquivalentTo(0), "should be usable for key enciphering")
145 Expect(cert.Cert.KeyUsage&x509.KeyUsageDigitalSignature).NotTo(BeEquivalentTo(0), "should be usable for signature verifying")
146 Expect(cert.Cert.ExtKeyUsage).To(ContainElement(x509.ExtKeyUsageServerAuth), "should be usable for server auth")
147
148 })
149
150 It("should be signed by the CA", func() {
151 cert, err := ca.NewServingCert()
152 Expect(err).NotTo(HaveOccurred(), "should be able to generate a serving cert")
153 Expect(cert.Cert.CheckSignatureFrom(ca.CA.Cert)).To(Succeed())
154 })
155 })
156
157 Describe("Generated client certs", func() {
158 var cert certs.CertPair
159 BeforeEach(func() {
160 var err error
161 cert, err = ca.NewClientCert(certs.ClientInfo{
162 Name: "user",
163 Groups: []string{"group1", "group2"},
164 })
165 Expect(err).NotTo(HaveOccurred(), "should be able to create a client cert")
166 })
167
168 It("should be valid for short enough to avoid production usage, but long enough for long-running tests", func() {
169 duration := time.Until(cert.Cert.NotAfter)
170 Expect(duration).To(BeNumerically("<=", 168*time.Hour), "not-after should be short-ish (<= 1 week)")
171 Expect(duration).To(BeNumerically(">=", 2*time.Hour), "not-after should be enough for long tests (couple of hours)")
172 })
173
174 It("should be usable for client auth, verifying, and enciphering", func() {
175 Expect(cert.Cert.KeyUsage&x509.KeyUsageKeyEncipherment).NotTo(BeEquivalentTo(0), "should be usable for key enciphering")
176 Expect(cert.Cert.KeyUsage&x509.KeyUsageDigitalSignature).NotTo(BeEquivalentTo(0), "should be usable for signature verifying")
177 Expect(cert.Cert.ExtKeyUsage).To(ContainElement(x509.ExtKeyUsageClientAuth), "should be usable for client auth")
178 })
179
180 It("should encode the user name as the common name", func() {
181 Expect(cert.Cert.Subject.CommonName).To(Equal("user"))
182 })
183
184 It("should encode the groups as the organization values", func() {
185 Expect(cert.Cert.Subject.Organization).To(ConsistOf("group1", "group2"))
186 })
187
188 It("should be signed by the CA", func() {
189 Expect(cert.Cert.CheckSignatureFrom(ca.CA.Cert)).To(Succeed())
190 })
191 })
192 })
193
194 var _ = Describe("Certificate Pairs", func() {
195 var pair certs.CertPair
196 BeforeEach(func() {
197 ca, err := certs.NewTinyCA()
198 Expect(err).NotTo(HaveOccurred(), "should be able to generate a cert pair")
199
200 pair = ca.CA
201 })
202
203 Context("when serializing just the public key", func() {
204 It("should serialize into a CERTIFICATE PEM block", func() {
205 bytes := pair.CertBytes()
206 Expect(bytes).NotTo(BeEmpty(), "should produce some cert bytes")
207
208 block, rest := pem.Decode(bytes)
209 Expect(rest).To(BeEmpty(), "shouldn't have any data besides the PEM block")
210
211 Expect(block).To(PointTo(MatchAllFields(Fields{
212 "Type": Equal("CERTIFICATE"),
213 "Headers": BeEmpty(),
214 "Bytes": Equal(pair.Cert.Raw),
215 })))
216 })
217 })
218
219 Context("when serializing both parts", func() {
220 var certBytes, keyBytes []byte
221 BeforeEach(func() {
222 var err error
223 certBytes, keyBytes, err = pair.AsBytes()
224 Expect(err).NotTo(HaveOccurred(), "should be able to serialize the pair")
225 })
226
227 It("should serialize the private key in PKCS8 form in a PRIVATE KEY PEM block", func() {
228 Expect(keyBytes).NotTo(BeEmpty(), "should produce some key bytes")
229
230 By("decoding & checking the PEM block")
231 block, rest := pem.Decode(keyBytes)
232 Expect(rest).To(BeEmpty(), "shouldn't have any data besides the PEM block")
233
234 Expect(block.Type).To(Equal("PRIVATE KEY"))
235
236 By("decoding & checking the PKCS8 data")
237 Expect(x509.ParsePKCS8PrivateKey(block.Bytes)).NotTo(BeNil(), "should be able to parse back the private key")
238 })
239
240 It("should serialize the public key into a CERTIFICATE PEM block", func() {
241 Expect(certBytes).NotTo(BeEmpty(), "should produce some cert bytes")
242
243 block, rest := pem.Decode(certBytes)
244 Expect(rest).To(BeEmpty(), "shouldn't have any data besides the PEM block")
245
246 Expect(block).To(PointTo(MatchAllFields(Fields{
247 "Type": Equal("CERTIFICATE"),
248 "Headers": BeEmpty(),
249 "Bytes": Equal(pair.Cert.Raw),
250 })))
251 })
252
253 })
254 })
255
View as plain text