Skip to content

Commit 3e35acf

Browse files
authored
Avoid creating certs that violate Apple requirements for macOS 10.15 (#208)
* Prevent creating non-standards compliant certs. Changes generated certificates to have a NotBefore based on either the CA NotBefore or the current time. This prevents creation of certificates that are valid for too long making them return errors on platforms like MacOS. * Add license header and add test cases
1 parent 4654f37 commit 3e35acf

File tree

6 files changed

+141
-21
lines changed

6 files changed

+141
-21
lines changed

cert/cert.go

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -73,15 +73,15 @@ func NewPrivateKey() (*rsa.PrivateKey, error) {
7373

7474
// NewSelfSignedCACert creates a CA certificate
7575
func NewSelfSignedCACert(cfg Config, key crypto.Signer) (*x509.Certificate, error) {
76-
now := time.Now()
76+
notBefore := CalculateNotBefore(nil)
7777
tmpl := x509.Certificate{
7878
SerialNumber: new(big.Int).SetInt64(0),
7979
Subject: pkix.Name{
8080
CommonName: cfg.CommonName,
8181
Organization: cfg.Organization,
8282
},
83-
NotBefore: now.UTC(),
84-
NotAfter: now.Add(duration365d * 10).UTC(),
83+
NotBefore: notBefore,
84+
NotAfter: notBefore.Add(duration365d * 10),
8585
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
8686
BasicConstraintsValid: true,
8787
IsCA: true,
@@ -125,6 +125,7 @@ func NewSignedCert(cfg Config, key crypto.Signer, caCert *x509.Certificate, caKe
125125
}
126126
}
127127

128+
notBefore := CalculateNotBefore(caCert)
128129
certTmpl := x509.Certificate{
129130
Subject: pkix.Name{
130131
CommonName: cfg.CommonName,
@@ -133,8 +134,8 @@ func NewSignedCert(cfg Config, key crypto.Signer, caCert *x509.Certificate, caKe
133134
DNSNames: cfg.AltNames.DNSNames,
134135
IPAddresses: cfg.AltNames.IPs,
135136
SerialNumber: serial,
136-
NotBefore: caCert.NotBefore,
137-
NotAfter: time.Now().Add(expiresAt).UTC(),
137+
NotBefore: notBefore,
138+
NotAfter: notBefore.Add(expiresAt),
138139
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
139140
ExtKeyUsage: cfg.Usages,
140141
}
@@ -186,8 +187,8 @@ func GenerateSelfSignedCertKey(host string, alternateIPs []net.IP, alternateDNS
186187
// <host>_<ip>-<ip>_<alternateDNS>-<alternateDNS>.key
187188
// Certs/keys not existing in that directory are created.
188189
func GenerateSelfSignedCertKeyWithFixtures(host string, alternateIPs []net.IP, alternateDNS []string, fixtureDirectory string) ([]byte, []byte, error) {
189-
validFrom := time.Now().Add(-time.Hour) // valid an hour earlier to avoid flakes due to clock skew
190-
maxAge := time.Hour * 24 * 365 // one year self-signed certs
190+
notBefore := CalculateNotBefore(nil)
191+
maxAge := time.Hour * 24 * 365 // one year self-signed certs
191192

192193
baseName := fmt.Sprintf("%s_%s_%s", host, strings.Join(ipsToStrings(alternateIPs), "-"), strings.Join(alternateDNS, "-"))
193194
certFixturePath := filepath.Join(fixtureDirectory, baseName+".crt")
@@ -214,8 +215,8 @@ func GenerateSelfSignedCertKeyWithFixtures(host string, alternateIPs []net.IP, a
214215
Subject: pkix.Name{
215216
CommonName: fmt.Sprintf("%s-ca@%d", host, time.Now().Unix()),
216217
},
217-
NotBefore: validFrom,
218-
NotAfter: validFrom.Add(maxAge),
218+
NotBefore: notBefore,
219+
NotAfter: notBefore.Add(maxAge),
219220

220221
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
221222
BasicConstraintsValid: true,
@@ -242,8 +243,8 @@ func GenerateSelfSignedCertKeyWithFixtures(host string, alternateIPs []net.IP, a
242243
Subject: pkix.Name{
243244
CommonName: fmt.Sprintf("%s@%d", host, time.Now().Unix()),
244245
},
245-
NotBefore: validFrom,
246-
NotAfter: validFrom.Add(maxAge),
246+
NotBefore: notBefore,
247+
NotAfter: notBefore.Add(maxAge),
247248

248249
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
249250
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},

cert/validity.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
Copyright 2014 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 cert
18+
19+
import (
20+
"crypto/x509"
21+
"time"
22+
23+
clockutil "k8s.io/utils/clock"
24+
)
25+
26+
var clock clockutil.PassiveClock = &clockutil.RealClock{}
27+
28+
// CalculateNotBefore calculates a NotBefore time of 1 hour in the past, or the
29+
// NotBefore time of the optionally provided *x509.Certificate, whichever is greater.
30+
func CalculateNotBefore(ca *x509.Certificate) time.Time {
31+
// Subtract 1 hour for clock skew
32+
now := clock.Now().UTC().Add(-time.Hour)
33+
34+
// It makes no sense to return a time before the CA itself is valid.
35+
if ca != nil && now.Before(ca.NotBefore) {
36+
return ca.NotBefore
37+
}
38+
return now
39+
}

cert/validity_test.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
Copyright 2014 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 cert
18+
19+
import (
20+
"crypto/x509"
21+
"testing"
22+
"time"
23+
24+
clocktest "k8s.io/utils/clock/testing"
25+
)
26+
27+
func TestCalculateNotBefore(t *testing.T) {
28+
baseTime := time.Date(2025, 9, 29, 12, 0, 0, 0, time.UTC)
29+
30+
tests := []struct {
31+
name string
32+
ca *x509.Certificate
33+
now time.Time
34+
expected time.Time
35+
}{
36+
{
37+
name: "nil CA returns 1h ago",
38+
ca: nil,
39+
now: baseTime,
40+
expected: baseTime.Add(-time.Hour),
41+
},
42+
{
43+
name: "CA notBefore before now returns 1h ago",
44+
ca: &x509.Certificate{
45+
NotBefore: baseTime.Add(-2 * time.Hour),
46+
},
47+
now: baseTime,
48+
expected: baseTime.Add(-time.Hour),
49+
},
50+
{
51+
name: "CA notBefore after now returns CA.NotBefore",
52+
ca: &x509.Certificate{
53+
NotBefore: baseTime.Add(2 * time.Hour),
54+
},
55+
now: baseTime,
56+
expected: baseTime.Add(2 * time.Hour),
57+
},
58+
{
59+
name: "CA notBefore equal to now returns now",
60+
ca: &x509.Certificate{
61+
NotBefore: baseTime,
62+
},
63+
now: baseTime,
64+
expected: baseTime,
65+
},
66+
}
67+
68+
for _, tt := range tests {
69+
t.Run(tt.name, func(t *testing.T) {
70+
clock = clocktest.NewFakePassiveClock(tt.now)
71+
result := CalculateNotBefore(tt.ca)
72+
if !result.Equal(tt.expected) {
73+
t.Errorf("Expected %v, got %v", tt.expected, result)
74+
}
75+
})
76+
}
77+
}

factory/cert_utils.go

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"strings"
1616
"time"
1717

18+
"github.com/rancher/dynamiclistener/cert"
1819
"github.com/sirupsen/logrus"
1920
)
2021

@@ -24,13 +25,13 @@ const (
2425
)
2526

2627
func NewSelfSignedCACert(key crypto.Signer, cn string, org ...string) (*x509.Certificate, error) {
27-
now := time.Now()
28+
notBefore := cert.CalculateNotBefore(nil)
2829
tmpl := x509.Certificate{
2930
BasicConstraintsValid: true,
3031
IsCA: true,
3132
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
32-
NotAfter: now.Add(time.Hour * 24 * 365 * 10).UTC(),
33-
NotBefore: now.UTC(),
33+
NotBefore: notBefore,
34+
NotAfter: notBefore.Add(time.Hour * 24 * 365 * 10),
3435
SerialNumber: new(big.Int).SetInt64(0),
3536
Subject: pkix.Name{
3637
CommonName: cn,
@@ -55,11 +56,12 @@ func NewSignedClientCert(signer crypto.Signer, caCert *x509.Certificate, caKey c
5556
return nil, err
5657
}
5758

59+
notBefore := cert.CalculateNotBefore(caCert)
5860
parent := x509.Certificate{
5961
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
6062
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
61-
NotAfter: time.Now().Add(time.Hour * 24 * 365).UTC(),
62-
NotBefore: caCert.NotBefore,
63+
NotBefore: notBefore,
64+
NotAfter: notBefore.Add(time.Hour * 24 * 365),
6365
SerialNumber: serialNumber,
6466
Subject: pkix.Name{
6567
CommonName: cn,
@@ -98,13 +100,14 @@ func NewSignedCert(signer crypto.Signer, caCert *x509.Certificate, caKey crypto.
98100
}
99101
}
100102

103+
notBefore := cert.CalculateNotBefore(caCert)
101104
parent := x509.Certificate{
102105
DNSNames: domains,
103106
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
104107
IPAddresses: ips,
105108
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
106-
NotAfter: time.Now().Add(time.Hour * 24 * time.Duration(expirationDays)).UTC(),
107-
NotBefore: caCert.NotBefore,
109+
NotBefore: notBefore,
110+
NotAfter: notBefore.Add(time.Hour * 24 * time.Duration(expirationDays)),
108111
SerialNumber: serialNumber,
109112
Subject: pkix.Name{
110113
CommonName: cn,

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ require (
1010
k8s.io/api v0.34.1
1111
k8s.io/apimachinery v0.34.1
1212
k8s.io/client-go v0.34.1
13+
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4
1314
)
1415

1516
require (
@@ -56,7 +57,6 @@ require (
5657
gopkg.in/yaml.v3 v3.0.1 // indirect
5758
k8s.io/klog/v2 v2.130.1 // indirect
5859
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect
59-
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect
6060
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
6161
sigs.k8s.io/randfill v1.0.0 // indirect
6262
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,8 @@ k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
167167
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
168168
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA=
169169
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts=
170-
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y=
171-
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
170+
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck=
171+
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
172172
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
173173
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
174174
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=

0 commit comments

Comments
 (0)