【问题标题】:Parsing the CN out of a certificate DN [duplicate]从证书 DN 中解析 CN [重复]
【发布时间】:2011-12-17 12:31:35
【问题描述】:

首先让我说明这是一个美学问题。我已经解决了自己的问题,我只是对更好的方法感到好奇。

所以,我有一个证书 DN,如下所示:

CN=Jimmy Blooptoop,OU=Someplace,OU=Employees,DC=Bloopsoft-Inc

现在,我想从中获取 CN。在 java 中,除了从 X509 证书中获取完整的 DN 外,没有原生支持,无需使用诸如 bouncy castle 之类的第 3 方库——我无法使用。所以我必须把它解析出来,这不是什么大问题。唯一让它有点棘手的是CN并不总是被格式化为<first name> <last name>。通常情况下,它实际上是<last name>, <first name> <middle initial>。因此,在上面的示例中,CN 可以是 Jimmy Blooptoop 或 Blooptoop, Jimmy J(当然是 Joop 的缩写)。

在阅读了有关正则表达式的内容后,我写了以下内容,效果很好:

Matcher m = Pattern.compile("CN=[A-Za-z]*[, ]*[ A-Za-z]*").matcher(dn);
if (m.find())
  cn = m.group();

我只是好奇是否有一些看起来不像废话的表达方式。我相当有信心,因为我只是在阅读了正则表达式的介绍后解决了这个问题。

