Java版Bouncy Castleを利用して署名用電子証明書から基本4情報を取り出す

本記事では、Java版 Bouncy Castle ライブラリを利用して、マイナンバーカードに格納されている(公的個人認証サービスの)署名用電子証明書から基本4情報を取り出すサンプルを作ってみます。

【目次】

[1]はじめに

以前の記事『マイナンバーカードの電子証明書(公的個人認証サービス)にJava APIでアクセスする』において、個人認証サービス AP(公的個人認証サービスのJavaライブラリ)を利用して、署名用電子証明書に記載されている基本4情報(氏名、性別、生年月日、住所)を取り出す方法を見ました。

本記事では、公的個人認証サービスのライブラリに依存しない方法で、署名用電子証明書から基本4情報を取り出す方法を見ていきます。

電子証明書を解析する方法はいろいろありますが、Java続きという事で、今回はJava版の Bouncy Castle ライブラリを利用することにします。

[2]参考情報

ここはさらりと流して頂いて構わないのですが、今回のコードに関する参考情報をメモしておきます。

ガッツリ理解するなら、電子証明書の基礎から理解する必要があります。

この内容に加えて、実際の証明書のバイナリファイルを理解しようとすると、構文規則のASN.1と、その符号化規則(BER、DER)の知識も必要となります。

その他、PKIX関連の内容などを含めて、深く理解するのはかなりハードルが高いと思いますが、与えられた証明書から必要な情報を抽出するプログラムを作る、ということだけを考えれば、ライブラリを利用することで、かなり敷居が下がります。

具体的には、まず目標とする署名用電子証明書の仕様は、
にある「署名用電子証明書及び利用者証明用電子証明書のプロファイル仕様書」で公開されています。

ここにある「署名用電子証明書のプロファイル」の説明は、表形式で書かれていることもあり、かなり直観的にイメージできると思います。
また、基本4情報の内容は、「② 署名用電子証明書のプロファイル拡張領域(Extension)」の項目 subjectAltName に「利用者日本語表記」として格納されていることが分かります。

続いて、Java言語で証明書を解釈するためのライブラリとして、今回は Bouncy Castle を利用します。

Bouncy Castle はASN.1の符号化に関する低レベルな機能を持っていますが、証明書やCMSなどを扱うための高レベルな機能も提供してくれており、署名用電子証明書にある項目もほとんど用意されています。このため、ASN.1の符号化などの深い知識が無くてもプログラムを作ることができます。

[3]基本4情報を抽出するサンプルプログラム

以下のコードは、署名用電子証明書のバイナリデータを与えると、個人認証サービス AP(JPKIUserCertServiceクラスのgetBasicDataのメソッド)と同様に、以下の項目を抽出します。
  • 氏名(1.2.392.200149.8.5.5.1)
  • 氏名の代替文字の使用位置情(1.2.392.200149.8.5.5.2)
  • 性別(1.2.392.200149.8.5.5.3)
  • 生年月日(1.2.392.200149.8.5.5.4)
  • 住所(1.2.392.200149.8.5.5.5)
  • 住所の代替文字の使用位置情(1.2.392.200149.8.5.5.6)

ここで、カッコ内の数字は「署名用電子証明書のプロファイル」にある各項目のOIDです。

import java.io.IOException;

import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.OtherName;
import org.bouncycastle.cert.X509CertificateHolder;

/**
 * 署名用電子証明書の基本4情報
 */
public class JPKIBasicData_Sample {
    
