apereo/cas · error · CredentialsException

Unable to accept certificate

Error message

Unable to accept certificate

What it means

Thrown by the X.509 client-certificate authenticator when one or more certificate attributes (configured required attributes) extracted from the presented TLS certificate do not match the acceptable values defined by the registered service. The certificate itself parsed and a profile was built, but attribute-level policy rejected it.

Solutions

  1. Compare the actual certificate attributes (openssl x509 -text) against the required attribute map in the registered service and update the registry to match the real DN format
  2. Normalize DN configuration (ordering of RDNs, spacing) to the exact string CAS extracts
  3. Reissue or obtain a client certificate containing the required attributes (e.g. correct email SAN)
  4. Relax or correct the acceptable-attribute configuration if it is unnecessarily strict

Example fix

// before
"requiredAttributes": { "subjectDN": "CN=Alice, OU=Org, O=Company" }
// after
"requiredAttributes": { "subjectDN": "CN=Alice,OU=Org,O=Company" }
Defensive patterns

Strategy: validation

Validate before calling

const cert = req.socket.getPeerCertificate();
if (!cert || !cert.subject) throw new Error('mTLS client certificate required and must carry the attributes configured on the service');

Prevention

When it happens

Trigger: Mutual-TLS request presents a client certificate whose subject/issuer/SAN/RFC822 attributes fail isAcceptableX509Attribute checks against the registered service's required certificate attribute map — any non-matching attribute makes the allMatch predicate false.

Common situations: Certificate reissued with a different subject DN or email SAN than what the service requires; DN formatting differences (spaces, ordering, escaped characters) breaking exact-string comparison; service registry configured with overly strict expected attribute values; expired/reissued certs after CA migration.

Understand the failure class

Background: "You do not have permission" / 403 Forbidden errors: authenticated but not allowed — causes and fixes across open-source libraries — this error's family across 31 libraries.

Related errors


AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08). Data as JSON: /api/errors/0a8397089d3c5486. Report an issue: GitHub.

Appendix: source

Thrown at support/cas-server-support-oauth-core-api/src/main/java/org/apereo/cas/support/oauth/authenticator/OAuth20X509Authenticator.java:79

            val profile = result.get().getUserProfile();
            val certificate = ((X509Credentials) credentials).getCertificate();
            val digest = EncodingUtils.encodeBase64(DigestUtils.digest("SHA-256", certificate.getPublicKey().getEncoded()));
            profile.addAttribute(OAuth20Constants.X509_CERTIFICATE_DIGEST, digest);
            profile.addAttribute(AuthenticationManager.AUTHENTICATION_METHOD_ATTRIBUTE, "X.509");
            profile.addAttribute(OAuth20Constants.CLIENT_ID, registeredService.getClientId());

            val attributeMap = CollectionUtils.<String, String>wrap(
                "x509-sanEmail", registeredService.getTlsClientAuthSanEmail(),
                "x509-sanDNS", registeredService.getTlsClientAuthSanDns(),
                "x509-sanIP", registeredService.getTlsClientAuthSanIp(),
                "x509-sanURI", registeredService.getTlsClientAuthSanUri()
            );
            val accepted = attributeMap
                .entrySet()
                .stream()
                .allMatch(entry -> isAcceptableX509Attribute(profile, entry.getKey(), entry.getValue()));
            if (!accepted) {
                throw new CredentialsException("Unable to accept certificate");
            }
        }

        return result;
    }

    protected @Nullable OAuthRegisteredService locateRegisteredService(
        final CallContext ctx, final Credentials credentials) {
        if (credentials instanceof final X509Credentials x509Credentials) {
            val certificate = x509Credentials.getCertificate();
            val subjectAltNames = FunctionUtils.doUnchecked(certificate::getSubjectAlternativeNames);
            if (subjectAltNames != null) {
                val spiffeEntries = new ArrayList<String>();
                subjectAltNames.forEach(altName -> {
                    altName.stream()
                        .filter(String.class::isInstance)
                        .map(String.class::cast)
                        .filter(name -> Strings.CI.startsWith(name, SUBJECT_ALT_NAME_SPIFFE_PREFIX))

View on GitHub (pinned to e7288fc434)