プライマリ・コンテンツに移動
Java Platform, Standard Editionセキュリティ開発者ガイド
リリース9
E91919-01
目次へ移動
目次

前
次
次へ

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

Java XMLデジタル署名APIは、XML署名を生成および検証するための標準のJava APIです。このAPIは、Javaコミュニティ・プロセスの下でJSR 105として定義されました。このJSRは最終的なものであり、このリリースのJava SEには、APIの最終バージョンのFCSアクセス実装が含まれています。

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

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

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

パッケージ階層

次に示す6個のパッケージが、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実装を作成または使用する開発者およびユーザーのみが、これらのパッケージを直接使用する必要があります。

サービス・プロバイダ

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

JSR 105は、JCAプロバイダ・モデルを使用してXMLSignatureFactoryおよびKeyInfoFactory実装の登録とロードを行います。

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

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署名の内容を説明するもっとも簡単な方法は、実際のサンプルを示し、各コンポーネントについて詳細に説明することです。次に、XMLドキュメントの内容に対して生成される包含XML署名の例を示します。署名される前のドキュメントの内容は、次のとおりです。

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

結果の包含XML署名は次のとおりです。読みやすくするために、インデントおよび形式設定されています。

<?xml version="1.0" encoding="UTF-8"?>
<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/2000/09/xmldsig#dsa-sha1"/>
      <Reference URI="">
        <Transforms>
          <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
        </Transforms>
        <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
        <DigestValue>uooqbWYa5VCqcJCbuymBKqm17vY=</DigestValue>
      </Reference>
    </SignedInfo>
    <SignatureValue>
      KedJuTob5gtvYx9qM3k3gm7kbLBwVbEQRl26S2tmXjqNND7MRGtoew==
    </SignatureValue>
    <KeyInfo>
      <KeyValue>
        <DSAKeyValue>
          <P>
            /KaCzo4Syrom78z3EQ5SbbB4sF7ey80etKII864WF64B81uRpH5t9jQTxe
            Eu0ImbzRMqzVDZkVG9xD7nN1kuFw==
          </P>
          <Q>li7dzDacuo67Jg7mtqEm2TRuOMU=</Q>
          <G>
            Z4Rxsnqc9E7pGknFFH2xqaryRPBaQ01khpMdLRQnG541Awtx/
            XPaF5Bpsy4pNWMOHCBiNU0NogpsQW5QvnlMpA==
          </G>
          <Y>
            qV38IqrWJG0V/mZQvRVi1OHw9Zj84nDC4jO8P0axi1gb6d+475yhMjSc/
            BrIVC58W3ydbkK+Ri4OKbaRZlYeRA==
          </Y>
        </DSAKeyValue>
      </KeyValue>
    </KeyInfo>
  </Signature>
</Envelope> 

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

<SignedInfo>
  <CanonicalizationMethod 
    Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"/>
  <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#dsa-sha1"/>
  <Reference URI="">
    <Transforms>
      <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
    </Transforms>
    <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
    <DigestValue>uooqbWYa5VCqcJCbuymBKqm17vY=</DigestValue>
  </Reference>
</SignedInfo> 

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

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

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

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

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

<KeyInfo>
  <KeyValue>
    <DSAKeyValue>
      <P>
        /KaCzo4Syrom78z3EQ5SbbB4sF7ey80etKII864WF64B81uRpH5t9jQTxe
        Eu0ImbzRMqzVDZkVG9xD7nN1kuFw==
      </P>
      <Q>li7dzDacuo67Jg7mtqEm2TRuOMU=</Q>
      <G>
        Z4Rxsnqc9E7pGknFFH2xqaryRPBaQ01khpMdLRQnG541Awtx/
        XPaF5Bpsy4pNWMOHCBiNU0NogpsQW5QvnlMpA==
      </G>
      <Y>
        qV38IqrWJG0V/mZQvRVi1OHw9Zj84nDC4jO8P0axi1gb6d+475yhMjSc/
        BrIVC58W3ydbkK+Ri4OKbaRZlYeRA==
      </Y>
    </DSAKeyValue>
  </KeyValue>
</KeyInfo> 

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

XMLデジタル署名APIの例

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

検証の例

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

$ javac Validate.java 
$ java Validate signature.xml

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

