11 XMLデジタル署名APIの概要とチュートリアル

Java XMLデジタル署名APIは、XML署名を生成および検証するための標準のJava APIです。このAPIは、Javaコミュニティ・プロセスの下でJSR 105として定義されました。

XML署名は、どのタイプ(XMLまたはバイナリ)のデータにも適用できます(XML署名の構文と処理を参照)。結果の署名は、XMLで表されます。XML署名は、データを保護するために使用でき、データの整合性、メッセージ認証、および署名者認証を提供します。

このドキュメントでは、XML署名とXMLデジタル署名APIの概要を説明したあとで、APIを使用してXML署名を検証および生成する方法を示す2つの例について説明します。このドキュメントは、読者に暗号化およびデジタル署名の基本的な知識があることを前提としています。

APIは、「XML-Signature Syntax and Processing」でのW3C勧告の必須機能または推奨機能をすべてサポートするように設計されています。APIは拡張可能およびプラガブルであり、Java暗号化サービス・プロバイダ・アーキテクチャに基づいてます。Java暗号化アーキテクチャ(JCA)リファレンス・ガイドを参照してください。APIは、次の2種類の開発者向けに設計されています。

  • XMLデジタル署名APIを使用してXML署名を生成および検証する開発者
  • XMLデジタル署名APIの固定実装を作成し、JCAプロバイダの暗号化サービスとして登録する開発者(Providerクラスを参照)

パッケージ階層

次の6個のパッケージはjava.xml.cryptoモジュールに含まれ、XMLデジタル署名APIを構成します。

  • javax.xml.crypto
  • javax.xml.crypto.dsig
  • javax.xml.crypto.dsig.keyinfo
  • javax.xml.crypto.dsig.spec
  • javax.xml.crypto.dom
  • javax.xml.crypto.dsig.dom

javax.xml.cryptoパッケージには、XML署名の生成やXMLデータの暗号化など、XML暗号化操作を行う場合に使用する一般的なクラスが含まれています。このパッケージで注意する必要がある2つのクラスのうちの1つはKeySelectorクラスで、開発者は、KeyInfoオブジェクトに含まれる情報を使用してキーを見つけてオプションで検証する実装を提供できます。もう1つのクラスはURIDereferencerクラスで、開発者は、実装を間接参照する独自のURIを作成および指定できます。

javax.xml.crypto.dsigパッケージには、W3C XMLデジタル署名仕様で定義されているコア要素を表すインタフェースが含まれています。最も重要なのはXMLSignatureクラスです。このクラスを使用すると、XMLデジタル署名の署名および検証を行うことができます。XML署名構造または要素のほとんどは、対応するインタフェースによって表されます(KeyInfo構造を除く。これは独自のパッケージに含まれており、次の段落で説明します)。これらのインタフェースには、SignedInfoCanonicalizationMethodSignatureMethodReferenceTransformDigestMethodXMLObjectManifestSignaturePropertyおよびSignaturePropertiesなどがあります。XMLSignatureFactoryクラスは、これらのインタフェースを実装するオブジェクトを作成する場合に使用する抽象ファクトリです。

javax.xml.crypto.dsig.keyinfoパッケージには、KeyInfoKeyNameKeyValueX509DataX509IssuerSerialRetrievalMethodおよびPGPDataなど、W3C XMLデジタル署名勧告で定義されているほとんどのKeyInfo構造を表すインタフェースが含まれています。KeyInfoFactoryクラスは、これらのインタフェースを実装するオブジェクトを作成する場合に使用する抽象ファクトリです。

javax.xml.crypto.dsig.specパッケージには、ダイジェスト、署名、変換、またはXML署名の処理で使用される正規化アルゴリズム用の入力パラメータを表すインタフェースおよびクラスが含まれています。

最後に、javax.xml.crypto.domおよびjavax.xml.crypto.dsig.domパッケージには、javax.xml.cryptoおよびjavax.xml.crypto.dsigパッケージ用のDOM固有のクラスがそれぞれ含まれています。DOMベースのXMLSignatureFactoryまたはKeyInfoFactory実装を作成または使用する開発者およびユーザーのみが、これらのパッケージを直接使用する必要があります。

サービス・プロバイダ

Java XML署名は、XMLSignatureFactoryおよびKeyInfoFactory抽象クラスの固定実装であり、XML署名やKeyInfo構造を解析、生成および検証するオブジェクトやアルゴリズムを作成します。XMLSignatureFactoryの固定実装は、XML署名についてW3C勧告で指定されているように、必須アルゴリズムそれぞれをサポートする必要があります。オプションで、W3C勧告またはその他の仕様で定義されているように、その他のアルゴリズムをサポートできます。

Java XMLデジタル署名APIは、JCAプロバイダ・モデルを使用してXMLSignatureFactoryおよびKeyInfoFactory実装の登録とロードを行います。

