1
16
17 package authority
18
19 import (
20 "crypto/ecdsa"
21 "crypto/elliptic"
22 "crypto/rand"
23 "crypto/x509"
24 "crypto/x509/pkix"
25 "math/big"
26 "net/url"
27 "testing"
28 "time"
29
30 "github.com/google/go-cmp/cmp"
31 "github.com/google/go-cmp/cmp/cmpopts"
32
33 capi "k8s.io/api/certificates/v1"
34 "k8s.io/apimachinery/pkg/util/diff"
35 )
36
37 func TestCertificateAuthority(t *testing.T) {
38 caKey, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
39 if err != nil {
40 t.Fatal(err)
41 }
42 now := time.Now()
43 nowFunc := func() time.Time { return now }
44 tmpl := &x509.Certificate{
45 SerialNumber: big.NewInt(42),
46 Subject: pkix.Name{
47 CommonName: "test-ca",
48 },
49 NotBefore: now.Add(-24 * time.Hour),
50 NotAfter: now.Add(24 * time.Hour),
51 KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
52 BasicConstraintsValid: true,
53 IsCA: true,
54 }
55 der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, caKey.Public(), caKey)
56 if err != nil {
57 t.Fatal(err)
58 }
59 caCert, err := x509.ParseCertificate(der)
60 if err != nil {
61 t.Fatal(err)
62 }
63
64 uri, err := url.Parse("help://me@what:8080/where/when?why=true")
65 if err != nil {
66 t.Fatal(err)
67 }
68
69 tests := []struct {
70 name string
71 cr x509.CertificateRequest
72 policy SigningPolicy
73 mutateCA func(ca *CertificateAuthority)
74
75 want x509.Certificate
76 wantErr string
77 }{
78 {
79 name: "ca info",
80 policy: PermissiveSigningPolicy{TTL: time.Hour, Now: nowFunc},
81 want: x509.Certificate{
82 Issuer: caCert.Subject,
83 AuthorityKeyId: caCert.SubjectKeyId,
84 NotBefore: now,
85 NotAfter: now.Add(1 * time.Hour),
86 BasicConstraintsValid: true,
87 },
88 },
89 {
90 name: "key usage",
91 policy: PermissiveSigningPolicy{TTL: time.Hour, Usages: []capi.KeyUsage{"signing"}, Now: nowFunc},
92 want: x509.Certificate{
93 NotBefore: now,
94 NotAfter: now.Add(1 * time.Hour),
95 BasicConstraintsValid: true,
96 KeyUsage: x509.KeyUsageDigitalSignature,
97 },
98 },
99 {
100 name: "ext key usage",
101 policy: PermissiveSigningPolicy{TTL: time.Hour, Usages: []capi.KeyUsage{"client auth"}, Now: nowFunc},
102 want: x509.Certificate{
103 NotBefore: now,
104 NotAfter: now.Add(1 * time.Hour),
105 BasicConstraintsValid: true,
106 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
107 },
108 },
109 {
110 name: "backdate without short",
111 policy: PermissiveSigningPolicy{TTL: time.Hour, Backdate: 5 * time.Minute, Now: nowFunc},
112 want: x509.Certificate{
113 NotBefore: now.Add(-5 * time.Minute),
114 NotAfter: now.Add(55 * time.Minute),
115 BasicConstraintsValid: true,
116 },
117 },
118 {
119 name: "backdate without short and super small ttl",
120 policy: PermissiveSigningPolicy{TTL: time.Minute, Backdate: 5 * time.Minute, Now: nowFunc},
121 want: x509.Certificate{
122 NotBefore: now.Add(-5 * time.Minute),
123 NotAfter: now.Add(-4 * time.Minute),
124 BasicConstraintsValid: true,
125 },
126 },
127 {
128 name: "backdate with short",
129 policy: PermissiveSigningPolicy{TTL: time.Hour, Backdate: 5 * time.Minute, Short: 8 * time.Hour, Now: nowFunc},
130 want: x509.Certificate{
131 NotBefore: now.Add(-5 * time.Minute),
132 NotAfter: now.Add(time.Hour),
133 BasicConstraintsValid: true,
134 },
135 },
136 {
137 name: "backdate with short and super small ttl",
138 policy: PermissiveSigningPolicy{TTL: time.Minute, Backdate: 5 * time.Minute, Short: 8 * time.Hour, Now: nowFunc},
139 want: x509.Certificate{
140 NotBefore: now.Add(-5 * time.Minute),
141 NotAfter: now.Add(time.Minute),
142 BasicConstraintsValid: true,
143 },
144 },
145 {
146 name: "backdate with short but longer ttl",
147 policy: PermissiveSigningPolicy{TTL: 24 * time.Hour, Backdate: 5 * time.Minute, Short: 8 * time.Hour, Now: nowFunc},
148 want: x509.Certificate{
149 NotBefore: now.Add(-5 * time.Minute),
150 NotAfter: now.Add(24*time.Hour - 5*time.Minute),
151 BasicConstraintsValid: true,
152 },
153 },
154 {
155 name: "truncate expiration",
156 policy: PermissiveSigningPolicy{TTL: 48 * time.Hour, Now: nowFunc},
157 want: x509.Certificate{
158 NotBefore: now,
159 NotAfter: now.Add(24 * time.Hour),
160 BasicConstraintsValid: true,
161 },
162 },
163 {
164 name: "uri sans",
165 policy: PermissiveSigningPolicy{TTL: time.Hour, Now: nowFunc},
166 cr: x509.CertificateRequest{
167 URIs: []*url.URL{uri},
168 },
169 want: x509.Certificate{
170 URIs: []*url.URL{uri},
171 NotBefore: now,
172 NotAfter: now.Add(1 * time.Hour),
173 BasicConstraintsValid: true,
174 },
175 },
176 {
177 name: "expired ca",
178 policy: PermissiveSigningPolicy{TTL: time.Hour, Now: nowFunc},
179 mutateCA: func(ca *CertificateAuthority) {
180 ca.Certificate.NotAfter = now
181 },
182 wantErr: "the signer has expired: NotAfter=" + now.String(),
183 },
184 {
185 name: "expired ca with backdate",
186 policy: PermissiveSigningPolicy{TTL: time.Hour, Backdate: 5 * time.Minute, Now: nowFunc},
187 mutateCA: func(ca *CertificateAuthority) {
188 ca.Certificate.NotAfter = now
189 },
190 wantErr: "refusing to sign a certificate that expired in the past: NotAfter=" + now.String(),
191 },
192 }
193
194 crKey, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
195 if err != nil {
196 t.Fatal(err)
197 }
198
199 for _, test := range tests {
200 t.Run(test.name, func(t *testing.T) {
201 caCertShallowCopy := *caCert
202
203 ca := &CertificateAuthority{
204 Certificate: &caCertShallowCopy,
205 PrivateKey: caKey,
206 }
207
208 if test.mutateCA != nil {
209 test.mutateCA(ca)
210 }
211
212 csr, err := x509.CreateCertificateRequest(rand.Reader, &test.cr, crKey)
213 if err != nil {
214 t.Fatal(err)
215 }
216
217 certDER, err := ca.Sign(csr, test.policy)
218 if len(test.wantErr) > 0 {
219 if errStr := errString(err); test.wantErr != errStr {
220 t.Fatalf("expected error %s but got %s", test.wantErr, errStr)
221 }
222 return
223 }
224 if err != nil {
225 t.Fatal(err)
226 }
227
228 cert, err := x509.ParseCertificate(certDER)
229 if err != nil {
230 t.Fatal(err)
231 }
232
233 opts := cmp.Options{
234 cmpopts.IgnoreFields(x509.Certificate{},
235 "SignatureAlgorithm",
236 "PublicKeyAlgorithm",
237 "Version",
238 "MaxPathLen",
239 ),
240 diff.IgnoreUnset(),
241 cmp.Transformer("RoundTime", func(x time.Time) time.Time {
242 return x.Truncate(time.Second)
243 }),
244 cmp.Comparer(func(x, y *url.URL) bool {
245 return ((x == nil) && (y == nil)) || x.String() == y.String()
246 }),
247 }
248 if !cmp.Equal(*cert, test.want, opts) {
249 t.Errorf("unexpected diff: %v", cmp.Diff(*cert, test.want, opts))
250 }
251 })
252 }
253 }
254
255 func errString(err error) string {
256 if err == nil {
257 return ""
258 }
259
260 return err.Error()
261 }
262
View as plain text