0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

X.509証明書チェーンのバリデーション

Posted at

はじめに

システムのルート証明書を用いて X.509 証明書チェーン (証明書パス) のバリデーションを Java プログラムでおこなう方法を見ていきます。

certificate_chain_33.png

CertPathValidator

最終的なゴールは、CertPathValidator クラスの validate メソッドを呼び出すことです。

そのための準備は、大きく二つに分かれます。一つ目は、CertPathValidator クラスのインスタンスを作成することです。二つ目は、validate メソッドに渡すパラメータ群を用意することです。

ここでは、PKIX (X.509 ベースの公開鍵基盤) で規定されたアルゴリズムでバリデーションを行う CertPathValidator インスタンスを取得するコードを見ていきます。

とはいっても、この処理は簡単で、CertPathValidatorgetInstance(String) メソッドに "PKIX" を渡すだけで済みます。

ここでは、CertPathVaidatorgetInstance(String) メソッドの呼び出しを getSystemCertPathValidator() というメソッドで囲んでおくことにします。

PKIX 用の CertPathValidator を取得する
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 をサポートしなければならないとされているため、CertPathValidatorgetInstance("PKIX") メソッドコールが NoSuchAlgorithmException (検査例外) を投げることはないはずです。そのため、上記のコードでは NoSuchAlgorithmException を catch して実行時例外に変換してから投げています。

個人的には、コードをすっきりさせるため、発生しえない検査例外を実行時例外に変換する処理をよく書きます。例えば、MessageDigestgetInstance(String)"SHA-256" を渡す場合などです。

CertPath

CertPathValidatorvalidate メソッドの第一引数は、バリデーション対象となる証明書チェーン (証明書パス) を表す CertPath インスタンスです。

CertPath インスタンスが既に用意できていればよいですが、そうではなく、例えば手元にある証明書チェーンが X509CertificateList だった場合、それを CertPath に変換する必要があります。

下記は、List<X509Certificate> から CertPath を作成するコードです。

X509Certificate のリストから CertPath を作成する
private static CertPath buildCertPath(
        List<X509Certificate> certs) throws CertificateException
{
    // X509Certificate のリストを CertPath へ変換する
    //
    // 全ての Java プラットフォームは X.509 をサポートしなければならない
    return CertificateFactory.getInstance("X.509").generateCertPath(certs);
}

CertPathParameters

CertPathValidatorvalidate メソッドの第二引数は CertPathParameters です。これを用意するのに手間がかかるので、一つずつ見てきましょう。

構築済みの X.509 証明書チェーンのバリデーションの際は、CertPathParameters インターフェースの実装として PKIXParameters を用います。

PKIXParameters には次の二つのコンストラクタがあります。

  • PKIXParameters(KeyStore)
  • PKIXParameters(Set<TrustAnchor>)

ここでは、特定のキーストアを用いるのではなく、システムにインストールされているルート証明書群を用いて証明書パスのバリデーションをおこなうため、Set<TrustAnchor> を引数に取るコンストラクタを使うことにします。

ルート証明書群を表す Set<TrustAnchor> を用意する手順は次のとおりです。

  1. PKIX 用の TrustManagerFactory を取得する
  2. その TrustManagerFactory から TrustManager (複数) を取得する
  3. TrustManager から、信用している X.509 証明書 (複数) を取得する
  4. 各 X.509 証明書から TrustAnchor のインスタンスを作成する

一つずつ見ていきましょう。

TrustManagerFactory

PKIX 用の TrustManagerFactory は、TrustManagerFactorygetInstance(String) メソッドに "PKIX" を渡すことで得られます。この処理を getSystemTrustManagerFactory() というメソッドとして実装します。

PKIX 用の TrustManagerFactory を取得する
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() というメソッド名で実装します。

TrustManager のリストを取得する
private static List<TrustManager> getSystemTrustManagers()
{
    // TrustManagerFactory を取得し、その TrustManagerFactory から
    // TrustManager のリストを取得する。
    return Arrays.asList(
            getSystemTrustManagerFactory().getTrustManagers());
}

List<X509Certificate>

PKIX 用の TrustManager は、X509TrustManager にキャストできます。この X509TrustManagergetAcceptedIssuers() メソッドを呼ぶと、その X509TrustManager が信用している X.509 証明書のリストが得られます。

全ての TrustManager から、信用している 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 インスタンスを作成することができます。

ルート証明書群を表す TrustAnchor のセットを取得する
private static Set<TrustAnchor> getSystemTrustAnchors()
{
    // ルート証明書群を TrustAnchor のセットに変換する
    return getSystemRootCertificates().stream()
            .map(certificate -> new TrustAnchor(certificate, null))
            .collect(Collectors.toCollection(LinkedHashSet::new));
}

PKIXParameters

TrustAnchorSet が用意できれば、CertPathParameters インターフェースの実装である PKIXParameters のインスタンスを作成することができます。

private static CertPathParameters getSystemCertPathParameters()
        throws InvalidAlgorithmParameterException
{
    // PKIX 用 CertPathValidator の validate メソッドに渡す
    // パラメータを作成する。
    return new PKIXParameters(getSystemTrustAnchors());
}

CertPathValidator.validate

ここまでで、次の三つが用意できました。

  • CertPathValidator
  • CertPath
  • CertPathParameters

これにより、CertPathVaidatorvalidate メソッドを呼ぶ準備が整いました。バリデーション対象となる証明書チェーンを 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 証明書について説明しましたので、動画による解説の方がお好みであれば、そちらをご視聴ください。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?