固定実装XMLSignatureFactoryまたはKeyInfoFactoryはそれぞれ、XML署名やKeyInfo構造を解析および生成するときに実装によって内部で使用されるXML処理メカニズムを識別する特定の「XMLメカニズム・タイプ」をサポートしています。このJSRは、1つの標準形式であるDOMをサポートします。JDKにバンドルされているXMLデジタル署名プロバイダ実装は、DOMメカニズムをサポートします。

XMLデジタル署名API実装は、java.security.Signaturejava.security.MessageDigestなど、基盤となるJCAエンジン・クラスを使用して暗号化操作を実行する必要があります。

XMLSignatureFactoryおよびKeyInfoFactoryクラス以外に、JSR 105は、変換および正規化アルゴリズム用のサービス・プロバイダ・インタフェースもサポートしています。TransformServiceクラスを使用すると、特定のXMLメカニズム・タイプ用の固有の変換または正規化アルゴリズムの実装を開発およびプラグインできます。TransformServiceクラスは、実装を登録およびロードするときに標準JCAプロバイダ・モデルを使用します。各JSR 105実装は、TransformServiceクラスを使用して、生成または検証するXML署名内の変換および正規化アルゴリズムをサポートするプロバイダを見つけるようにしてください。

XML署名について

XML署名を使用すると、XMLかバイナリかにかかわらず、任意のデータに署名できます。データは1つ以上のReference要素内のURIによって識別されます。XML署名は分離、内包または包含の3つの形式のうち、1つ以上の形式で記述されます。分離署名は外部、つまり署名要素自体の外にあるデータに対するものです。内包署名は署名要素の内側にあるデータに対する署名です。包含署名は署名対象のデータ内に含まれる署名です。

XML署名の例

XML署名の内容を説明するもっとも簡単な方法は、実際のサンプルを示し、各コンポーネントについて詳細に説明することです。例11-3は、XMLドキュメントの内容に対して生成される包含XML署名です。次のように、ルート要素EnvelopにはSignature要素が含まれています。

<Envelope xmlns="urn:envelope">
  <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
    <!-- ... -->
  </Signature>
</Envelope> 

Signature要素は署名対象の内容に挿入されており、それによって包含署名になっています。必須のSignedInfo要素には、実際に署名される次の情報が含まれています。

<Envelope xmlns="urn:envelope">
  <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
    <SignedInfo>
      <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"/>
      <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
      <Reference URI="">
        <Transforms>
          <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
        </Transforms>
        <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
        <DigestValue>/juoQ4bDxElf1M+KJauO20euW+QAvvPP0nDCruCQooM=</DigestValue>
      </Reference>
    </SignedInfo>
    <!-- ... -->
  </Signature>
</Envelope> 

必須のCanonicalizationMethod要素は、署名または検証の前にSignedInfo要素を正規化するために使用されるアルゴリズムを定義します。正規化とは、そのデータに対する署名を無効化する可能性がある変更を考慮するために、XMLコンテンツを正規形に変換する処理のことを意味します。正規化は、XMLの性質および異なるプロセッサと中間的存在による解析のために必要です。正規化により、署名が無効になっても署名されたデータが論理的に同等であるようにデータを変更できます。

必須のSignatureMethod要素は、署名の生成に使用されるデジタル署名アルゴリズムを定義します。この場合は、SHA-256を使用するRSAです。

1つ以上のReference要素が、ダイジェストされるデータを識別します。各Reference要素は、URIによってデータを識別します。この例では、URIの値は空の文字列("")で、これはドキュメントのルートを示します。オプションのTransforms要素には、1つ以上のTransform要素のリストが含まれており、それぞれがダイジェスト前のデータの変換に使用される変換アルゴリズムを記述します。この例では、包含変換アルゴリズムのTransform要素が1つあります。包含変換は、署名値を計算する前に署名要素自体が削除されるようにするために、包含署名に必要です。必須のDigestMethod要素は、データのダイジェストに使用されるアルゴリズムを定義します。この場合は、SHA-256です。最後に、必須のDigestValue要素には、実際のBase64でエンコードされたダイジェスト値が含まれています。

必須のSignatureValue要素には、SignedInfo要素に対する署名のBase64でエンコードされた署名値が含まれています。

オプションのKeyInfo要素には、署名の検証に必要なキーに関する情報が含まれています。

    <KeyInfo>
      <KeyValue>
        <RSAKeyValue>
          <Modulus>
