Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions handshake.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ import (
"golang.org/x/net/idna"
)

var (
// ErrCertNotAvailable is returned when no certificates are available for given name.
ErrCertNotAvailable = errors.New("no certificates available")
// ErrCertTimeout is returned when obtaining a certificate times out.
ErrCertTimeout = errors.New("certificate timeout")
)

// GetCertificate gets a certificate to satisfy clientHello. In getting
// the certificate, it abides the rules and settings defined in the Config
// that matches clientHello.ServerName. It tries to get certificates in
Expand Down Expand Up @@ -238,7 +245,7 @@ func DefaultCertificateSelector(hello *tls.ClientHelloInfo, choices []Certificat
return choices[0], nil
}
if len(choices) == 0 {
return Certificate{}, fmt.Errorf("no certificates available")
return Certificate{}, ErrCertNotAvailable
}

// Slow path: There are choices, so we need to check each of them.
Expand Down Expand Up @@ -306,7 +313,7 @@ func (cfg *Config) getCertDuringHandshake(ctx context.Context, hello *tls.Client
timeout := time.NewTimer(2 * time.Minute)
select {
case <-timeout.C:
return Certificate{}, fmt.Errorf("timed out waiting to load certificate for %s", name)
return Certificate{}, fmt.Errorf("%w: timed out waiting to load certificate for %s", ErrCertTimeout, name)
case <-ctx.Done():
timeout.Stop()
return Certificate{}, ctx.Err()
Expand Down Expand Up @@ -412,7 +419,7 @@ func (cfg *Config) getCertDuringHandshake(ctx context.Context, hello *tls.Client
zap.Bool("load_or_obtain_if_necessary", loadOrObtainIfNecessary),
zap.Bool("on_demand", cfg.OnDemand != nil))

return Certificate{}, fmt.Errorf("no certificate available for '%s'", name)
return Certificate{}, fmt.Errorf("%w: name: %s", ErrCertNotAvailable, name)
}

// loadCertFromStorage loads the certificate for name from storage and maintains it
Expand Down Expand Up @@ -487,7 +494,7 @@ func (cfg *Config) checkIfCertShouldBeObtained(ctx context.Context, name string,
}
if len(cfg.OnDemand.hostAllowlist) > 0 {
if _, ok := cfg.OnDemand.hostAllowlist[name]; !ok {
return fmt.Errorf("certificate for '%s' is not managed", name)
return fmt.Errorf("%w: certificate for '%s' is not managed", ErrCertNotAvailable, name)
}
}
}
Expand Down Expand Up @@ -522,7 +529,7 @@ func (cfg *Config) obtainOnDemandCertificate(ctx context.Context, hello *tls.Cli
timeout := time.NewTimer(2 * time.Minute)
select {
case <-timeout.C:
return Certificate{}, fmt.Errorf("timed out waiting to obtain certificate for %s", name)
return Certificate{}, fmt.Errorf("%w: timed out waiting to obtain certificate for %s", ErrCertTimeout, name)
case <-wait:
timeout.Stop()
}
Expand Down
5 changes: 5 additions & 0 deletions handshake_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package certmagic
import (
"crypto/tls"
"crypto/x509"
"errors"
"net"
"testing"
)
Expand Down Expand Up @@ -78,6 +79,10 @@ func TestGetCertificate(t *testing.T) {
// When cache is NOT empty but there's no SNI
if _, err := cfg.GetCertificate(helloNoSNI); err == nil {
t.Errorf("Expected TLS allert when no SNI and no DefaultServerName, but got: %v", err)
} else {
if !errors.Is(err, ErrCertNotAvailable) {
t.Errorf("Expected ErrCertNotAvailable, got: %v", err)
}
}

// When no certificate matches, raise an alert
Expand Down