はじめに
システムのルート証明書を用いて X.509 証明書チェーン (証明書パス) のバリデーションを Java プログラムでおこなう方法を見ていきます。
CertPathValidator
最終的なゴールは、CertPathValidator クラスの validate メソッドを呼び出すことです。
そのための準備は、大きく二つに分かれます。一つ目は、CertPathValidator クラスのインスタンスを作成することです。二つ目は、validate メソッドに渡すパラメータ群を用意することです。
ここでは、PKIX (X.509 ベースの公開鍵基盤) で規定されたアルゴリズムでバリデーションを行う CertPathValidator インスタンスを取得するコードを見ていきます。
とはいっても、この処理は簡単で、CertPathValidator の getInstance(String) メソッドに "PKIX" を渡すだけで済みます。
ここでは、CertPathVaidator の getInstance(String) メソッドの呼び出しを getSystemCertPathValidator() というメソッドで囲んでおくことにします。
private static CertPathValidator getSystemCertPathValidator()
{
try
{
// PKIX 用の CertPathValidator を取得する。全ての Java
// プラットフォームの実装は PKIX をサポートしなければならない。
return CertPathValidator.getInstance("PKIX");
}
catch (NoSuchAlgorithmException cause)
{
// 全ての Java プラットフォームは PKIX をサポートすべきなので、
// この例外は発生しないはず。
cause.printStackTrace();
// 検査例外を実行時例外に変換して投げる。
throw new RuntimeException(
"CertPathValidator for 'PKIX' is not available.", cause);
}
}
なお、全ての Java プラットフォームは PKIX をサポートしなければならないとされているため、CertPathValidator の getInstance("PKIX") メソッドコールが NoSuchAlgorithmException (検査例外) を投げることはないはずです。そのため、上記のコードでは NoSuchAlgorithmException を catch して実行時例外に変換してから投げています。
個人的には、コードをすっきりさせるため、発生しえない検査例外を実行時例外に変換する処理をよく書きます。例えば、MessageDigest の getInstance(String) に "SHA-256" を渡す場合などです。
CertPath
CertPathValidator の validate メソッドの第一引数は、バリデーション対象となる証明書チェーン (証明書パス) を表す CertPath インスタンスです。
CertPath インスタンスが既に用意できていればよいですが、そうではなく、例えば手元にある証明書チェーンが X509Certificate の List だった場合、それを CertPath に変換する必要があります。
下記は、List<X509Certificate> から CertPath を作成するコードです。
private static CertPath buildCertPath(
List<X509Certificate> certs) throws CertificateException
{
// X509Certificate のリストを CertPath へ変換する
//
// 全ての Java プラットフォームは X.509 をサポートしなければならない
return CertificateFactory.getInstance("X.509").generateCertPath(certs);
}
CertPathParameters
CertPathValidator の validate メソッドの第二引数は CertPathParameters です。これを用意するのに手間がかかるので、一つずつ見てきましょう。
構築済みの X.509 証明書チェーンのバリデーションの際は、CertPathParameters インターフェースの実装として PKIXParameters を用います。
PKIXParameters には次の二つのコンストラクタがあります。
PKIXParameters(KeyStore)PKIXParameters(Set<TrustAnchor>)
ここでは、特定のキーストアを用いるのではなく、システムにインストールされているルート証明書群を用いて証明書パスのバリデーションをおこなうため、Set<TrustAnchor> を引数に取るコンストラクタを使うことにします。
ルート証明書群を表す Set<TrustAnchor> を用意する手順は次のとおりです。
- PKIX 用の
TrustManagerFactoryを取得する - その
TrustManagerFactoryからTrustManager(複数) を取得する - 各
TrustManagerから、信用している X.509 証明書 (複数) を取得する - 各 X.509 証明書から
TrustAnchorのインスタンスを作成する
一つずつ見ていきましょう。
TrustManagerFactory
PKIX 用の TrustManagerFactory は、TrustManagerFactory の getInstance(String) メソッドに "PKIX" を渡すことで得られます。この処理を getSystemTrustManagerFactory() というメソッドとして実装します。
private static TrustManagerFactory getSystemTrustManagerFactory()
{
TrustManagerFactory factory;
try
{
// PKIX 用の TrustManagerFactory を取得する。全ての Java
// プラットフォームの実装は PKIX をサポートしなければならない。
factory = TrustManagerFactory.getInstance("PKIX");
}
catch (NoSuchAlgorithmException cause)
{
// 全ての Java プラットフォームは PKIX をサポートすべきなので、
// この例外は発生しないはず。
cause.printStackTrace();
// 検査例外を実行時例外に変換して投げる。
throw new RuntimeException(
"TrustManagerFactory for 'PKIX' is not available.", cause);
}
try
{
// 特定のキーストアを指定せずに TrustManagerFactory を初期化する。
// 結果として、デフォルトのキーストアを参照することになる。
factory.init((KeyStore)null);
}
catch (KeyStoreException cause)
{
// init(KeyStore) メソッドに null を渡しても問題ないはずなので、
// この例外は起こらないはず。
cause.printStackTrace();
// 検査例外を実行時例外に変換して投げる。
throw new RuntimeException(
"TrustManagerFactory.init((KeyStore)null) failed.", cause);
}
return factory;
}
List<TrustManager>
TrustManagerFactory のインスタンスが得られれば、getTrustManagers() メソッドで TrustManager のインスタンス群を得られます。この処理を getSystemTrustManagers() というメソッド名で実装します。
private static List<TrustManager> getSystemTrustManagers()
{
// TrustManagerFactory を取得し、その TrustManagerFactory から
// TrustManager のリストを取得する。
return Arrays.asList(
getSystemTrustManagerFactory().getTrustManagers());
}
List<X509Certificate>
PKIX 用の TrustManager は、X509TrustManager にキャストできます。この X509TrustManager の getAcceptedIssuers() メソッドを呼ぶと、その X509TrustManager が信用している X.509 証明書のリストが得られます。
全ての TrustManager から、信用している X.509 証明書をかき集める処理は次のように書くことができます。
private static List<X509Certificate> getSystemRootCertificates()
{
// TrustManager 群から X.509 証明書群をかき集める
return getSystemTrustManagers().stream()
.filter(X509TrustManager.class::isInstance)
.map(X509TrustManager.class::cast)
.map(manager -> Arrays.asList(manager.getAcceptedIssuers()))
.flatMap(Collection::stream)
.collect(Collectors.toList());
}
Set<TrustAnchor>
X509Certificate インスタンスが用意できれば、TrustAnchor のコンストラクタを用いて TrustAnchor インスタンスを作成することができます。
private static Set<TrustAnchor> getSystemTrustAnchors()
{
// ルート証明書群を TrustAnchor のセットに変換する
return getSystemRootCertificates().stream()
.map(certificate -> new TrustAnchor(certificate, null))
.collect(Collectors.toCollection(LinkedHashSet::new));
}
PKIXParameters
TrustAnchor の Set が用意できれば、CertPathParameters インターフェースの実装である PKIXParameters のインスタンスを作成することができます。
private static CertPathParameters getSystemCertPathParameters()
throws InvalidAlgorithmParameterException
{
// PKIX 用 CertPathValidator の validate メソッドに渡す
// パラメータを作成する。
return new PKIXParameters(getSystemTrustAnchors());
}
CertPathValidator.validate
ここまでで、次の三つが用意できました。
CertPathValidatorCertPathCertPathParameters
これにより、CertPathVaidator の validate メソッドを呼ぶ準備が整いました。バリデーション対象となる証明書チェーンを List<X509Certificate> または CertPath として受けとってバリデーションをおこない、その結果を返す処理を実装しましょう。
public static PKIXCertPathValidatorResult validate(List<X509Certificate> certs)
throws CertPathValidatorException, InvalidAlgorithmParameterException, CertificateException
{
// 証明書のリストを CertPath に変換してからバリデーションをおこなう
return validate(buildCertPath(certs));
}
private static PKIXCertPathValidatorResult validate(CertPath certPath)
throws CertPathValidatorException, InvalidAlgorithmParameterException
{
// PKIX 用の CertPathValidator を取得する
CertPathValidator validator = getSystemCertPathValidator();
// validate メソッドに渡すパラメータを用意する
CertPathParameters params = getSystemCertPathParameters();
// バリデーションをおこなう
return (PKIXCertPathValidatorResult)validator.validate(certPath, params);
}
実装まとめ
SystemCertChainValidator.java
package com.authlete.server.util;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertPath;
import java.security.cert.CertPathParameters;
import java.security.cert.CertPathValidator;
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.PKIXCertPathValidatorResult;
import java.security.cert.PKIXParameters;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
public final class SystemCertChainValidator
{
private static final String PKIX = "PKIX";
private static final String X509 = "X.509";
public static PKIXCertPathValidatorResult validate(List<X509Certificate> certs)
throws CertPathValidatorException, InvalidAlgorithmParameterException, CertificateException
{
// 証明書のリストを CertPath に変換してからバリデーションをおこなう
return validate(buildCertPath(certs));
}
private static CertPath buildCertPath(List<X509Certificate> certs) throws CertificateException
{
// X509Certificate のリストを CertPath へ変換する
//
// 全ての Java プラットフォームは X.509 をサポートしなければならない
return CertificateFactory.getInstance(X509).generateCertPath(certs);
}
private static PKIXCertPathValidatorResult validate(CertPath certPath)
throws CertPathValidatorException, InvalidAlgorithmParameterException
{
// PKIX 用の CertPathValidator を取得する
CertPathValidator validator = getSystemCertPathValidator();
// validate メソッドに渡すパラメータを用意する
CertPathParameters params = getSystemCertPathParameters();
// バリデーションをおこなう
return (PKIXCertPathValidatorResult)validator.validate(certPath, params);
}
private static CertPathValidator getSystemCertPathValidator()
{
try
{
// PKIX 用の TrustManagerFactory を取得する。全ての Java
// プラットフォームの実装は PKIX をサポートしなければならない。
return CertPathValidator.getInstance(PKIX);
}
catch (NoSuchAlgorithmException cause)
{
// 全ての Java プラットフォームは PKIX をサポートすべきなので、
// この例外は発生しないはず。
cause.printStackTrace();
// 検査例外を実行時例外に変換して投げる。
throw new RuntimeException(
"CertPathValidator for 'PKIX' is not available.", cause);
}
}
private static CertPathParameters getSystemCertPathParameters()
throws InvalidAlgorithmParameterException
{
// PKIX 用 CertPathValidator の validate メソッドに渡す
// パラメータを作成する。
return new PKIXParameters(getSystemTrustAnchors());
}
private static Set<TrustAnchor> getSystemTrustAnchors()
{
// ルート証明書群を TrustAnchor のセットに変換する
return getSystemRootCertificates().stream()
.map(certificate -> new TrustAnchor(certificate, null))
.collect(Collectors.toCollection(LinkedHashSet::new));
}
private static List<X509Certificate> getSystemRootCertificates()
{
// TrustManager 群から X.509 証明書群をかき集める
return getSystemTrustManagers().stream()
.filter(X509TrustManager.class::isInstance)
.map(X509TrustManager.class::cast)
.map(manager -> Arrays.asList(manager.getAcceptedIssuers()))
.flatMap(Collection::stream)
.collect(Collectors.toList());
}
private static List<TrustManager> getSystemTrustManagers()
{
// TrustManagerFactory を取得し、その TrustManagerFactory から
// TrustManager のリストを取得する。
return Arrays.asList(
getSystemTrustManagerFactory().getTrustManagers());
}
private static TrustManagerFactory getSystemTrustManagerFactory()
{
TrustManagerFactory factory;
try
{
// PKIX 用の TrustManagerFactory を取得する。全ての Java
// プラットフォームの実装は PKIX をサポートしなければならない。
factory = TrustManagerFactory.getInstance(PKIX);
}
catch (NoSuchAlgorithmException cause)
{
// 全ての Java プラットフォームは PKIX をサポートすべきなので、
// この例外は発生しないはず。
cause.printStackTrace();
// 検査例外を実行時例外に変換して投げる。
throw new RuntimeException(
"TrustManagerFactory for 'PKIX' is not available.", cause);
}
try
{
// 特定のキーストアを指定せずに TrustManagerFactory を初期化する。
// 結果として、デフォルトのキーストアを参照することになる。
factory.init((KeyStore)null);
}
catch (KeyStoreException cause)
{
// init(KeyStore) メソッドに null を渡しても問題ないはずなので、
// この例外は起こらないはず。
cause.printStackTrace();
// 検査例外を実行時例外に変換して投げる。
throw new RuntimeException(
"TrustManagerFactory.init((KeyStore)null) failed.", cause);
}
return factory;
}
}
おわりに
X.509 証明書チェーンの詳細については『図解 X.509 証明書』をご参照ください。
また、『OAuth & OIDC 勉強会 クライアント認証編』という勉強会でも X.509 証明書について説明しましたので、動画による解説の方がお好みであれば、そちらをご視聴ください。