9hSmAKw/4TTw/1l1u1pYzdFm6lOjRB/5NfdGWl/fB8iAa/tiK0f1u/VWoK6SMtogYgSDKqQThbAu
9dy9rRnOWRGY2He1JtpOvGh0WCmIFUEs2P22HvEf+JGKVEpkoP4hv53ucT69T+7nKGK3/bjxgp+T
C7fbnVj651+jAHuDFlC8Txt1R8ZymfN5cUeHIH96dvNFrtai/uwZDbVMfhV9chL//+Vyhx4O5nHv
jfS+0So9Qi52YAbEyLu6+BLdu8wnMWapC88CfXsRwrpx8b6aCU0e6QSZyOvdgXWz3+9ifVTBDIxE
kjhL5OASx0qjvc+dPUOMvq7fJE05RRZLyb0YJw==
          </Modulus>
          <Exponent>AQAB</Exponent>
        </RSAKeyValue>
      </KeyValue>
    </KeyInfo>

このKeyInfo要素にはKeyValue要素が含まれています。この要素には、署名の検証に必要な公開キーで構成されるRSAKeyValue要素が含まれています。KeyInfoには、X.509証明書およびPGPキー識別子など、様々な内容を含めることができます。様々なKeyInfo型の詳細は、XML署名の構文と処理KeyInfo要素を参照してください。

XML署名のセキュアな検証モード

XML署名のセキュアな検証モードでは、サービス拒否やその他のタイプのセキュリティ問題を引き起こす場合がある潜在的に攻撃的な構文が含まれている可能性のあるXML署名からユーザーを保護できます。

XML署名のセキュアな検証モードは、デフォルトで有効になっています。

必要に応じて、自己責任で、XML署名のセキュアな検証モードを無効にするには、システム・プロパティorg.jcp.xml.dsig.secureValidationfalseに設定するか、DOMValidateContext.setProperty()メソッドを使用してorg.jcp.xml.dsig.secureValidationプロパティをBoolean.FALSEに設定します。システム・プロパティは、APIプロパティの値よりも優先されます。

XML署名のセキュアな検証モードを有効にすると、XML署名はよりセキュアに処理されます。様々なXML署名の構文に制限を設定して、サービス拒否攻撃などの状況を回避します。デフォルトでは、次の制限が強制されます。

  • XSLT変換の使用の禁止
  • SignedInfoまたはManifest Reference要素の数を30以下に制限
  • Reference変換の数を5以下に制限
  • MD5またはSHA-1の署名またはMD5 MACアルゴリズムの使用の禁止
  • 署名ラッピング攻撃を回避するための、Reference IDの一意性の確保
  • httphttpsまたはfileタイプのReference URIの禁止
  • RetrievalMethod要素から別のRetrievalMethod要素への参照の不許可
  • 1024ビット未満のRSAキーまたはDSAキーの禁止
  • 224ビット未満のECキーの禁止

また、jdk.xml.dsig.secureValidationPolicyセキュリティ・プロパティを使用して、前述の制限を制御および微調整したり、他の制限を追加できます。詳細は、java.securityファイルで、このセキュリティ・プロパティの定義を参照してください。

XMLデジタル署名APIの例

次のセクションでは、XMLデジタル署名APIを使用する方法を示す2つの例について説明します。

検証の例

この例をコンパイルし動作させるには、次のコマンドを実行します。

$ javac Validate.java 
$ java Validate signature.xml

サンプル・プログラムは現在の作業ディレクトリ内のファイルsignature.xmlの署名を検証します。

例11-1 Validate.java

import javax.xml.crypto.*;
import javax.xml.crypto.dsig.*;
import javax.xml.crypto.dom.*;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.crypto.dsig.keyinfo.*;
import java.io.FileInputStream;
import java.security.*;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;

/**
 * This is a simple example of validating an XML Signature using
 * the XML Signature API. It assumes the key needed to validate
 * the signature is contained in a KeyValue KeyInfo.
 */
public class Validate {

    //
    // Synopsis: java Validate [document]
    //
    //    where "document" is the name of a file containing the XML document
    //    to be validated.
    //
    public static void main(String[] args) throws Exception {

        // Instantiate the document to be validated
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(true);
        Document doc = null;
        try (FileInputStream fis = new FileInputStream(args[0])) {
            doc = dbf.newDocumentBuilder().parse(fis);
        }

        // Find Signature element
        NodeList nl =
            doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
        if (nl.getLength() == 0) {
            throw new Exception("Cannot find Signature element");
        }

        // Create a DOM XMLSignatureFactory that will be used to unmarshal the
        // document containing the XMLSignature
        XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");

        // Create a DOMValidateContext and specify a KeyValue KeySelector
        // and document context
        DOMValidateContext valContext = new DOMValidateContext
            (new KeyValueKeySelector(), nl.item(0));

        // unmarshal the XMLSignature
        XMLSignature signature = fac.unmarshalXMLSignature(valContext);

        // Validate the generated XMLSignature
        boolean coreValidity = signature.validate(valContext);

        // Check core validation status
        if (coreValidity == false) {
            System.err.println("Signature failed core validation");
            boolean sv = signature.getSignatureValue().validate(valContext);
            System.out.println("signature validation status: " + sv);
            // check the validation status of each Reference
            Iterator<Reference> i =
                signature.getSignedInfo().getReferences().iterator();
            for (int j=0; i.hasNext(); j++) {
                boolean refValid = i.next().validate(valContext);
                System.out.println("ref["+j+"] validity status: " + refValid);
            }
        } else {
            System.out.println("Signature passed core validation");
        }
    }