    /**
     * 署名用電子証明書から基本4情報を取得する
     * @param cert
     * @return
     * @throws IOException
     */
    public static JPKIBasicData_Sample parseBasicData(byte[] cert) throws IOException {
        // BouncyCastleの証明書オブジェクト
        X509CertificateHolder certHldr = new X509CertificateHolder(cert);
        // このクラスのインスタンスを生成
        JPKIBasicData_Sample ret = new JPKIBasicData_Sample();

        // 証明書の拡張領域からsubjectAltNameを抽出
        GeneralNames subjectAltName = GeneralNames.fromExtensions(
                certHldr.getExtensions(), Extension.subjectAlternativeName);
        if (subjectAltName!=null) {
            // GeneralNameの要素ごとに処理
            for (GeneralName gnm : subjectAltName.getNames()) {
                if (gnm.getTagNo()==GeneralName.otherName) {
                    // otherName のtypeに応じて値を抽出
                    OtherName oName = OtherName.getInstance(gnm.getName());
                    String oValue = oName.getValue().toString();
                    switch( oName.getTypeID().getId() ) {
                    // 氏名
                    case "1.2.392.200149.8.5.5.1":
                        ret.name = oValue;
                        break;
                    // 生年月日
                    case "1.2.392.200149.8.5.5.4":
                        ret.dateOfBirth = oValue;
                        break;
                    // 性別
                    case "1.2.392.200149.8.5.5.3":
                        ret.gender = oValue;
                        break;
                    // 住所
                    case "1.2.392.200149.8.5.5.5":
                        ret.address = oValue;
                        break;
                    // 利用者の氏名代替文字の使用位置情
                    case "1.2.392.200149.8.5.5.2":
                        ret.substituteCharacterOfAddress = oValue;
                        break;
                    // 利用者の住所代替文字の使用位置情報
                    case "1.2.392.200149.8.5.5.6":
                        ret.substituteCharacterOfName = oValue;
                        break;
                    }
                }
            }
        }
        return ret;
    }
    
    private JPKIBasicData_Sample() {}
    
    private String name = "";
    private String dateOfBirth = "";
    private String gender = "";
    private String address = "";
    private String substituteCharacterOfAddress = "";
    private String substituteCharacterOfName = "";

    /**
     * 氏名
     * @return
     */
    public String getName() {
        return name;
    }

    /**
     * 生年月日
     * @return
     */
    public String getDateOfBirth() {
        return dateOfBirth;
    }

    /**
     * 性別
     * @return
     */
    public String getGender() {
        return gender;
    }

    /**
     * 住所
     * @return
     */
    public String getAddress() {
        return address;
    }

    /**
     * 利用者の氏名代替文字の使用位置情
     * @return
     */
    public String getSubstituteCharacterOfAddress() {
        return substituteCharacterOfAddress;
    }
    
    /**
     * 利用者の住所代替文字の使用位置情報
     * @return
     */
    public String getSubstituteCharacterOfName() {
        return substituteCharacterOfName;
    }
}

なお、上記の処理は、JPKIUserCertServiceクラスのgetBasicDataのメソッドと同様に、証明書の内容をそのまま抽出しているだけです。よって、実用的な表示を行う場合は、性別や生年月日などを利用目的に応じて整形する必要があります。

これらの詳細については、上記技術仕様のサイトにある、以下の仕様書を参考にしてください。
  • 各種証明書のプロファイル仕様書
    • ② 署名用電子証明書のプロファイル拡張領域(Extension)の説明
  • 利用者クライアントソフト機能概要説明書
    • 第5章 機能概要(個人番号カード編) .4 画面仕様 以降の記述

以下のコードは、上記クラスの利用例です。
// 署名用電子証明書の準備(ここではファイルから読み込み)
byte[] cert = Files.readAllBytes(Paths.get(証明書ファイル名));

// 基本4情報を取得
JPKIBasicData_Sample res = JPKIBasicData_Sample.parseBasicData(cert);
//
System.out.println("氏名=" + res.getName());
System.out.println("生年月日=" + res.getDateOfBirth());
System.out.println("性別=" + res.getGender());
System.out.println("住所=" + res.getAddress());
System.out.println("氏名の代替文字=" + res.getSubstituteCharacterOfAddress());
System.out.println("住所の代替文字=" + res.getSubstituteCharacterOfName());

コメント

このブログの人気の投稿

Google Document AIで画像から表形式データを抽出する(Vision API OCRとの違い)

VirtualBoxのスナップショット機能

Ubuntu/Colab環境でPDFファイルのページを画像化する(pdf2image、pdftoppm、pdftocairo)