{"record":{"id":"cf3c403eceacd0ee","repo":"spring-projects/spring-security","slug":"failed-to-parse-client-certificate","errorCode":null,"errorMessage":"Failed to parse client certificate","messagePattern":"Failed to parse client certificate","errorType":"exception","errorClass":"BadCredentialsException","httpStatus":null,"severity":"error","filePath":"web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectX500PrincipalExtractor.java","lineNumber":83,"sourceCode":"\tpublic Object extractPrincipal(X509Certificate clientCert) {\n\t\tAssert.notNull(clientCert, \"clientCert cannot be null\");\n\t\tX500Principal principal = clientCert.getSubjectX500Principal();\n\t\tString subjectDN = principal.getName(this.x500PrincipalFormat);\n\t\tthis.logger.debug(LogMessage.format(\"Subject DN is '%s'\", subjectDN));\n\t\tString principalName = getSubject(subjectDN);\n\t\tthis.logger.debug(LogMessage.format(\"Extracted Principal name is '%s'\", principalName));\n\t\treturn principalName;\n\t}\n\n\tprivate List<Rdn> getDns(String subjectDn) {\n\t\ttry {\n\t\t\t// read most-specific first, see gh-19254\n\t\t\tList<Rdn> rdns = new ArrayList<>(new LdapName(subjectDn).getRdns());\n\t\t\tCollections.reverse(rdns);\n\t\t\treturn rdns;\n\t\t}\n\t\tcatch (InvalidNameException ex) {\n\t\t\tthrow new BadCredentialsException(\"Failed to parse client certificate\", ex);\n\t\t}\n\t}\n\n\tprivate String getSubject(String subjectDn) {\n\t\tfor (Rdn rdn : getDns(subjectDn)) {\n\t\t\tString type = rdn.getType();\n\t\t\tif (this.subjectDnType.equals(type)) {\n\t\t\t\treturn String.valueOf(rdn.getValue());\n\t\t\t}\n\t\t}\n\t\tthrow new BadCredentialsException(this.messages.getMessage(\"SubjectX500PrincipalExtractor.noMatching\",\n\t\t\t\tnew Object[] { subjectDn }, \"No matching pattern was found in subject DN: {0}\"));\n\t}\n\n\t@Override\n\tpublic void setMessageSource(MessageSource messageSource) {\n\t\tAssert.notNull(messageSource, \"messageSource cannot be null\");\n\t\tthis.messages = new MessageSourceAccessor(messageSource);","sourceCodeStart":65,"sourceCodeEnd":101,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectX500PrincipalExtractor.java#L65-L101","documentation":"SubjectX500PrincipalExtractor parses the certificate's subject DN as an LDAP name (javax.naming.ldap.LdapName) to walk its RDNs. If the DN string is not a valid LDAP name, InvalidNameException is converted to a BadCredentialsException with the message 'Failed to parse client certificate'.","triggerScenarios":"Calling getDns (indirectly via getSubject/principalName) with a subject DN that LdapName cannot parse — e.g. a DN containing characters that must be escaped (like a CN containing a comma, '+', or quotes) but are not RFC 2253 escaped.","commonSituations":"Certificates with free-form CN values containing special characters (commas in company names, apostrophes in names); unusual attribute type OIDs or legacy DN encodings produced by some CAs; code passing a manually constructed DN string instead of one from getSubjectX500Principal().getName(X500Principal.RFC2253).","solutions":["Inspect the certificate's DN and confirm it is RFC 2253 compliant; reissue the certificate with a properly escaped DN if it is not.","Feed the extractor the RFC 2253 form of the DN (cert.getSubjectX500Principal().getName(X500Principal.RFC2253)) rather than the legacy getSubjectDN().getName().","Fall back to SubjectDnX509PrincipalExtractor with a regex if the DNs in your PKI are not LDAP-parseable.","Catch BadCredentialsException around authentication so a malformed certificate results in a 401 rather than a server error."],"exampleFix":"// before\nString dn = cert.getSubjectDN().getName(); // legacy, may be unparseable\n// after\nString dn = cert.getSubjectX500Principal().getName(X500Principal.RFC2253); // properly escaped for LdapName","handlingStrategy":"try-catch","validationCode":"try {\n    new javax.naming.ldap.LdapName(dn);\n} catch (javax.naming.InvalidNameException e) {\n    // DN not RFC 2253 parseable — refuse before authentication\n}","typeGuard":"boolean isParseableDn(String dn) {\n    try { new LdapName(dn); return true; } catch (InvalidNameException e) { return false; }\n}","tryCatchPattern":"try {\n    return extractor.extractPrincipal(cert);\n} catch (BadCredentialsException e) {\n    log.error(\"Unparseable subject DN on client cert\", e);\n    throw new BadCredentialsException(\"Client certificate rejected\");\n}","preventionTips":["Use getSubjectX500Principal().getName(X500Principal.RFC2253) as the DN source","Issue certificates with properly escaped CN values (no raw commas/quotes)","Test with real-world DNs containing special characters","Have a fallback extractor for legacy DN formats"],"tags":["x509","ldap","dn-parsing","authentication","spring-security"],"backgroundTag":"invalid-argument-format","analyzedSha":"96852e8860138a482cb13d1479573f24ff6443c6","analyzedAt":"2026-09-10T23:25:23.477Z","contentChangedAt":"2026-09-10T23:25:23.477Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}