例11-5 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 JSR 105 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 =
            dbf.newDocumentBuilder().parse(new FileInputStream(args[0]));

        // 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 XMLSignature (generated above)
        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 i = signature.getSignedInfo().getReferences().iterator();
            for (int j=0; i.hasNext(); j++) {
                boolean refValid =
                    ((Reference) 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 list = keyInfo.getContent();

            for (int i = 0; i < list.size(); i++) {
                XMLStructure 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!");
        }

        //@@@FIXME: this should also work for key types other than DSA/RSA
        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-6 envelope.xml

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

例11-7 signature.xml

<?xml version="1.0" encoding="UTF-8"?>
<Envelope xmlns="urn:envelope">
  <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
    <SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
      <CanonicalizationMethod
        Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
        xmlns="http://www.w3.org/2000/09/xmldsig#"/>
      <SignatureMethod
        Algorithm="http://www.w3.org/2009/xmldsig11#dsa-sha256"
        xmlns="http://www.w3.org/2000/09/xmldsig#"/>
      <Reference URI="" xmlns="http://www.w3.org/2000/09/xmldsig#">
        <Transforms xmlns="http://www.w3.org/2000/09/xmldsig#">
          <Transform
            Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"
            xmlns="http://www.w3.org/2000/09/xmldsig#"/>
        </Transforms>
        <DigestMethod
          Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"
          xmlns="http://www.w3.org/2000/09/xmldsig#"/>
        <DigestValue xmlns="http://www.w3.org/2000/09/xmldsig#">uooqbWYa5VCqcJCbuymBKqm17vY=</DigestValue>
      </Reference>
    </SignedInfo>
    <SignatureValue xmlns="http://www.w3.org/2000/09/xmldsig#">eO7K1BdC0kzNvr1HpMf4hKoWsvl+oI04nMw55GO+Z5hyI6By3Oihow==</SignatureValue>
    <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
      <KeyValue xmlns="http://www.w3.org/2000/09/xmldsig#">
        <DSAKeyValue xmlns="http://www.w3.org/2000/09/xmldsig#">
          <P xmlns="http://www.w3.org/2000/09/xmldsig#">/KaCzo4Syrom78z3EQ5SbbB4sF7ey80etKII864WF64B81uRpH5t9jQTxeEu0ImbzRMqzVDZkVG9xD7nN1kuFw==</P>
          <Q xmlns="http://www.w3.org/2000/09/xmldsig#">li7dzDacuo67Jg7mtqEm2TRuOMU=</Q>
          <G xmlns="http://www.w3.org/2000/09/xmldsig#">Z4Rxsnqc9E7pGknFFH2xqaryRPBaQ01khpMdLRQnG541Awtx/XPaF5Bpsy4pNWMOHCBiNU0NogpsQW5QvnlMpA==</G>
          <Y xmlns="http://www.w3.org/2000/09/xmldsig#">OqFi0sGpvroi6Ut3m154QNWc6gavH3j2ZoRPDW7qVBbgk7XompuKvZe1owz0yvxq+1K+mWbL7ST+t5nr6UFBCg==</Y>
        </DSAKeyValue>
      </KeyValue>
    </KeyInfo>
  </Signature>
</Envelope>

XML署名の検証

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

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

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

DocumentBuilderFactory dbf = 
  DocumentBuilderFactory.newInstance(); 

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

dbf.setNamespaceAware(true); 

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

DocumentBuilder builder = dbf.newDocumentBuilder();  
Document doc = builder.parse(new FileInputStream(argv[0])); 

検証する署名要素の指定

ドキュメントに複数含まれている場合もあるため、検証する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 factory = 
  XMLSignatureFactory.getInstance("DOM"); 

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

XMLSignature signature = 
  factory.unmarshalXMLSignature(valContext); 

XML署名の検証

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

boolean coreValidity = signature.validate(valContext);

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

KeySelectorの使用

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

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

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

KeyValueKeySelectorは、抽象KeySelectorクラスの固定実装です。KeyValueKeySelector実装は、XMLSignatureのKeyInfo要素の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 list = keyInfo.getContent();

    for (int i = 0; i < list.size(); i++) {
      XMLStructure 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(SignatureMethod.DSA_SHA1)) {
      return true;
    } else if (algName.equalsIgnoreCase("RSA") &&
        algURI.equalsIgnoreCase(SignatureMethod.RSA_SHA1)) {
      return true;
    } else {
      return false;
    }
  }
} 

GenEnvelopedの例

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

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

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

例11-8 GenEnveloped.java