    /**
     * KeySelector which retrieves the public key out of the
     * KeyValue element and returns it.
     * NOTE: If the key algorithm doesn't match signature algorithm,
     * then the public key will be ignored.
     */
    private static class KeyValueKeySelector extends KeySelector {
        public KeySelectorResult select(KeyInfo keyInfo,
                                        KeySelector.Purpose purpose,
                                        AlgorithmMethod method,
                                        XMLCryptoContext context)
            throws KeySelectorException {
            if (keyInfo == null) {
                throw new KeySelectorException("Null KeyInfo object!");
            }
            SignatureMethod sm = (SignatureMethod) method;
            List<XMLStructure> list = keyInfo.getContent();

            for (int i = 0; i < list.size(); i++) {
                XMLStructure xmlStructure = list.get(i);
                if (xmlStructure instanceof KeyValue) {
                    PublicKey pk = null;
                    try {
                        pk = ((KeyValue)xmlStructure).getPublicKey();
                    } catch (KeyException ke) {
                        throw new KeySelectorException(ke);
                    }
                    // make sure algorithm is compatible with method
                    if (algEquals(sm.getAlgorithm(), pk.getAlgorithm())) {
                        return new SimpleKeySelectorResult(pk);
                    }
                }
            }
            throw new KeySelectorException("No KeyValue element found!");
        }

        static boolean algEquals(String algURI, String algName) {
            if (algName.equalsIgnoreCase("DSA") &&
                algURI.equalsIgnoreCase("http://www.w3.org/2009/xmldsig11#dsa-sha256")) {
                return true;
            } else if (algName.equalsIgnoreCase("RSA") &&
                       algURI.equalsIgnoreCase("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256")) {
                return true;
            } else {
                return false;
            }
        }
    }

    private static class SimpleKeySelectorResult implements KeySelectorResult {
        private PublicKey pk;
        SimpleKeySelectorResult(PublicKey pk) {
            this.pk = pk;
        }

        public Key getKey() { return pk; }
    }
}

例11-2 envelope.xml

<Envelope xmlns="urn:envelope">
</Envelope>

例11-3 signature.xml

このファイルは、読みやすくするためにインデントおよびフォーマットされています。

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Envelope xmlns="urn:envelope">
  <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
    <SignedInfo>
      <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"/>
      <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
      <Reference URI="">
        <Transforms>
          <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
        </Transforms>
        <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
        <DigestValue>/juoQ4bDxElf1M+KJauO20euW+QAvvPP0nDCruCQooM=</DigestValue>
      </Reference>
    </SignedInfo>
    <SignatureValue>
Vorr4nABCD7eWOjh4jn8pdM5iseGJPt4BmlgjEbxr05TsR9ObHq7WLVOBOtJfb3M6pXv6NnTucpH
e/97zHbuUMaNeGxCs/gN7YDUGOkQE1Gs4HAbGwXuTcif3pw+066ZW4uxyzapwS6lZHmqIm7PRl8I
NIQXVL4dezLe+rx77Kh+rZRheVe4UlTTP+TmIOaBZo93GQ5FudreMhSiuIC0Nx2SP7mAkt6+8kVH
luZouFbqriSvyhzIxDgyOXpm/PHCuuPU2scCokwjEZBtlZXDOl6lIWGllnyrptWntQ6F/ngQObI5
c2+npgCshq1svGuS/xx18MAFHGWi98Vj+07QCg==
    </SignatureValue>
    <KeyInfo>
      <KeyValue>
        <RSAKeyValue>
          <Modulus>
9hSmAKw/4TTw/1l1u1pYzdFm6lOjRB/5NfdGWl/fB8iAa/tiK0f1u/VWoK6SMtogYgSDKqQThbAu
9dy9rRnOWRGY2He1JtpOvGh0WCmIFUEs2P22HvEf+JGKVEpkoP4hv53ucT69T+7nKGK3/bjxgp+T
C7fbnVj651+jAHuDFlC8Txt1R8ZymfN5cUeHIH96dvNFrtai/uwZDbVMfhV9chL//+Vyhx4O5nHv
jfS+0So9Qi52YAbEyLu6+BLdu8wnMWapC88CfXsRwrpx8b6aCU0e6QSZyOvdgXWz3+9ifVTBDIxE
kjhL5OASx0qjvc+dPUOMvq7fJE05RRZLyb0YJw==
          </Modulus>
          <Exponent>AQAB</Exponent>
        </RSAKeyValue>
      </KeyValue>
    </KeyInfo>
  </Signature>
