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));