import javax.xml.crypto.*;
import javax.xml.crypto.dsig.*;
import javax.xml.crypto.dom.*;
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.Collections;
import java.util.Iterator;
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 JSR 105 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/2000/09/xmldsig#dsa-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>K8M/lPbKnuMDsO0Uzuj75lQtzQI=<DigestValue>
 *     </Reference>
 *   </SignedInfo>
 *   <SignatureValue>
 *     DpEylhQoiUKBoKWmYfajXO7LZxiDYgVtUtCNyTgwZgoChzorA2nhkQ==
 *   </SignatureValue>
 *   <KeyInfo>
 *     <KeyValue>
 *       <DSAKeyValue>
 *         <P>
 *           rFto8uPQM6y34FLPmDh40BLJ1rVrC8VeRquuhPZ6jYNFkQuwxnu/wCvIAMhukPBL
 *           FET8bJf/b2ef+oqxZajEb+88zlZoyG8g/wMfDBHTxz+CnowLahnCCTYBp5kt7G8q
 *           UobJuvjylwj1st7V9Lsu03iXMXtbiriUjFa5gURasN8=
 *         </P>
 *         <Q>
 *           kEjAFpCe4lcUOdwphpzf+tBaUds=
 *         </Q>
 *         <G>
 *           oe14R2OtyKx+s+60O5BRNMOYpIg2TU/f15N3bsDErKOWtKXeNK9FS7dWStreDxo2
 *           SSgOonqAd4FuJ/4uva7GgNL4ULIqY7E+mW5iwJ7n/WTELh98mEocsLXkNh24HcH4
 *           BZfSCTruuzmCyjdV1KSqX/Eux04HfCWYmdxN3SQ/qqw=
 *         </G>
 *         <Y>
 *           pA5NnZvcd574WRXuOA7ZfC/7Lqt4cB0MRLWtHubtJoVOao9ib5ry4rTk0r6ddnOv
 *           AIGKktutzK3ymvKleS3DOrwZQgJ+/BDWDW8kO9R66o6rdjiSobBi/0c2V1+dkqOg
 *           jFmKz395mvCOZGhC7fqAVhHat2EjGPMfgSZyABa7+1k=
 *         </Y>
 *       </DSAKeyValue>
 *     </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 {

        // 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),
             Collections.singletonList
              (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/2000/09/xmldsig#dsa-sha256", null),
             Collections.singletonList(ref));

        // Create a DSA KeyPair
        KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
        kpg.initialize(2048);
        KeyPair kp = kpg.generateKeyPair();

        // Create a KeyValue containing the DSA 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(Collections.singletonList(kv));

        // Instantiate the document to be signed
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(true);
        Document doc =
            dbf.newDocumentBuilder().parse(new FileInputStream(args[0]));

        // Create a DOMSignContext and specify the DSA PrivateKey and
        // location of the resulting XMLSignature's parent element
        DOMSignContext dsc = new DOMSignContext
            (kp.getPrivate(), doc.getDocumentElement());

        // 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-9 envelope.xml

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

XML署名の生成

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

XML署名およびそのさまざまなコンポーネントの基本的な知識が、このセクションの理解に役立ちます。詳細は、http://www.w3.org/TR/xmldsig-core/を参照してください。

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

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

DocumentBuilderFactory dbf =
  DocumentBuilderFactory.newInstance(); 

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

dbf.setNamespaceAware(true); 

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

DocumentBuilder builder = dbf.newDocumentBuilder();  
Document doc = builder.parse(new FileInputStream(argv[0])); 

公開鍵のペアの作成

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

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

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

署名コンテキストの作成

署名を生成するための入力パラメータを含む、XMLデジタル署名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 (ここでは、SHA1を使用)

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

Reference ref = fac.newReference
  ("", fac.newDigestMethod(DigestMethod.SHA1, null),
    Collections.singletonList
      (fac.newTransform(Transform.ENVELOPED,
        (TransformParameterSpec) null)), null, null); 

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

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

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

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

SignedInfo si = fac.newSignedInfo
  (fac.newCanonicalizationMethod
    (CanonicalizationMethod.INCLUSIVE_WITH_COMMENTS,
      (C14NMethodParameterSpec) null),
    fac.newSignatureMethod(SignatureMethod.DSA_SHA1, null),
    Collections.singletonList(ref)); 

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

KeyInfoFactory kif = fac.getKeyInfoFactory();

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

KeyValue kv = kif.newKeyValue(kp.getPublic());
KeyInfo ki = kif.newKeyInfo(Collections.singletonList(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));