</Envelope>
XML署名の検証

この例では、Java XMLデジタル署名APIを使用してXML署名を検証する方法を示します。この例はDOM (Document Object Model)を使用して、署名要素を含むXMLドキュメントおよびDOM実装を解析し、署名を検証します。

署名を含むドキュメントのインスタンス化

最初に、JAXP DocumentBuilderFactoryを使用して、署名を含むXMLドキュメントを解析します。アプリケーションは、次のコード行を呼び出すことによって、DocumentBuilderFactoryのデフォルト実装を取得します。

    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

ファクトリ名前空間認識も作成します。

    dbf.setNamespaceAware(true);

次に、このファクトリを使用してDocumentBuilderのインスタンスを取得します。これはドキュメントの解析で使用されます。

    Document doc = null;
    try (FileInputStream fis = new FileInputStream(args[0])) {
        doc = dbf.newDocumentBuilder().parse(fis);
    }
検証する署名要素の指定

ドキュメントに複数含まれている場合もあるため、検証するSignature要素を指定する必要があります。DOMメソッドDocument.getElementsByTagNameNSを使用して、次に示すように、Signature要素のXML署名名前空間URIおよびタグ名をメソッドに渡します。

    NodeList nl =
        doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
    if (nl.getLength() == 0) {
        throw new Exception("Cannot find Signature element");
    } 

これは、ドキュメント内にあるすべてのSignature要素のリストを返します。この例ではSignature要素は1つのみです。

検証コンテキストの作成

署名を検証するための入力パラメータを含むXMLValidateContextインスタンスを作成します。DOMを使用しているため、DOMValidateContextインスタンス(XMLValidateContextのサブクラス)をインスタンス化し、2つのパラメータを渡します。2つのパラメータとは、KeyValueKeySelectorオブジェクトと、先に生成したNodeListの最初のエントリである、検証するSignature要素の参照です。

    DOMValidateContext valContext = new DOMValidateContext
        (new KeyValueKeySelector(), nl.item(0));

KeyValueKeySelectorについては、「KeySelectorの使用」で詳細に説明します。

XML署名のアンマーシャリング

Signature要素の内容をXMLSignatureオブジェクトに抽出します。この処理は非整列化と呼ばれます。Signature要素はXMLSignatureFactoryオブジェクトを使用して非整列化されます。アプリケーションは次のコード行を呼び出すことによって、XMLSignatureFactoryのDOM実装を取得できます。

    XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");

次に、ファクトリのunmarshalXMLSignatureメソッドを呼び出してXMLSignatureオブジェクトを非整列化し、先に作成した検証コンテキストを渡します。

    XMLSignature signature = fac.unmarshalXMLSignature(valContext);
XML署名の検証

これで、署名を検証する準備が整いました。XMLSignatureオブジェクトでvalidateメソッドを呼び出して、次に示すように検証コンテキストを渡します。

    boolean coreValidity = signature.validate(valContext);

W3C XML署名勧告のコア検証規則に従って署名が正常に検証されると、validateメソッドは「true」を返します。それ以外の場合はfalseを返します。

XML署名の検証に失敗した場合

XMLSignature.validateメソッドがfalseを返した場合、失敗の原因の絞込みを試行できます。コアXML署名検証には、次の2つのフェーズがあります。

  • Signature validation (署名の暗号検証)
  • Reference validation (署名内の各参照のダイジェストの検証)

署名が有効になるには、それぞれのフェーズが成功する必要があります。署名の暗号化の検証が失敗したかどうかをチェックするために、次に示すようにステータスをチェックできます。

    boolean sv = signature.getSignatureValue().validate(valContext);
    System.out.println("signature validation status: " + sv);

次に示すように、参照に対して繰返し実行して、各参照の検証ステータスをチェックすることもできます。

    Iterator<Reference> i =
        signature.getSignedInfo().getReferences().iterator();
    for (int j=0; i.hasNext(); j++) {
        boolean refValid = i.next().validate(valContext);
        System.out.println("ref["+j+"] validity status: " + refValid);
    }
KeySelectorの使用

KeySelectorは、XMLSignatureの検証に必要なキーを見つけて選択するために使用されます。以前は、DOMValidateContextオブジェクトを作成したときは、KeyValueKeySelectorオブジェクトを最初の引数として渡しました。

    DOMValidateContext valContext = new DOMValidateContext
        (new KeyValueKeySelector(), nl.item(0));

また、署名の検証に必要なキーがすでにわかっている場合は、PublicKeyを最初の引数として渡すこともできました。ただし、多くの場合はわかりません。

