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

■ はじめに

 以下の関連記事
https://blogs.yahoo.co.jp/dk521123/36518468.html
のようにキーストアを操作するのにコマンドを使用するが

そのキーストアのコマンド操作をJavaアプリから行う。

■ 使用上の注意:ファイル権限について

 * キーストアの書き込み権限がないと、キーストアへの証明書追加・削除時に、
   以下のような例外が発生する

例外

java.io.FileNotFoundException: /usr/lib/jvm/java-8-openjdk-amd64/jre/lib/security/cacerts (Permission denied)

対応案

1) Javaアプリをroot権限で実行する
2) キーストアの書き込み権限をアプリ実行している権限でも行えるようにする
3) Javaアプリ専用のキーストアを用意する

etc...

【任意設定】準備

動作確認用のキーストア

https://blogs.yahoo.co.jp/dk521123/36518468.html
"%JAVA_HOME%\bin\keytool" -genkey -alias demo_alias -keyalg RSA -keypass changeit -storepass changeit -keystore demo.keystore -validity 3650

動作確認用の証明書

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

■ サンプル

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

/**
 * キーストア管理クラス.
 */
public class KeyStoreManager {

  public static void main(String[] args) {
    String keyStorePath = Paths.get("etc", "demo.keystore").toString();
    String keyStorePassword = "changeit";
    try {
      KeyStoreManager keyStoreManager = new KeyStoreManager(keyStorePath, keyStorePassword);
      X509Certificate x509Certificate = keyStoreManager.getCertificatesByAlias("demo_alias");
      System.out.println("Type : " + x509Certificate.getType());
      System.out.println("Before : " + x509Certificate.getNotBefore());
      System.out.println("After : " + x509Certificate.getNotAfter());
      System.out.println("Issuer Name : " + x509Certificate.getIssuerX500Principal().getName());
      System.out.println("Subject Name : " + x509Certificate. getSubjectX500Principal().getName());
      
      System.out.println();

      // 追加
      String certificatePath = Paths.get("etc", "cacert.crt").toString();
      keyStoreManager.addCertificate("demo_alias2", certificatePath);

      List<X509Certificate> x509Certificates = keyStoreManager.getCertificatesByPrefixAlias("demo_alias");
      int counter = 1;
      for (X509Certificate certificate : x509Certificates) {
        System.out.println(counter);
        System.out.println("Type : " + certificate.getType());
        System.out.println("Before : " + certificate.getNotBefore());
        System.out.println("After : " + certificate.getNotAfter());
        System.out.println("Issuer Name : " + certificate.getIssuerX500Principal().getName());
        System.out.println("Subject Name : " + certificate. getSubjectX500Principal().getName());
        counter++;
      }

      System.out.println();

      // エクスポート機能
      String certificateExportPath = Paths.get("etc", "export.crt").toString();
      keyStoreManager.exportCertificate("demo_alias2", certificateExportPath);
      KeyStoreManager keyStoreManagerForExport = new KeyStoreManager(keyStorePath, keyStorePassword);
      X509Certificate x509CertificateForExport = keyStoreManagerForExport.getCertificatesByAlias("demo_alias2");
      System.out.println("Type For Export : " + x509CertificateForExport.getType());
      System.out.println("Before For Export : " + x509CertificateForExport.getNotBefore());
      System.out.println("After For Export : " + x509CertificateForExport.getNotAfter());
      System.out.println("Issuer Name For Export : " + x509CertificateForExport.getIssuerX500Principal().getName());
      System.out.println("Subject Name For Export : " + x509CertificateForExport. getSubjectX500Principal().getName());
      
      System.out.println();

      // 削除
      keyStoreManager.deleteCertificate("demo_alias2");

      x509Certificates = keyStoreManager.getCertificatesByPrefixAlias("demo_alias");
      counter = 1;
      for (X509Certificate certificate : x509Certificates) {
        System.out.println(counter);
        System.out.println("Type : " + certificate.getType());
        System.out.println("Before : " + certificate.getNotBefore());
        System.out.println("After : " + certificate.getNotAfter());
        System.out.println("Issuer Name : " + certificate.getIssuerX500Principal().getName());
        System.out.println("Subject Name : " + certificate.getSubjectX500Principal().getName());

        counter++;
      }
    } catch (Exception ex) {
      ex.getStackTrace();
    }
  }

  private static final String CERTIFICATE_TYPE = "X.509";

  /** キーストア・パスワード. */
  private char[] keyStorePasswordBytes;
  /** キーストア・ファイル. */
  private File keyStoreFile;
  /** キーストア. */
  private KeyStore keyStore;

  // コンストラクタ

  public KeyStoreManager(String keyStorePassword)
      throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException {
    this(KeyStoreManager.getDefaultKeyStorePath(), keyStorePassword);
  }

  public KeyStoreManager(String keyStorePath, String keyStorePassword)
      throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException {
    this.keyStoreFile = new File(keyStorePath);
    this.keyStorePasswordBytes = keyStorePassword.toCharArray();
    this.keyStore = KeyStoreManager.geyKeyStore(this.keyStoreFile, this.keyStorePasswordBytes);
  }

  // Public Static メソッド

  // デフォルトのキーストアパスを取得
  public static String getDefaultKeyStorePath() {
    return Paths.get(System.getProperty("java.home"), "lib", "security", "cacerts").toString();
  }

  // 期限切れの判定
  public static boolean isExpiredCertificate(X509Certificate x509Certificate) {
    try {
      x509Certificate.checkValidity();
    } catch (CertificateExpiredException ex) {
      return true;
    } catch (CertificateNotYetValidException ex) {
      // Go Through
    }
    return false;
  }