【问题讨论】:

    标签: java regex


    【解决方案1】:

    javax.naming.ldap.LdapName 怎么样?

    String dn = "CN=Jimmy Blooptoop,OU=Someplace,OU=Employees,DC=Bloopsoft-Inc";
    LdapName ln = new LdapName(dn);
    
    for(Rdn rdn : ln.getRdns()) {
        if(rdn.getType().equalsIgnoreCase("CN")) {
            System.err.println("CN is: " + rdn.getValue());
            break;
        }
    }
    

    这不是最漂亮的界面,因为缺少像 LdapName#getByType(String) 这样的东西,但它让您不必考虑 DN 可能具有哪些奇怪的功能。

    【讨论】:

    • 绝对是比正则表达式更可靠的解决方案,使用 BouncyCastle API 是另一种选择,请参阅how to extract cn from x509certificate in java
    • 稍微好一点的解决方案:for (Rdn rdn : ln.getRdns()) { ... }
    • @gregschlom:你说的太对了。我不知道我为什么要使用旧学校循环。
    • 这种方法对包含多值 RDN 的 DN 有限制,例如:OU=Sales+CN=J. Smith,O=Widget Inc.,C=US。在这种情况下,您可以使用 Rdn.toAttributes().getAll() 并遍历返回的枚举。
    【解决方案2】:

    您可以使用 Spring Frameworks LdapUtils 以简洁的方式提取 CN,如下所示:

    String cn = LdapUtils.getStringValue(new LdapName(group),"cn");
    

    不使用 Spring Framework,如下所示:

    String cn = (String)new LdapName(group).getRdns().stream().filter(rdn -> rdn.getType().equalsIgnoreCase("CN")).findFirst().get().getValue();
    

    【讨论】:

      【解决方案3】:

      您可以避免使用“蹩脚”的表达方式。如果你已经有了DN,你可以拆分String然后找到CN。例如:

      String dn = "CN=Jimmy Blooptoop,OU=Someplace,OU=Employees,DC=Bloopsoft-Inc";
      String[] split = dn.split(","); 
      for (String x : split) {
          if (x.contains("CN=")) {
              System.out.println(x.trim());
          }
      }
      

      【讨论】:

      • 虽然正则表达式有效,但它们通常使用起来很昂贵。对于这种简单的字符串格式,逗号分隔,字符串拆分会更好。不幸的是,OP 已经声明这些值有时也可以包含逗号,这使得该解决方案不起作用。
      • @JesseWebb:String#split() 也会创建一个正则表达式。
      • @musiKk - 感谢您指出这一点,我不知道。我想这两种解决方案都同样昂贵。
      • 将失败,例如,CN=Go Daddy Root Certificate Authority - G2, O="GoDaddy.com, Inc.", L=Scottsdale, ST=Arizona, C=US
      • CN=Development Cyanogen Application,OU=Release Management,O=Cyanogen\, Inc.,L=Seattle,ST=Washington,C=US上也会失败
      【解决方案4】:

      看起来这工作正常

      CertificateFactory fact = CertificateFactory.getInstance("X.509");
      FileInputStream is = new FileInputStream ("cert.pem");
      X509Certificate cert = (X509Certificate) fact.generateCertificate(is);
      String cn = ((X500Name)cert.getSubjectDN()).getCommonName()
      

      【讨论】:

      • 也可以用于 String issuerCN = ((X500Name)x509cert.getIssuerDN()).getCommonName();这非常有用,特别是如果源证书中的颁发者(或主题)的 CN 包含特殊字符,如逗号、反斜杠或引号。对我来说,它与导入 sun.security.x509.X500Name 一起使用。
      • 这应该是公认的答案!
      【解决方案5】:

      如果您使用的是 Spring Framework,您可以使用 DistinguishedName,如下所示:

      String path = "CN=Jimmy Blooptoop,OU=Someplace,OU=Employees,DC=Bloopsoft-Inc";
      DistinguishedName dn = new DistinguishedName(path);
      
      String cn = dn.getValue("cn"); // Jimmy Blooptoop
      

      【讨论】:

      • DistinguishedName 现在已弃用,取而代之的是使用 javax.naming.ldap.LdapName。但是您仍然可以使用 Spring 使用 LdapUtils.getStringValue(ldapName, "cn") 从中提取名称
      【解决方案6】:

      您不应该使用上面提供的正则表达式。这些正则表达式的问题是您不会遵循RFC 4514 并且不会取消转义以前转义的符号,并且可能会丢失或误解专有名称。 按照 musiKk 的建议,改用 LdapName 类来正确解析可分辨名称。

      【讨论】:

        【解决方案7】:

        我像这样解析一个 DN。您将所有属性作为属性:

        Principal principal = cert.getSubjectDN();
        Properties prop = new Properties();
        prop.load(new StringReader(principal.getName().replaceAll(",", "\n")));
        prop.list(System.out);
        String CN= props.get("CN");
        

        但它不适用于像 ou 或 dc 这样的重复元素。

        【讨论】:

        • 与其他普通的正则表达式/拆分“解决方案”一样,这将在引用部分中的逗号上失败,例如CN=Go Daddy Root Certificate Authority - G2, O="GoDaddy.com, Inc.", L=Scottsdale, ST=Arizona, C=US
        【解决方案8】:

        正则表达式使用起来相当昂贵。对于这样一个简单的任务,它可能是一个过度杀戮。相反,您可以使用简单的字符串拆分:

        String dn = ((X509Certificate) certificate).getIssuerDN().getName();
        String CN = getValByAttributeTypeFromIssuerDN(dn,"CN=");
        
        private String getValByAttributeTypeFromIssuerDN(String dn, String attributeType)
        {
            String[] dnSplits = dn.split(","); 
            for (String dnSplit : dnSplits) 
            {
                if (dnSplit.contains(attributeType)) 
                {
                    String[] cnSplits = dnSplit.trim().split("=");
                    if(cnSplits[1]!= null)
                    {
                        return cnSplits[1].trim();
                    }
                }
            }
            return "";
        }
        

        【讨论】:

        • 与其他普通的正则表达式/拆分“解决方案”一样,这将在引用部分中的逗号上失败,例如CN=Go Daddy Root Certificate Authority - G2, O="GoDaddy.com, Inc.", L=Scottsdale, ST=Arizona, C=US
        【解决方案9】:

        如果我理解正确,这应该会产生相同的结果,只是看起来更具可读性。 \w 匹配任何单词字符。

        "CN=[\\w]*[., ]+[\\w ]*"
        

        如果你想要更灵活的东西,你可以这样做:

        Matcher m = Pattern.compile("(CN=.*?),[A-Z]{2}=").matcher(dn);
        if (m.find())
          cn = m.group(1);
        

        匹配 "CN=" 和 ",XX=" 之间的任何内容,其中 XX 是两个大写字母。 group(1) 返回第一组(括号内的匹配项)。

        正则表达式并不像看起来那么难!

        【讨论】:

        • 但是在引用部分的逗号上会失败,例如CN=Go Daddy Root Certificate Authority - G2, O="GoDaddy.com, Inc.", L=Scottsdale, ST=Arizona, C=US
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-10-20
        • 1970-01-01
        • 2019-12-17
        • 1970-01-01
        相关资源
        最近更新 更多