KeyValueKeySelectorクラスは、抽象KeySelectorクラスの固定実装です。KeyValueKeySelector実装は、XMLSignatureKeyInfo要素のKeyValue要素に含まれるデータを使用して、適切な検証キーを見つけようとします。キーが信頼できるかどうかは判断しません。これは、単純なKeySelector実装であり、実際の使用というよりは説明のために設計されています。KeySelectorのより実用的な例は、KeyInfoに含まれるX509Data情報(X509SubjectName要素、X509IssuerSerial要素、X509SKI要素、X509Certificate要素など)に一致する信頼できるキーでKeyStoreを検索する例です。

KeyValueKeySelectorクラスの実装は、次のとおりです。

    private static class KeyValueKeySelector extends KeySelector {
        public KeySelectorResult select(KeyInfo keyInfo,
                                        KeySelector.Purpose purpose,
                                        AlgorithmMethod method,
                                        XMLCryptoContext context)
            throws KeySelectorException {
            if (keyInfo == null) {
                throw new KeySelectorException("Null KeyInfo object!");
            }
            SignatureMethod sm = (SignatureMethod) method;
            List<XMLStructure> list = keyInfo.getContent();

            for (int i = 0; i < list.size(); i++) {
                XMLStructure xmlStructure = list.get(i);
                if (xmlStructure instanceof KeyValue) {
                    PublicKey pk = null;
                    try {
                        pk = ((KeyValue)xmlStructure).getPublicKey();
                    } catch (KeyException ke) {
                        throw new KeySelectorException(ke);
                    }
                    // make sure algorithm is compatible with method
                    if (algEquals(sm.getAlgorithm(), pk.getAlgorithm())) {
                        return new SimpleKeySelectorResult(pk);
                    }
                }
            }
            throw new KeySelectorException("No KeyValue element found!");
        }

        static boolean algEquals(String algURI, String algName) {
            if (algName.equalsIgnoreCase("DSA") &&
                algURI.equalsIgnoreCase("http://www.w3.org/2009/xmldsig11#dsa-sha256")) {
                return true;
            } else if (algName.equalsIgnoreCase("RSA") &&
                       algURI.equalsIgnoreCase("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256")) {
                return true;
            } else {
                return false;
            }
        }
    }

    private static class SimpleKeySelectorResult implements KeySelectorResult {
        private PublicKey pk;
        SimpleKeySelectorResult(PublicKey pk) {
            this.pk = pk;
        }

        public Key getKey() { return pk; }
    }

GenEnvelopedの例

このサンプルをコンパイルし動作させるには、次のコマンドを実行します。

$ javac GenEnveloped.java
$ java GenEnveloped envelope.xml envelopedSignature.xml

このサンプル・プログラムはファイルenvelope.xml内のドキュメントの包含署名を生成し、現在の作業ディレクトリ内のファイルenvelopedSignature.xmlに保存します。

例11-4 GenEnveloped.java

import javax.xml.crypto.dsig.*;
import javax.xml.crypto.dsig.dom.DOMSignContext;
import javax.xml.crypto.dsig.keyinfo.*;
import javax.xml.crypto.dsig.spec.*;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.security.*;
import java.util.List;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;

/**
 * This is a simple example of generating an Enveloped XML
 * Signature using the Java XML Digital Signature API. The
 * resulting signature will look like (key and signature
 * values will be different):
 *
 * <pre><code>
 *<Envelope xmlns="urn:envelope">
 * <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
 *   <SignedInfo>
 *     <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
 *     <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
 *     <Reference URI="">
 *       <Transforms>
 *         <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
 *       </Transforms>
 *       <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
 *       <DigestValue>/juoQ4bDxElf1M+KJauO20euW+QAvvPP0nDCruCQooM=<DigestValue>
 *     </Reference>
 *   </SignedInfo>
 *   <SignatureValue>
 *     YeS+F0uiYv0h946M69Q9pKFNnD6dxUwLA8QT3GX/0H3cSPKRnNFyZiR4RPgaA1ir/ztb4rt6Lqb8
 *     hgwPERIa5qhoGUJyHDfUTcQ0Xqn1jYCVoC3ho+oUgJPXNVgtMAtpvOgxcWXUPATYdyimO6RrHF8+
 *     JXDkeICI9BPA4NKN1i77CAy6JJbaA87aNIpMJPImwJf8CM7mYsXremZz+RsafNE2cXXRzAoNOynC
 *     pi4oPYpE7CBLzhd23gf7zYRoyT06/bVIj4j3qOlVY1TQofsQ20NtAz6PbqAs7QkNoDzkX1CYlDSJ
 *     U8cGHuwXpul/UIpOiL6MZF8I/YI4ZlJn+O8Mvg==
 *   </SignatureValue>
 *   <KeyInfo>
 *     <KeyValue>
 *       <RSAKeyValue>
 *         <Modulus>
 *           mH0S/iw2K2tFTFHI75BtB67pzjR52HvQ8K7Xi5UX3NJm0oA+KX2mm0IrVcUuv609vbAAyQoW7CWm
 *           4kswVgStCm68dlw36309cxrEmPhG+PKBmUaGuBmRzwityjXRyRZJ6yaLenE8SJO/DC5ntQvmHqQQ
 *           qeOJYvz2Cbi2bi6x9XwmpqOfZCE5iTvYwioEsrglhP1uLG9fiXyNR2PXUTyLqD91HLhZFj1CEiU7
 *           aE++WfkKaowIx5p8e3F6hQ+VFRNXjtemK5aajuL0gwU+Oujg9ijgbyMh19vBoI8LruJoMOBrYFNN
 *           2boQJ3wP0Ek7CPIqAzQB5MnmvKc9jICKiiZVZw==
 *         </Modulus>
 *         <Exponent>AQAB</Exponent>
 *       </RSAKeyValue>
 *     </KeyValue>
 *   </KeyInfo>
 * </Signature>
 *</Envelope>
 * </code></pre>
 */
