@@ -59,13 +59,35 @@ func (lc *LoadedCertificate) IsValid() bool {
5959 return lc .valid
6060}
6161
62+ // loadCertificateConfig is an internal config for LoadCertificate.
63+ type loadCertificateConfig struct {
64+ // ignoreFilePermissions indicates if file permissions should be ignored during load.
65+ ignoreFilePermissions bool
66+ }
67+
68+ // LoadCertificateOpt are functions to change the behavior of LoadCertificate.
69+ type LoadCertificateOpt func (* loadCertificateConfig )
70+
71+ // WithLoadCertificateIgnoreFilePermissions instructs LoadCertificate to ignore file permissions
72+ // if ignore is true.
73+ func WithLoadCertificateIgnoreFilePermissions (ignore bool ) LoadCertificateOpt {
74+ return func (c * loadCertificateConfig ) {
75+ c .ignoreFilePermissions = ignore
76+ }
77+ }
78+
6279// LoadCertificate loads a key pair from certPath and keyPath, performing several checks
6380// along the way. If any checks fail or an error occurs loading the files, then an error is returned.
6481// If keyPath is empty, then certPath is assumed to contain both the certificate and the private key.
6582// Only trusted input (standard configuration files) should be used for certPath and keyPath.
66- func LoadCertificate (certPath , keyPath string ) (LoadedCertificate , error ) {
83+ func LoadCertificate (certPath , keyPath string , opts ... LoadCertificateOpt ) (LoadedCertificate , error ) {
6784 fail := func (err error ) (LoadedCertificate , error ) { return LoadedCertificate {valid : false }, err }
6885
86+ config := loadCertificateConfig {}
87+ for _ , o := range opts {
88+ o (& config )
89+ }
90+
6991 if certPath == "" {
7092 return fail (fmt .Errorf ("LoadCertificate: certificate: %w" , ErrPathEmpty ))
7193 }
@@ -95,9 +117,11 @@ func LoadCertificate(certPath, keyPath string) (LoadedCertificate, error) {
95117 }
96118 }()
97119
98- if err := file .VerifyFilePermissivenessF (f , maxPerms ); err != nil {
99- // VerifyFilePermissivenessF includes a lot context in its errors. No need to add duplicate here.
100- return nil , fmt .Errorf ("LoadCertificate: %w" , err )
120+ if ! config .ignoreFilePermissions {
121+ if err := file .VerifyFilePermissivenessF (f , maxPerms ); err != nil {
122+ // VerifyFilePermissivenessF includes a lot context in its errors. No need to add duplicate here.
123+ return nil , fmt .Errorf ("LoadCertificate: %w" , err )
124+ }
101125 }
102126 data , err := io .ReadAll (f )
103127 if err != nil {
@@ -120,7 +144,7 @@ func LoadCertificate(certPath, keyPath string) (LoadedCertificate, error) {
120144 // Create key pair from loaded data
121145 cert , err := tls .X509KeyPair (certData , keyData )
122146 if err != nil {
123- return fail (fmt .Errorf ("error loading x509 key pair: %w" , err ))
147+ return fail (fmt .Errorf ("error loading x509 key pair (%q / %q) : %w" , certPath , keyPath , err ))
124148 }
125149
126150 // Parse the first X509 certificate in cert's chain.
@@ -129,11 +153,11 @@ func LoadCertificate(certPath, keyPath string) (LoadedCertificate, error) {
129153 if err != nil {
130154 // This should be impossible to reach because `tls.X509KeyPair` will fail
131155 // if the leaf certificate can't be parsed.
132- return fail (fmt .Errorf ("error parsing leaf certificate: %w" , err ))
156+ return fail (fmt .Errorf ("error parsing leaf certificate (%q / %q) : %w" , certPath , keyPath , err ))
133157 }
134158 if leaf == nil {
135159 // This shouldn't happen, but we should be extra careful with TLS certs.
136- return fail (fmt .Errorf ("error parsing leaf certificate: %w" , ErrCertificateNil ))
160+ return fail (fmt .Errorf ("error parsing leaf certificate (%q / %q) : %w" , certPath , keyPath , ErrCertificateNil ))
137161 }
138162
139163 return LoadedCertificate {
@@ -157,12 +181,18 @@ type TLSCertLoader struct {
157181 // certificateCheckInterval determines the duration between each certificate check.
158182 certificateCheckInterval time.Duration
159183
184+ // ignoreFilePermissions is true if file permission checks should be bypassed.
185+ ignoreFilePermissions bool
186+
160187 // closeOnce is used to close closeCh exactly one time.
161188 closeOnce sync.Once
162189
163190 // closeCh is used to trigger closing the monitor.
164191 closeCh chan struct {}
165192
193+ // monitorStartWg can be used to detect if the monitor goroutine has started.
194+ monitorStartWg sync.WaitGroup
195+
166196 // mu protects all members below.
167197 mu sync.Mutex
168198
@@ -181,28 +211,35 @@ type TLSCertLoader struct {
181211
182212type TLSCertLoaderOpt func (* TLSCertLoader )
183213
184- // WithExpirationAdvanced sets the how far ahead a CertLoader will
214+ // WithCertLoaderExpirationAdvanced sets the how far ahead a CertLoader will
185215// warn about a certificate that is about to expire.
186- func WithExpirationAdvanced (d time.Duration ) TLSCertLoaderOpt {
216+ func WithCertLoaderExpirationAdvanced (d time.Duration ) TLSCertLoaderOpt {
187217 return func (cl * TLSCertLoader ) {
188218 cl .expirationAdvanced = d
189219 }
190220}
191221
192- // WithCertificateCheckInterval sets how often to check for certificate expiration.
193- func WithCertificateCheckInterval (d time.Duration ) TLSCertLoaderOpt {
222+ // WithCertLoaderCertificateCheckInterval sets how often to check for certificate expiration.
223+ func WithCertLoaderCertificateCheckInterval (d time.Duration ) TLSCertLoaderOpt {
194224 return func (cl * TLSCertLoader ) {
195225 cl .certificateCheckInterval = d
196226 }
197227}
198228
199- // WithLogger assigns a logger for to use.
200- func WithLogger (logger * zap.Logger ) TLSCertLoaderOpt {
229+ // WithCertLoaderLogger assigns a logger for to use.
230+ func WithCertLoaderLogger (logger * zap.Logger ) TLSCertLoaderOpt {
201231 return func (cl * TLSCertLoader ) {
202232 cl .logger = logger
203233 }
204234}
205235
236+ // WithCertLoaderIgnoreFilePermissions skips file permission checking when loading certificates.
237+ func WithCertLoaderIgnoreFilePermissions (ignore bool ) TLSCertLoaderOpt {
238+ return func (cl * TLSCertLoader ) {
239+ cl .ignoreFilePermissions = ignore
240+ }
241+ }
242+
206243// NewTLSCertLoader creates a TLSCertLoader loaded with the certifcate found in certPath and keyPath.
207244// Only trusted input (standard configuration files) should be used for certPath and keyPath.
208245// If the certificate can not be loaded, an error is returned. On success, a monitor is setup to
@@ -230,10 +267,8 @@ func NewTLSCertLoader(certPath, keyPath string, opts ...TLSCertLoaderOpt) (rCert
230267 }
231268
232269 // Start monitoring certificate.
233- var monitorWg sync.WaitGroup
234- monitorWg .Add (1 )
235- go cl .monitorCert (& monitorWg )
236- monitorWg .Wait ()
270+ cl .monitorStartWg .Add (1 )
271+ go cl .monitorCert (& cl .monitorStartWg )
237272
238273 return cl , nil
239274}
@@ -308,16 +343,22 @@ func (cl *TLSCertLoader) GetCertificate(*tls.ClientHelloInfo) (*tls.Certificate,
308343// certificate.
309344func (cl * TLSCertLoader ) GetClientCertificate (cri * tls.CertificateRequestInfo ) (* tls.Certificate , error ) {
310345 if cri == nil {
311- return new (tls.Certificate ), ErrCertificateRequestInfoNil
346+ return new (tls.Certificate ), fmt . Errorf ( "tls client: %w" , ErrCertificateRequestInfoNil )
312347 }
313348 cert := cl .Certificate ()
314349 if cert == nil {
315- return new (tls.Certificate ), ErrCertificateNil
350+ return new (tls.Certificate ), fmt . Errorf ( "tls client: %w" , ErrCertificateNil )
316351 }
317- if err := cri .SupportsCertificate (cert ); err != nil {
318- return new (tls.Certificate ), err
352+
353+ // Will our certificate be accepted by server?
354+ if err := cri .SupportsCertificate (cert ); err == nil {
355+ return cert , nil
319356 }
320- return cert , nil
357+
358+ // We don't have a certificate that would be accepted by the server. Don't return an error.
359+ // This replicates Go's behavior when tls.Config.Certificates is used instead of GetClientCertificate
360+ // and gives a better error on both the client and server side.
361+ return new (tls.Certificate ), nil
321362}
322363
323364// Leaf returns the parsed x509 certificate of the currently loaded certificate.
@@ -328,13 +369,17 @@ func (cl *TLSCertLoader) Leaf() *x509.Certificate {
328369 return cl .leaf
329370}
330371
372+ func (cl * TLSCertLoader ) loadCertificate (certPath , keyPath string ) (LoadedCertificate , error ) {
373+ return LoadCertificate (certPath , keyPath , WithLoadCertificateIgnoreFilePermissions (cl .ignoreFilePermissions ))
374+ }
375+
331376// Load loads the certificate at the given certificate path and private keyfile path.
332377// Only trusted input (standard configuration files) should be used for certPath and keyPath.
333378func (cl * TLSCertLoader ) Load (certPath , keyPath string ) (rErr error ) {
334379 log , logEnd := logger .NewOperation (cl .logger , "Loading TLS certificate" , "tls_load_cert" , zap .String ("cert" , certPath ), zap .String ("key" , keyPath ))
335380 defer logEnd ()
336381
337- loadedCert , err := LoadCertificate (certPath , keyPath )
382+ loadedCert , err := cl . loadCertificate (certPath , keyPath )
338383
339384 cl .mu .Lock ()
340385 defer cl .mu .Unlock ()
@@ -365,7 +410,7 @@ func (cl *TLSCertLoader) Load(certPath, keyPath string) (rErr error) {
365410// If the certificate can be loaded, a function that will apply the certificate reload is
366411// returned. Otherwise, an error is returned.
367412func (cl * TLSCertLoader ) PrepareLoad (certPath , keyPath string ) (func () error , error ) {
368- loadedCert , err := LoadCertificate (certPath , keyPath )
413+ loadedCert , err := cl . loadCertificate (certPath , keyPath )
369414 if err != nil {
370415 return nil , err
371416 }
@@ -457,6 +502,12 @@ func (cl *TLSCertLoader) checkCurrentCert() {
457502 }
458503}
459504
505+ // WaitForMonitorStart will wait for the certificate monitor goroutine to start. This is mainly useful
506+ // for tests to avoid race conditions.
507+ func (cl * TLSCertLoader ) WaitForMonitorStart () {
508+ cl .monitorStartWg .Wait ()
509+ }
510+
460511// monitorCert periodically logs errors with the currently loaded certificate.
461512func (cl * TLSCertLoader ) monitorCert (wg * sync.WaitGroup ) {
462513 cl .logger .Info ("Starting TLS certificate monitor" )
0 commit comments