k8s.io/kubernetes@v1.29.3/pkg/controller/certificates/authority/authority_test.go (about) 1 /* 2 Copyright 2019 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 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 // pretend that the CA has expired 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 // pretend that the CA has expired 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 }