kubernetes/kops · critical

verifying PKCS7 signature: %w

Error message

verifying PKCS7 signature: %w

What it means

This wraps p7.Verify() failing after the PKCS#7 structure parsed successfully. Verify checks the message digest and the embedded certificates' signature chain against the trusted root; failure means the signature over the attested payload is cryptographically invalid or the embedded cert does not chain to the expected Azure root. It is returned by parseAndValidatePKCS7Signer to callers in verifyAttestedDocumentWithRootAndFetcher.

Source

Thrown at upup/pkg/fi/cloudup/azure/attest.go:252

	if signature == "" {
		return nil, nil, fmt.Errorf("empty PKCS7 signature")
	}

	sigBytes, err := base64.StdEncoding.DecodeString(signature)
	if err != nil {
		return nil, nil, fmt.Errorf("decoding PKCS7 signature: %w", err)
	}
	klog.V(4).Infof("Decoded PKCS7 signature (%d bytes)", len(sigBytes))

	p7, err := pkcs7.Parse(sigBytes)
	if err != nil {
		return nil, nil, fmt.Errorf("parsing PKCS7 signature: %w", err)
	}
	klog.V(8).Infof("Parsed PKCS7 structure with %d embedded certificate(s)", len(p7.Certificates))

	// Verify the PKCS7 signature against the embedded leaf certificate.
	if err := p7.Verify(); err != nil {
		return nil, nil, fmt.Errorf("verifying PKCS7 signature: %w", err)
	}
	klog.V(4).Infof("PKCS7 self-signature verified")

	signer := p7.GetOnlySigner()
	if signer == nil {
		return nil, nil, fmt.Errorf("PKCS7 signer certificate not found")
	}
	klog.V(8).Infof("PKCS7 signer certificate: subject=%q issuer=%q SANs=%v", signer.Subject, signer.Issuer, signer.DNSNames)
	if err := validateAzureMetadataSignerSAN(signer); err != nil {
		return nil, nil, fmt.Errorf("validating PKCS7 signer SAN: %w", err)
	}
	klog.V(4).Infof("PKCS7 signer SAN validated as Azure metadata endpoint")

	return p7, signer, nil
}

// nonceForBody derives the IMDS attestation nonce from the request body; the shared
// azuremetadata implementation keeps the authenticator and verifier sides identical.

View on GitHub (pinned to 4c8573c808)

Solutions

  1. Check VM system clock (chrony/ntpd) for skew; expired signer certs are the most common cause
  2. Confirm the trusted root and any intermediates supplied to verifyAttestedDocumentWithRootAndFetcher match the current Azure IMDS signing hierarchy
  3. Re-fetch the attested document directly from IMDS (169.254.169.254) bypassing proxies to rule out tampering/truncation
  4. Compare signature verification on a known-good VM to isolate whether the certificate chain or the payload is at fault

Example fix

// before
rootPool := x509.NewCertPool() // missing Azure intermediates
if err := p7.Verify(); err != nil { return err } // "verifying PKCS7 signature: x509: certificate signed by unknown authority"
// after
rootPool := x509.NewCertPool()
rootPool.AddCert(azureRoot)
for _, c := range azureIntermediates {
    rootPool.AddCert(c)
}
if err := p7.Verify(); err != nil { return err }
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-check: ensure the trust root/intermediates are loaded before calling verifyAttestedDocumentWithRootAndFetcher
if rootPool == nil {
    return fmt.Errorf("azure attestation root not initialized")
}

Type guard

func certChainLoaded(pool *x509.CertPool, roots ...*x509.Certificate) bool {
    if pool == nil {
        return false
    }
    for _, r := range roots {
        if r == nil {
            return false
        }
    }
    return true
}

Try / catch

if err := p7.Verify(); err != nil {
    var certErr x509.CertificateInvalidError
    if errors.As(err, &certErr) && errors.Is(certErr.Err, x509.Expired) {
        klog.Warningf("PKCS7 signer cert expired at %v; check VM clock skew (now=%v)", certErr.NotAfter, time.Now().UTC())
    }
    return nil, nil, fmt.Errorf("verifying PKCS7 signature: %w", err)
}

Prevention

When it happens

Trigger: p7.Verify() is invoked on a parsed PKCS7 blob whose SignedData digest does not match the message content, whose signer cert is expired, or whose certificate chain does not validate against the root passed to verifyAttestedDocumentWithRootAndFetcher.

Common situations: Clock skew making the embedded certificate appear expired/not-yet-valid; the attested document was tampered with or replayed from another VM; custom root/fetcher wired with the wrong intermediate (missing Azure intermediate certs); MITM proxy re-signing responses.

Related errors


AI-assisted analysis of kubernetes/kops@4c8573c808 (2026-09-05). Data as JSON: /api/errors/a2b0515e7ceca333. Report an issue: GitHub.