public class GenEnveloped {

    //
    // Synopsis: java GenEnveloped [document] [output]
    //
    //    where "document" is the name of a file containing the XML document
    //    to be signed, and "output" is the name of the file to store the
    //    signed document. The 2nd argument is optional - if not specified,
    //    standard output will be used.
    //
    public static void main(String[] args) throws Exception {
    	
        // Instantiate the document to be signed
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(true);
        Document doc = null;
        try (FileInputStream fis = new FileInputStream(args[0])) {
            doc = dbf.newDocumentBuilder().parse(fis);
        }
        
        // Create a RSA KeyPair
        KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
        kpg.initialize(2048);
        KeyPair kp = kpg.generateKeyPair();
        
        // Create a DOMSignContext and specify the RSA PrivateKey and
        // location of the resulting XMLSignature's parent element
        DOMSignContext dsc = new DOMSignContext
            (kp.getPrivate(), doc.getDocumentElement());
      
        // Create a DOM XMLSignatureFactory that will be used to generate the
        // enveloped signature
        XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");

        // Create a Reference to the enveloped document (in this case we are
        // signing the whole document, so a URI of "" signifies that) and
        // also specify the SHA256 digest algorithm and the ENVELOPED Transform.
        Reference ref = fac.newReference
            ("", fac.newDigestMethod(DigestMethod.SHA256, null),
             List.of
              (fac.newTransform
                (Transform.ENVELOPED, (TransformParameterSpec) null)),
             null, null);

        // Create the SignedInfo
        SignedInfo si = fac.newSignedInfo
            (fac.newCanonicalizationMethod
             (CanonicalizationMethod.INCLUSIVE_WITH_COMMENTS,
              (C14NMethodParameterSpec) null),
             fac.newSignatureMethod("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", null),
             List.of(ref));

        // Create a KeyValue containing the RSA PublicKey that was generated
        KeyInfoFactory kif = fac.getKeyInfoFactory();
        KeyValue kv = kif.newKeyValue(kp.getPublic());

        // Create a KeyInfo and add the KeyValue to it
        KeyInfo ki = kif.newKeyInfo(List.of(kv));

        // Create the XMLSignature (but don't sign it yet)
        XMLSignature signature = fac.newXMLSignature(si, ki);

        // Marshal, generate (and sign) the enveloped signature
        signature.sign(dsc);

        // output the resulting document
        OutputStream os;
        if (args.length > 1) {
           os = new FileOutputStream(args[1]);
        } else {
           os = System.out;
        }

        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer trans = tf.newTransformer();
        trans.transform(new DOMSource(doc), new StreamResult(os));
    }
}

例11-5 envelope.xml

<Envelope xmlns="urn:envelope">
</Envelope>
XML署名の生成

この例では、XMLデジタル署名APIを使用してXML署名を生成する方法を示します。より具体的には、この例はXMLドキュメントの包含XML署名を生成します。包含署名とは、署名対象の内容に含まれる署名です。この例はDOM (Document Object Model)を使用して、署名対象のXMLドキュメントおよびDOM実装を解析し、結果の署名を生成します。

XML署名およびそのさまざまなコンポーネントの基本的な知識が、このセクションの理解に役立ちます。詳細は、XML Signature Syntax and Processing Version 1.1を参照してください。

署名するドキュメントのインスタンス化

最初に、JAXP DocumentBuilderFactoryを使用して、署名するXMLドキュメントを解析します。アプリケーションは、次のコード行を呼び出すことによって、DocumentBuilderFactoryのデフォルト実装を取得します。

    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

ファクトリ名前空間認識も作成します。

    dbf.setNamespaceAware(true);

