【Java】X.500 識別名(X500Principal)の文字列形式をCNやOUなど個別に扱えるようにする

■ はじめに

https://blogs.yahoo.co.jp/dk521123/37097725.html
で使用した以下のメソッド
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
 + X509Certificate.getIssuerX500Principal()
 + X509Certificate.getSubjectX500Principal()
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
の戻り値 X500Principal が思った以上にメソッドが少なく、
例えば、「一般名 / CN(Common Name)」だけ個別に取得したくてもできない。

そこで、CNやOUなどの値を個別に扱えるように、自作してみた

■ サンプル

DistinguishedNameType.java

import java.text.MessageFormat;
import java.util.Map;
import java.util.TreeMap;

/**
 * 識別名(DN: Distinguished Name)タイプ.
 * 
 * @see https://msdn.microsoft.com/en-us/library/aa366101(v=vs.85).aspx
 * @see http://www.atmarkit.co.jp/ait/articles/0612/23/news012.html
 * @see https://docs.oracle.com/cd/E19146-01/820-0875/geezw/index.html
 */
public enum DistinguishedNameType implements Comparable<DistinguishedNameType> {
  /** 一般名 / CN(Common Name). */
  COMMON_NAME("CN"),
  /** 部門名 / OU(Organizational Unit). */
  ORGANIZATIONAL_UNIT("OU"),
  /** 組織名 / O(Organization). */
  ORGANIZATION("O"),
  /** ストリート名 / STREET(Street). */
  STREET("STREET"),
  /** 市区町村名 / L(Locality). */
  LOCALITY("L"),
  /** 都道府県名 / ST(state Or Province Name). */
  STATE_OR_PROVINCE_NAME("ST"),
  /** 都道府県名 / S(State). */
  STATE("S"),
  /** 国名 / C(Country). */
  COUNTRY("C"),
  /** ドメイン / DC( Domain Component). */
  DOMAIN_COMPONENT("DC"),
  /** ユーザID / UID(user id). */
  USER_ID("UID"),
  /** E-mail / E(E-mail). */
  EMAIL("E"),
  /** その他. */
  OTHERS("");

  /** フォーマット. */
  private static final String FORMAT = "{0} = {1}";
  /** 改行. */
  private static final String NEW_LINE = System.getProperty("line.separator");
  /** 変換マッパー. */
  private static Map<String, DistinguishedNameType> convertMapper;
  /** コード. */
  private String code;

  static {
    convertMapper = new TreeMap<String, DistinguishedNameType>();
    for (DistinguishedNameType type : DistinguishedNameType.values()) {
      convertMapper.put(type.getCode(), type);
    }
  }

  /**
   * コンストラクタ.
   * 
   * @param code
   *          コード
   */
  private DistinguishedNameType(String code) {
    this.code = code;
  }

  /**
   * コードを取得する.
   * 
   * @return コード
   */
  public String getCode() {
    return this.code;
  }

  /**
   * X.500 識別名のMap化.
   * @param targetValue RFC 2253 で定義された形式で、X.500 識別名の文字列形式
   * @return  X.500 識別名のMap形式
   * @see https://docs.oracle.com/javase/jp/6/api/javax/security/auth/x500/X500Principal.html#getName()
   * @see https://www.ipa.go.jp/security/rfc/RFC2253JA.html
   */
  public static Map<DistinguishedNameType, String> parse(String targetValue) {
    if (targetValue == null) {
      return null;
    }

    Map<DistinguishedNameType, String> returnValues = new TreeMap<DistinguishedNameType, String>();

    String[] attributes = targetValue.split("(?<!\\\\),");
    for (String attribute : attributes) {
      String[] parts = attribute.split("(?<!\\\\)=");
      String type = parts[0];
      if (type == null || type.isEmpty() || !convertMapper.containsKey(type)) {
        String value = returnValues.get(DistinguishedNameType.OTHERS);
        if (value == null) {
          returnValues.put(DistinguishedNameType.OTHERS, attribute);
        } else {
          returnValues.put(DistinguishedNameType.OTHERS, value + NEW_LINE + attribute);
        }
      } else {
        if (parts[1] != null) {
          DistinguishedNameType targetType = convertMapper.get(type);
          // 「,」「\」がエスケープされている(「\,」「\\」)ので、それを取り除く必要がある
          String value = parts[1].replaceAll("\\\\,", ",").replaceAll("\\\\\\\\", "\\\\");
          returnValues.put(targetType, value);
        }
      }
    }
    return returnValues;
  }

  /**
   * 文字列化.
   * @param targetValue RFC 2253 で定義された形式で、X.500 識別名の文字列形式
   * @return テキストデータ
   * @see https://docs.oracle.com/javase/jp/6/api/javax/security/auth/x500/X500Principal.html#getName()
   * @see https://www.ipa.go.jp/security/rfc/RFC2253JA.html
   */
  public static String toString(String targetValue) {
    Map<DistinguishedNameType, String> targetMaps = DistinguishedNameType.parse(targetValue);
    return DistinguishedNameType.toString(targetMaps);
  }

  /**
   * 文字列化.
   * @param targetMaps X.500 識別名のMap形式
   * @return テキストデータ
   */
  public static String toString(Map<DistinguishedNameType, String> targetMaps) {
    if (targetMaps == null || targetMaps.isEmpty()) {
      return "";
    }

    StringBuilder returnValue = new StringBuilder();
    int index = 0;
    for (Map.Entry<DistinguishedNameType, String> map : targetMaps.entrySet()) {
      if (index != 0) {
        returnValue.append(NEW_LINE);
      }
      DistinguishedNameType targetType = map.getKey();
      if (DistinguishedNameType.OTHERS.equals(targetType)) {
        returnValue.append(map.getValue());
      } else {
        returnValue.append(MessageFormat.format(FORMAT, map.getKey().getCode(), map.getValue()));
      }
      index++;
    }
    return returnValue.toString();
  }
}

Main.java

呼び出し側
import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Enumeration;
import java.util.Map;

public class Main {

  public static void main(String[] args) {
    char[] keyPasswords = "changeit".toCharArray();
    try (FileInputStream fileInputStream = new FileInputStream("./etc/sample.keystore")) {
      KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
      keystore.load(fileInputStream, keyPasswords);
      Enumeration<String> aliases = keystore.aliases();
      while (aliases.hasMoreElements()) {
        String alias = aliases.nextElement();
        Certificate certificate = keystore.getCertificate(alias);
        if (certificate.getType().equals("X.509")) {
          X509Certificate x509Certificate = (X509Certificate) certificate;
          String issuerValue = x509Certificate.getIssuerX500Principal().getName();
          Map<DistinguishedNameType, String> issuerParams = DistinguishedNameType.parse(issuerValue);
          print(issuerParams);

          String subjectValue = x509Certificate.getSubjectX500Principal().getName();
          Map<DistinguishedNameType, String> subjectParams = DistinguishedNameType.parse(subjectValue);
          print(subjectParams);
        }
      }
      System.out.println("Done...");
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }

  private static void print(Map<DistinguishedNameType, String> params) {
    params.forEach((key, value) -> {
      System.out.println(key.getCode() + " " + value);
    });
  }
}

./etc/sample.keystore

https://blogs.yahoo.co.jp/dk521123/36518468.html
を参考に。。。
試験用キーストアファイル
"%JAVA_HOME%\bin\keytool" -genkey -alias sample -keyalg RSA -keypass changeit -storepass changeit -keystore sample.keystore -validity 3650


関連記事

Javaで、キーストアから証明書を追加・削除などを行う

https://blogs.yahoo.co.jp/dk521123/37097725.html