1 package issuercerts
2
3 import (
4 "context"
5 "crypto/ecdsa"
6 "crypto/rsa"
7 "crypto/x509"
8 "fmt"
9 "os"
10 "path/filepath"
11 "time"
12
13 "k8s.io/client-go/kubernetes"
14
15 "github.com/linkerd/linkerd2/pkg/k8s"
16 "github.com/linkerd/linkerd2/pkg/tls"
17 corev1 "k8s.io/api/core/v1"
18 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
19 )
20
21 const keyMissingError = "key %s containing the %s needs to exist in secret %s if --identity-external-issuer=%v"
22 const expirationWarningThresholdInDays = 60
23
24
25 type IssuerCertData struct {
26 TrustAnchors string
27 IssuerCrt string
28 IssuerKey string
29 Expiry *time.Time
30 }
31
32
33 func FetchIssuerData(ctx context.Context, api kubernetes.Interface, trustAnchors, controlPlaneNamespace string) (*IssuerCertData, error) {
34 secret, err := api.CoreV1().Secrets(controlPlaneNamespace).Get(ctx, k8s.IdentityIssuerSecretName, metav1.GetOptions{})
35 if err != nil {
36 return nil, err
37 }
38
39 crt, ok := secret.Data[k8s.IdentityIssuerCrtName]
40 if !ok {
41 return nil, fmt.Errorf(keyMissingError, k8s.IdentityIssuerCrtName, "issuer certificate", k8s.IdentityIssuerSecretName, false)
42 }
43
44 key, ok := secret.Data[k8s.IdentityIssuerKeyName]
45 if !ok {
46 return nil, fmt.Errorf(keyMissingError, k8s.IdentityIssuerKeyName, "issuer key", k8s.IdentityIssuerSecretName, true)
47 }
48
49 cert, err := tls.DecodePEMCrt(string(crt))
50 if err != nil {
51 return nil, fmt.Errorf("could not parse issuer certificate: %w", err)
52 }
53
54 return &IssuerCertData{trustAnchors, string(crt), string(key), &cert.Certificate.NotAfter}, nil
55 }
56
57
58 func FetchExternalIssuerData(ctx context.Context, api kubernetes.Interface, controlPlaneNamespace string) (*IssuerCertData, error) {
59 secret, err := api.CoreV1().Secrets(controlPlaneNamespace).Get(ctx, k8s.IdentityIssuerSecretName, metav1.GetOptions{})
60 if err != nil {
61 return nil, err
62 }
63
64 anchors, ok := secret.Data[k8s.IdentityIssuerTrustAnchorsNameExternal]
65 if !ok {
66 return nil, fmt.Errorf(keyMissingError, k8s.IdentityIssuerTrustAnchorsNameExternal, "trust anchors", k8s.IdentityIssuerSecretName, true)
67 }
68
69 crt, ok := secret.Data[corev1.TLSCertKey]
70 if !ok {
71 return nil, fmt.Errorf(keyMissingError, corev1.TLSCertKey, "issuer certificate", k8s.IdentityIssuerSecretName, true)
72 }
73
74 key, ok := secret.Data[corev1.TLSPrivateKeyKey]
75 if !ok {
76 return nil, fmt.Errorf(keyMissingError, corev1.TLSPrivateKeyKey, "issuer key", k8s.IdentityIssuerSecretName, true)
77 }
78
79 cert, err := tls.DecodePEMCrt(string(crt))
80 if err != nil {
81 return nil, fmt.Errorf("could not parse issuer certificate: %w", err)
82 }
83
84 return &IssuerCertData{string(anchors), string(crt), string(key), &cert.Certificate.NotAfter}, nil
85 }
86
87
88 func LoadIssuerCrtAndKeyFromFiles(keyPEMFile, crtPEMFile string) (string, string, error) {
89 key, err := os.ReadFile(filepath.Clean(keyPEMFile))
90 if err != nil {
91 return "", "", err
92 }
93
94 crt, err := os.ReadFile(filepath.Clean(crtPEMFile))
95 if err != nil {
96 return "", "", err
97 }
98
99 return string(key), string(crt), nil
100 }
101
102
103 func LoadIssuerDataFromFiles(keyPEMFile, crtPEMFile, trustPEMFile string) (*IssuerCertData, error) {
104 key, crt, err := LoadIssuerCrtAndKeyFromFiles(keyPEMFile, crtPEMFile)
105 if err != nil {
106 return nil, err
107 }
108
109 anchors, err := os.ReadFile(filepath.Clean(trustPEMFile))
110 if err != nil {
111 return nil, err
112 }
113
114 return &IssuerCertData{string(anchors), crt, key, nil}, nil
115 }
116
117
118 func CheckCertValidityPeriod(cert *x509.Certificate) error {
119 if cert.NotBefore.After(time.Now()) {
120 return fmt.Errorf("not valid before: %s", cert.NotBefore.Format(time.RFC3339))
121 }
122
123 if cert.NotAfter.Before(time.Now()) {
124 return fmt.Errorf("not valid anymore. Expired on %s", cert.NotAfter.Format(time.RFC3339))
125 }
126 return nil
127 }
128
129
130 func CheckExpiringSoon(cert *x509.Certificate) error {
131 if time.Now().AddDate(0, 0, expirationWarningThresholdInDays).After(cert.NotAfter) {
132 return fmt.Errorf("will expire on %s", cert.NotAfter.Format(time.RFC3339))
133 }
134 return nil
135 }
136
137
138
139
140 func CheckIssuerCertAlgoRequirements(cert *x509.Certificate) error {
141 if cert.PublicKeyAlgorithm == x509.ECDSA {
142 err := checkECDSACertRequirements(cert)
143 if err != nil {
144 return err
145 }
146 } else {
147 return fmt.Errorf("issuer certificate must use ECDSA for public key algorithm, instead %s was used", cert.PublicKeyAlgorithm)
148 }
149
150 return nil
151 }
152
153
154
155
156 func CheckTrustAnchorAlgoRequirements(cert *x509.Certificate) error {
157 if cert.PublicKeyAlgorithm == x509.ECDSA {
158 err := checkECDSACertRequirements(cert)
159 if err != nil {
160 return err
161 }
162 } else if cert.PublicKeyAlgorithm == x509.RSA {
163 err := checkRSACertRequirements(cert)
164 if err != nil {
165 return err
166 }
167 } else {
168 return fmt.Errorf("trust anchor must use ECDSA or RSA for public key algorithm, instead %s was used", cert.PublicKeyAlgorithm)
169 }
170
171 return nil
172 }
173
174 func checkECDSACertRequirements(cert *x509.Certificate) error {
175 k, ok := cert.PublicKey.(*ecdsa.PublicKey)
176 if !ok {
177 return fmt.Errorf("expected ecdsa.PublicKey but got something %v", cert.PublicKey)
178 }
179 if k.Params().BitSize != 256 {
180 return fmt.Errorf("must use P-256 curve for public key, instead P-%d was used", k.Params().BitSize)
181 }
182 if cert.SignatureAlgorithm != x509.ECDSAWithSHA256 &&
183 cert.SignatureAlgorithm != x509.SHA256WithRSA {
184 return fmt.Errorf("must be signed by an ECDSA P-256 key, instead %s was used", cert.SignatureAlgorithm)
185 }
186
187 return nil
188 }
189
190 func checkRSACertRequirements(cert *x509.Certificate) error {
191 k, ok := cert.PublicKey.(*rsa.PublicKey)
192 if !ok {
193 return fmt.Errorf("expected rsa.PublicKey but got something %v", cert.PublicKey)
194 }
195 if k.N.BitLen() != 2048 && k.N.BitLen() != 4096 {
196 return fmt.Errorf("RSA must use at least 2084 bit public key, instead %d bit public key was used", k.N.BitLen())
197 }
198 if cert.SignatureAlgorithm != x509.SHA256WithRSA {
199 return fmt.Errorf("must be signed by an RSA 2048/4096 bit key, instead %s was used", cert.SignatureAlgorithm)
200 }
201
202 return nil
203 }
204
205
206 func (ic *IssuerCertData) VerifyAndBuildCreds() (*tls.Cred, error) {
207 creds, err := tls.ValidateAndCreateCreds(ic.IssuerCrt, ic.IssuerKey)
208 if err != nil {
209 return nil, fmt.Errorf("failed to read CA: %w", err)
210 }
211
212
213 if err := CheckCertValidityPeriod(creds.Certificate); err != nil {
214 return nil, err
215 }
216
217
218 if err := CheckIssuerCertAlgoRequirements(creds.Certificate); err != nil {
219 return nil, err
220 }
221
222 if !creds.Certificate.IsCA {
223 return nil, fmt.Errorf("issuer cert is not a CA")
224 }
225
226 anchors, err := tls.DecodePEMCertPool(ic.TrustAnchors)
227 if err != nil {
228 return nil, err
229 }
230
231 if err := creds.Verify(anchors, "", time.Time{}); err != nil {
232 return nil, err
233 }
234
235 return creds, nil
236 }
237
View as plain text