次に、このファクトリを使用してDocumentBuilderのインスタンスを取得します。これはドキュメントの解析で使用されます。

    Document doc = null;
    try (FileInputStream fis = new FileInputStream(args[0])) {
        doc = dbf.newDocumentBuilder().parse(fis);
    }
公開キーのペアの作成

公開キーのペアを生成します。あとの例では、秘密キーを使用して署名を生成します。KeyPairGeneratorを使用して、キーのペアを作成します。この例では、2048バイトの長さのRSA KeyPairを作成します。

    KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
    kpg.initialize(2048);
    KeyPair kp = kpg.generateKeyPair();

実際には、秘密キーは通常は以前に生成されて、関連する公開キー証明書とともにKeyStoreファイルに保存されています。

署名コンテキストの作成

署名を生成するための入力パラメータを含むXMLSignContextを作成します。DOMを使用しているので、DOMSignContext (XMLSignContextのサブクラス)をインスタンス化し、2つのパラメータを渡します。パラメータは、ドキュメントの署名に使用される秘密キーと署名対象のドキュメントのルートです。

    DOMSignContext dsc = new DOMSignContext
        (kp.getPrivate(), doc.getDocumentElement());
XML署名の構築

Signature要素のさまざまな部分を組み合わせて、XMLSignatureオブジェクトにします。これらのオブジェクトはすべて、XMLSignatureFactoryオブジェクトを使用して、作成され組み合わせられます。アプリケーションは次のコード行を呼び出すことによって、XMLSignatureFactoryのDOM実装を取得します。

    XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");

次に、いろいろなファクトリ・メソッドを呼び出してXMLSignatureオブジェクトの様々な部分を作成します。Referenceオブジェクトを作成して、そのオブジェクトに次のものを渡します。

  • 署名対象のオブジェクトのURI (ここでは、ドキュメントのルートを示すURI ""を指定)

  • DigestMethod (ここでは、SHA256を使用)

  • 1つのTransform、包含Transform (署名値の計算の前に署名自体が削除されるように、包含署名で必要)

    Reference ref = fac.newReference
        ("", fac.newDigestMethod(DigestMethod.SHA256, null),
         List.of
          (fac.newTransform
            (Transform.ENVELOPED, (TransformParameterSpec) null)),
         null, null);

次に、実際に署名されるSignedInfoオブジェクトを作成します。SignedInfoを作成するときは、次のものをパラメータとして渡します。

  • CanonicalizationMethod (ここでは、INCLUSIVEを使用してコメントを保持)

  • SignatureMethod (ここでは、RSAを使用)

  • Referencesのリスト(この場合は1つのみ)

    SignedInfo si = fac.newSignedInfo
        (fac.newCanonicalizationMethod
         (CanonicalizationMethod.INCLUSIVE_WITH_COMMENTS,
          (C14NMethodParameterSpec) null),
         fac.newSignatureMethod("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", null),
         List.of(ref));

次に、オプションのKeyInfoオブジェクトを作成します。このオブジェクトには、受け側が署名の検証に必要なキーを見つけられるようにする情報が含まれています。この例では、公開キーを含むKeyValueオブジェクトを追加します。KeyInfoおよびその様々なサブタイプを作成するには、KeyInfoFactoryオブジェクトを使用します。このオブジェクトは次に示すように、XMLSignatureFactorygetKeyInfoFactoryメソッドを呼び出すことによって取得できます。

    KeyInfoFactory kif = fac.getKeyInfoFactory();

次に、KeyInfoFactoryを使用してKeyValueオブジェクトを作成し、KeyInfoオブジェクトに追加します。

    KeyValue kv = kif.newKeyValue(kp.getPublic());
    KeyInfo ki = kif.newKeyInfo(List.of(kv));

最後に、XMLSignatureオブジェクトを作成し、先に作成したSignedInfoおよびKeyInfoオブジェクトをパラメータとして渡します。

    XMLSignature signature = fac.newXMLSignature(si, ki);

まだ実際には署名を生成していません。次のステップで生成します。

XML署名の生成

これで、署名を生成する準備が整いました。XMLSignatureオブジェクトでsignメソッドを呼び出して、次に示すように署名コンテキストを渡します。

    signature.sign(dsc);

これで、結果のドキュメントには署名が含まれています。署名は、ルート要素の最後の子要素として挿入されました。

生成されるドキュメントの印刷または表示

次のコードを使用して、結果の署名済みドキュメントをファイルまたは標準出力に印刷できます。

    OutputStream os;
    if (args.length > 1) {
       os = new FileOutputStream(args[1]);
    } else {
       os = System.out;
    }

    TransformerFactory tf = TransformerFactory.newInstance();
    Transformer trans = tf.newTransformer();
    trans.transform(new DOMSource(doc), new StreamResult(os));