  // Public メソッド

  // エイリアス一覧取得
  public Enumeration<String> getKeyStoreAliases() throws KeyStoreException {
    return this.keyStore.aliases();
  }

  // 証明書の取得
  public X509Certificate getCertificatesByAlias(String alias) throws KeyStoreException {
    Certificate certificate = this.keyStore.getCertificate(alias);
    if (certificate != null && certificate.getType().equals(CERTIFICATE_TYPE)) {
      return (X509Certificate) certificate;
    } else {
      return null;
    }
  }

  // 証明書一覧の取得
  public List<X509Certificate> getCertificatesByPrefixAlias(String prefixAlias) throws KeyStoreException {
    List<X509Certificate> returnValues = new ArrayList<>();

    String targetPrefixAlias = prefixAlias.toLowerCase();
    Enumeration<String> aliases = this.getKeyStoreAliases();
    while (aliases.hasMoreElements()) {
      String alias = aliases.nextElement();
      if (alias == null || !alias.startsWith(targetPrefixAlias)) {
        continue;
      }

      X509Certificate x509Certificate = this.getCertificatesByAlias(alias);
      if (x509Certificate != null) {
        returnValues.add(x509Certificate);
      }
    }
    return returnValues;
  }

  // 追加
  public void addCertificate(String alias, String certificateFilePath)
      throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
    X509Certificate x509Certificate = KeyStoreManager.getX509Certificate(certificateFilePath);
    this.keyStore.setCertificateEntry(alias, x509Certificate);
    this.save();
  }
  // 追加
  public void addCertificate(String alias, byte[] keyStoreBytes)
      throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
    X509Certificate x509Certificate = KeyStoreManager.getX509Certificate(keyStoreBytes);
    this.keyStore.setCertificateEntry(alias, x509Certificate);
    this.save();
  }
  
  // 削除
  public void deleteCertificate(String alias)
      throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
    this.keyStore.deleteEntry(alias);
    this.save();
  }

  // エクスポート機能
  public void exportCertificate(String alias, String exportFilePath)
      throws CertificateEncodingException, IOException, KeyStoreException {
    try (FileOutputStream outputStream = new FileOutputStream(new File(exportFilePath))) {
      X509Certificate x509Certificate = this.getCertificatesByAlias(alias);
      outputStream.write(x509Certificate.getEncoded());
    }
  }

  // Private Static メソッド

  // キーストアの取得
  private static KeyStore geyKeyStore(File keyStoreFile, char[] keyStorePasswordBytes)
      throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException {
    KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
    try (InputStream fileInputStream = new FileInputStream(keyStoreFile)) {
      keyStore.load(fileInputStream, keyStorePasswordBytes);
      return keyStore;
    }
  }

  // 証明書ファイル→X509Certificate を変換
  private static X509Certificate getX509Certificate(String certificateFilePath)
      throws IOException, CertificateException {
    try (InputStream inputStream = new FileInputStream(new File(certificateFilePath))) {
      CertificateFactory certificateFactory = CertificateFactory.getInstance(CERTIFICATE_TYPE);
      return (X509Certificate) certificateFactory.generateCertificate(inputStream);
    }
  }

  // 証明書(byte[])→X509Certificate を変換
  private static X509Certificate getX509Certificate(byte[] certificates)
      throws IOException, CertificateException {
    try (InputStream inputStream = new ByteArrayInputStream(certificates)) {
      CertificateFactory certificateFactory = CertificateFactory.getInstance(CERTIFICATE_TYPE);
      return (X509Certificate) certificateFactory.generateCertificate(inputStream);
    }
  }
  
  // Private メソッド

  // 保存
  private void save() throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException {
    try (FileOutputStream fileOutputStream = new FileOutputStream(this.keyStoreFile)) {
      this.keyStore.store(fileOutputStream, this.keyStorePasswordBytes);
    }
  }
}

出力結果例

Type : X.509
Before : Fri Sep 08 22:58:04 JST 2017
After : Mon Sep 06 22:58:04 JST 2027
Issuer Name : CN=localhost,OU= ...
Subject Name : CN=localhost,OU= ...

1
Type : X.509
Before : Sun Jun 25 17:21:07 JST 2017
After : Wed Jun 24 17:21:07 JST 2020
Issuer Name : 1.2.840.113549.1.9.1=#16186469...
Subject Name : 1.2.840.113549.1.9.1=#16186469...
2
Type : X.509
Before : Fri Sep 08 22:58:04 JST 2017
After : Mon Sep 06 22:58:04 JST 2027
Issuer Name : CN=localhost,OU= ...
Subject Name : CN=localhost,OU= ...

Type For Export : X.509
Before For Export : Sun Jun 25 17:21:07 JST 2017
After For Export : Wed Jun 24 17:21:07 JST 2020
Issuer Name For Export : 1.2.840.113549.1.9.1=#161864...
Subject Name For Export : 1.2.840.113549.1.9.1=#161864...

1
Type : X.509
Before : Fri Sep 08 22:58:04 JST 2017
After : Mon Sep 06 22:58:04 JST 2027
Issuer Name : CN=localhost,OU= ...
Subject Name : CN=localhost,OU= ...
補足:CNやOUなどの値を個別に扱いたい場合
 * 以下の関連記事を参照のこと。
https://blogs.yahoo.co.jp/dk521123/37112129.html


関連記事

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

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

Javaで、キーストアファイルをロードし、一覧表示する

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

SSL証明書の拇印/フィンガープリントを生成するには...

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

Javaオブジェクト「X509Certificate」 ⇔ 証明書ファイル 変換処理

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