Java XMLデジタル署名APIは、XML署名を生成および検証するための標準のJava APIです。 このAPIは、Java Community ProcessのもとでJSR 105として定義されました(http://jcp.org/en/jsr/detail?id=105
を参照)。
XML署名は、任意のタイプのデータ(XMLまたはバイナリ)に適用できます(http://www.w3.org/TR/xmldsig-core/
を参照)。 結果の署名は、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
パッケージには、XML署名の生成やXMLデータの暗号化など、XML暗号化操作を行う場合に使用する共通クラスが含まれています。 このパッケージで注意すべき2つのクラスのうちの1つはKeySelector
クラスで、開発者は、KeyInfo
オブジェクトに含まれる情報を使用してキーを見つけてオプションで検証する実装を提供できます。もう1つのクラスはURIDereferencer
クラスで、開発者は、実装を間接参照する独自のURIを作成および指定できます。
javax.xml.crypto.dsig
パッケージには、W3C XMLデジタル署名仕様で定義されているコア要素を表すインタフェースが含まれています。 もっとも重要なのはXMLSignature
クラスです。このクラスを使用すると、XMLデジタル署名の署名および検証を行うことができます。 XML署名構造または要素のほとんどは、対応するインタフェースによって表されます(KeyInfo
構造を除く。これは独自のパッケージに含まれており、次の段落で説明します)。 これらのインタフェースには、SignedInfo
、CanonicalizationMethod
、SignatureMethod
、Reference
、Transform
、DigestMethod
、XMLObject
、Manifest
、SignatureProperty
およびSignatureProperties
が含まれます。 XMLSignatureFactory
クラスは、これらのインタフェースを実装するオブジェクトを作成する場合に使用する抽象ファクトリです。
javax.xml.crypto.dsig.keyinfo
パッケージには、W3C XMLデジタル署名勧告で定義されているほとんどのKeyInfo
構造を表すインタフェースが含まれており、KeyInfo
、KeyName
、KeyValue
、X509Data
、X509IssuerSerial
、RetrievalMethod
およびPGPData
が含まれます。 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.Signature
やjava.security.MessageDigest
など、基盤となるJCAエンジン・クラスを使用して暗号化操作を実行するようにしてください。
XMLSignatureFactory
およびKeyInfoFactory
クラス以外に、JSR 105は、変換および正規化アルゴリズム用のサービス・プロバイダ・インタフェースもサポートしています。 TransformService
クラスを使用すると、特定のXMLメカニズム・タイプ用の固有の変換または正規化アルゴリズムの実装を開発およびプラグインすることができます。 TransformService
クラスは、実装を登録およびロードするときに標準JCAプロバイダ・モデルを使用します。 各JSR 105実装は、TransformService
クラスを使用して、生成または検証するXML署名内の変換および正規化アルゴリズムをサポートするプロバイダを見つけるようにしてください。
XML署名を使用すると、XMLかバイナリかにかかわらず、任意のデータに署名できます。 データは1つ以上のReference要素内のURIによって識別されます。 XML署名は分離、内包または包含の3つの形式のうち、1つ以上の形式で記述されます。 分離署名は外部、つまり署名要素自体の外にあるデータに対するものです。 内包署名は署名要素の内側にあるデータに対する署名です。包含署名は署名対象のデータ内に含まれる署名です。
XML署名の内容を説明するもっとも簡単な方法は、実際のサンプルを示し、各コンポーネントについて詳細に説明することです。 次に、XMLドキュメントの内容に対して生成される包含XML署名の例を示します。 署名される前のドキュメントの内容は、次のとおりです。
結果の包含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署名のセキュアな検証モードでは、サービス拒否やその他のタイプのセキュリティ問題を引き起こす場合がある潜在的に攻撃的な構文が含まれている可能性のあるXML署名からユーザーを保護できます。
XML署名のセキュアな検証モードは、デフォルトで有効になっています。
必要に応じて、ユーザー自身の責任で、システム・プロパティorg.jcp.xml.dsig.secureValidation
をfalse
に設定するか、DOMValidateContext.setProperty()
メソッドを使用してorg.jcp.xml.dsig.secureValidation
プロパティをBoolean.FALSE
に設定することで、XMLシグネチャ・セキュア検証モードを無効にできます。 システム・プロパティは、APIプロパティの値よりも優先されます。
XML署名のセキュアな検証モードを有効にすると、XML署名はよりセキュアに処理されます。 様々なXML署名の構文に制限を設定して、サービス拒否攻撃などの状況を回避します。 デフォルトでは、次の制限が強制されます。
SignedInfo
またはManifest Reference
要素の数を30以下に制限 Reference
変換の数を5以下に制限
Reference
IDが一意であることを確認して、シグネチャ・ラッピング攻撃を防止 http
、https
またはfile
型のReference
URIを禁止RetrievalMethod
要素で別のRetrievalMethod
要素を参照することはできません
また、jdk.xml.dsig.secureValidationPolicy
セキュリティ・プロパティを使用して、前にリストした制限を制御および微調整したり、追加の制限を追加できます。 詳細は、java.security
ファイルで、このセキュリティ・プロパティの定義を参照してください。
次のセクションでは、XMLデジタル署名APIを使用する方法を示す2つの例について説明します。
このセクションで示すコードは、Validate.java
ファイルにあります。このファイルは
docs/technotes/guides/security/xmldsig
ディレクトリにあります。 処理のもとになるファイルenvelopedSignature.xml
は、同じディレクトリ内にあります。
この例をコンパイルして実行するには、docs/technotes/guides/security/xmldsig
ディレクトリから次のコマンドを実行します。
$javac Validate.java
$java Validate signature.xml
signature.xml
の署名を検証します。
この例では、JSR 105 APIを使用してXML署名を検証する方法を示します。 この例はDOM (Document Object Model)を使用して、署名要素を含むXMLドキュメントおよびJSR 105 DOM実装を解析し、署名を検証します。
最初に、JAXP DocumentBuilderFactory
を使用して、署名を含むXMLドキュメントを解析します。 アプリケーションは、次のコード行を呼び出すことによって、DocumentBuilderFactory
のデフォルト実装を取得します。
ファクトリ名前空間認識も作成します。
次に、このファクトリを使用して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
要素の参照です。
KeyValueKeySelector
については、「KeySelectorの使用」で詳細に説明します。
Signature
要素の内容をXMLSignature
オブジェクトに抽出します。 このプロセスをアンマーシャリングと呼びます。 Signature
要素はXMLSignatureFactory
オブジェクトを使用して非整列化されます。 アプリケーションは次のコード行を呼び出すことによって、XMLSignatureFactory
のDOM実装を取得できます。
次に、ファクトリのunmarshalXMLSignature
メソッドを呼び出してXMLSignature
オブジェクトを非整列化し、先に作成した検証コンテキストを渡します。
これで、署名を検証する準備が整いました。 XMLSignature
オブジェクトでvalidate
メソッドを呼び出して、次に示すように検証コンテキストを渡します。
W3C XML署名勧告
のコア検証規則
に従って署名が正常に検証されると、validate
メソッドは「true」を返します。それ以外の場合はfalseを返します。
XMLSignature.validate
メソッドがfalseを返した場合、失敗の原因の絞込みを試行できます。 コアXML署名検証には、次の2つのフェーズがあります。
署名が有効になるには、それぞれのフェーズが成功する必要があります。 署名の暗号化の検証が失敗したかどうかをチェックするために、次に示すようにステータスをチェックできます。
boolean sv = signature.getSignatureValue().validate(valContext); System.out.println("signature validation status: " + sv);
次に示すように、参照に対して繰返し実行して、各参照の検証ステータスをチェックすることもできます。
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); }
KeySelectors
は、XMLSignatureの検証に必要なキーを見つけて選択するために使用されます。 以前は、DOMValidateContext
オブジェクトを作成したときは、KeySelector
オブジェクトを最初の引数として渡しました。
また、署名の検証に必要なキーがすでにわかっている場合は、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("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; } } }
このセクションで説明しているコードは、GenEnveloped.java
ファイルにあります。このファイルは
docs/technotes/guides/security/xmldsig
ディレクトリにあります。 処理のもとになるファイルenvelope.xml
は、同じディレクトリ内にあります。 このファイルは、ファイルenvelopedSignature.xml
を生成します。
このサンプルをコンパイルし、実行するには、
docs/technotes/guides/security/xmldsig
ディレクトリから、次のコマンドを実行します。
$ javac GenEnveloped.java
$ java GenEnveloped envelope.xml envelopedSignature.xml
サンプル・プログラムはファイルenvelope.xml内のドキュメントの包含署名を生成し、現在の作業ディレクトリ内のファイルenvelopedSignature.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
のデフォルト実装を取得します。
ファクトリ名前空間認識も作成します。
次に、このファクトリを使用して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つのパラメータを渡します。パラメータは、ドキュメントの署名に使用される秘密キーと署名対象のドキュメントのルートです。
Signature
要素のさまざまな部分を組み合わせて、XMLSignature
オブジェクトにします。 これらのオブジェクトはすべて、XMLSignatureFactory
オブジェクトを使用して、作成され組み合わせられます。 アプリケーションは次のコード行を呼び出すことによって、XMLSignatureFactory
のDOM実装を取得します。
次に示すように、さまざまなファクトリ・メソッドを呼び出してXMLSignature
オブジェクトのさまざまな部分を作成します。 Reference
オブジェクトを作成して、そのオブジェクトに次のものを渡します。
次に、SignedInfo
オブジェクトを作成します。これは、次に示すように、実際に署名されるオブジェクトです。 SignedInfo
を作成するときは、次のものをパラメータとして渡します。
次に、オプションのKeyInfo
オブジェクトを作成します。このオブジェクトには、受け側が署名の検証に必要なキーを見つけられるようにする情報が含まれています。 この例では、公開キーを含むKeyValue
オブジェクトを追加します。 KeyInfo
およびその様々なサブタイプを作成するには、KeyInfoFactory
オブジェクトを使用します。このオブジェクトは次に示すように、XMLSignatureFactory
のgetKeyInfoFactory
メソッドを呼び出すことによって取得できます。
次に、KeyInfoFactory
を使用してKeyValue
オブジェクトを作成し、KeyInfo
オブジェクトに追加します。
KeyValue kv = kif.newKeyValue(kp.getPublic()); KeyInfo ki = kif.newKeyInfo(Collections.singletonList(kv));
最後に、XMLSignature
オブジェクトを作成し、先に作成したSignedInfo
およびKeyInfo
オブジェクトをパラメータとして渡します。
まだ実際には署名を生成していません。次のステップで生成します。
これで、署名を生成する準備が整いました。XMLSignature
オブジェクトでsign
メソッドを呼び出して、次に示すように署名コンテキストを渡します。
これで、結果のドキュメントには署名が含まれています。署名は、ルート要素の最後の子要素として挿入されました。
次のコードを使用して、結果の署名済みドキュメントをファイルまたは標準出力に印刷できます。
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));