hashicorp/nomad · error

certificate has expired or is not yet valid

Error message

certificate has expired or is not yet valid

What it means

getCassCert parses a PEM-encoded X.509 certificate and verifies the wall-clock time falls within the certificate's validity window (NotBefore..NotAfter). It returns this error when time.Now() is outside that window, meaning the certificate cannot currently be trusted for signing the OIDC client-assertion private key JWT.

Source

Thrown at lib/auth/oidc/client_assertion.go:184

	if k.PemCert != "" {
		source = "PemCert"
		bts = []byte(k.PemCert)
	}

	// ensure newlines around pem header/footer
	bts = newlineHeaders(bts)

	block, _ := pem.Decode(bts)
	if block == nil {
		return nil, fmt.Errorf("failed to decode %s PEM block", source)
	}
	cert, err := x509.ParseCertificate(block.Bytes)
	if err != nil {
		return nil, fmt.Errorf("failed to parse %s bytes: %w", source, err)
	}
	now := time.Now()
	if now.Before(cert.NotBefore) || now.After(cert.NotAfter) {
		return nil, errors.New("certificate has expired or is not yet valid")
	}
	return cert, nil
}

// hashKeyID derives a "certificate thumbprint" that the OIDC provider uses
// to find the certificate to verify the private key JWT signature.
// https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.7
func hashKeyID(cert *x509.Certificate, header structs.OIDCClientAssertionKeyIDHeader) (string, error) {
	var hasher hash.Hash
	switch header {
	case structs.OIDCClientAssertionHeaderX5t:
		if fips140.Enabled() {
			return "", errors.New("x5t assertion headers use SHA-1, which is forbidden in FIPS-140 mode")
		}

		hasher = sha1.New()
	case structs.OIDCClientAssertionHeaderX5tS256:
		hasher = sha256.New()

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Renew/reissue the OIDC client signing certificate and update the auth method config with the new PEM.
  2. Check server clock sync (NTP) — a skewed clock makes valid certs appear expired or not-yet-valid.
  3. If a new cert was just installed, confirm its NotBefore date has passed and redeploy after activation.
  4. Use a longer-lived certificate or automate rotation before NotAfter to prevent recurrence.

Example fix

// before (expired cert in config)
key_source = "cert" // cert PEM expired 2025-01-01
// after
# renew certificate, then update auth method:
key_source = "cert" # PEM with NotAfter in the future
# or verify time:
# openssl x509 -in cert.pem -noout -dates
Defensive patterns

Strategy: validation

Validate before calling

pemBytes, _ := os.ReadFile("client-cert.pem")
blk, _ := pem.Decode(pemBytes)
cert, err := x509.ParseCertificate(blk.Bytes)
if err != nil { return err }
now := time.Now()
if now.Before(cert.NotBefore) || now.After(cert.NotAfter) {
    return fmt.Errorf("OIDC client cert not currently valid: notBefore=%s notAfter=%s", cert.NotBefore, cert.NotAfter)
}

Prevention

When it happens

Trigger: BuildClientAssertionJWT calls getCassCert with the configured signing certificate PEM while (a) the certificate's NotAfter timestamp has passed, or (b) the certificate's NotBefore is in the future (e.g. clock skew or a not-yet-activated cert).

Common situations: Nomad ACL auth method configured with an OIDC client certificate that expired and nobody rotated it; server clock skew after NTP failure making a valid cert appear expired; a newly issued certificate deployed before its NotBefore date; long-lived certs issued years ago in test environments.

Understand the failure class

Related errors


AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04). Data as JSON: /api/errors/025854ce90c02138. Report an issue: GitHub.