![](https://blogimg.goo.ne.jp/user_image/6e/b1/7ddfca60a086bce59e13ad1b6fea2fff.png)
「法務省 不動産登記の電子署名をValidate.javaで検証してみた」では、一人がPDF文書に電子署名した署名を検証した。
申請総合ソフトのPDF電子署名は、複数人が同一文書に電子署名する事が出来る。複数人が電子署名を行うとXML署名ファイル内に複数人の署名ブロック(Signature ブロック)が生成される。一人の場合は、SignatureブロックのidアトリビュートがSignatureで生成される。二人目は、idアトリビュートがSignature1、3人目は、idアトリビュートがSignature2と複数人のSignatureブロックを内包したXML電子署名ファイルが生成される。
「法務省 不動産登記の電子署名をValidate.javaで検証してみた」では、最初の一人の電子署名だけが確認される。
複数人の電子署名を検証できるようプログラムを修正してみた。
複数人の電子署名に対応したValidate.java
import java.io.File;
import java.io.FileInputStream;
import java.security.Key;
import java.security.KeyException;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.security.cert.CertificateParsingException; //--added
import java.util.Iterator;
import java.util.List;
import java.util.HexFormat; //--added
import java.util.Arrays; //--added
import java.util.Collection; //--added
import java.lang.reflect.Array; //--added
import javax.xml.crypto.AlgorithmMethod;
import javax.xml.crypto.KeySelector;
import javax.xml.crypto.KeySelectorException;
import javax.xml.crypto.KeySelectorResult;
import javax.xml.crypto.XMLCryptoContext;
import javax.xml.crypto.XMLStructure;
import javax.xml.crypto.dsig.Reference;
import javax.xml.crypto.dsig.SignatureMethod;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.KeyValue;
import javax.xml.crypto.dsig.keyinfo.X509Data;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.w3c.dom.Element; //-- added
/**
* 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 {
String fileName = args[0];
// Instantiate the document to be validated
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document doc = dbf.newDocumentBuilder().parse(
new FileInputStream(fileName));
// Find Signature element
NodeList nl = doc.getElementsByTagNameNS(XMLSignature.XMLNS,
"Signature");
if (nl.getLength() == 0) {
throw new Exception("Cannot find Signature element");
}
//-- added(外部ファイルのベースディレクトリを指定)**移動
String cpath = new File(fileName).getParent(); //--**移動
cpath = "file://" + cpath.replace('\\', '/') + "/"; //--**移動
System.out.println("setBaseURI-> " + cpath); //--**移動
//--added for multiple signature loop
System.out.println("Signature block(s) = " + nl.getLength() );
for ( int k = 0; k < nl.getLength() ; k++ ) {
String SignatureId = nl.item(k).getAttributes().item(0).getNodeValue() ;
System.out.println("\nSignature id = " + SignatureId );
// 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(k)); //-- 変更
//--added
valContext.setBaseURI(cpath);
//-- added(署名対象XMLエレメントのアトリビュートを指定)**修正
// 申請用総合ソフト 登録通知
NodeList tg = doc.getElementsByTagName("BODY");
if ( tg.getLength() != 0 ) {
valContext.setIdAttributeNS( (Element)tg.item(0), null, "ID");
System.out.println("Find BODY element and setIdAttibute=\"ID\"");
}
// 医療費通知(保険組合)
tg = doc.getElementsByTagName("TEG700");
if ( tg.getLength() != 0 ) {
valContext.setIdAttributeNS( (Element)tg.item(0), null, "id");
System.out.println("Find TEG700 element and setIdAttribute=\"id\"");
}
// e-Tax( 所得税 RKO0010. 証明書更新 PTE0010, 電子申請等証明書 TEB120
tg = doc.getElementsByTagName("RKO0010");
if ( tg.getLength() != 0 ) {
valContext.setIdAttributeNS( (Element)tg.item(0), null, "id");
System.out.println("Find RKO0010 element and setIdAttribute=\"id\"");
}
tg = doc.getElementsByTagName("PTE0010");
if ( tg.getLength() != 0 ) {
valContext.setIdAttributeNS( (Element)tg.item(0), null, "id");
System.out.println("Find PTE0010 element and setIdAttribute=\"id\"");
}
tg = doc.getElementsByTagName("TEB120");
if ( tg.getLength() != 0 ) {
valContext.setIdAttributeNS( (Element)tg.item(0), null, "id");
System.out.println("Find TEB120 element and setIdAttribute=\"id\"");
}
// 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");
}
//-- added for multiple signature loop
}
}
/**
* 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 X509Data) {
PublicKey pk = null;
List<?> l = ((X509Data) xmlStructure).getContent();
if (l.size() > 0) {
X509Certificate cert = (X509Certificate) l.get(0);
pk = cert.getPublicKey();
//-- added(署名公開鍵のDNと有効期限を表示)
System.out.println("X509Certificate Subject -> " + cert.getSubjectX500Principal().getName() );
System.out.println("X509Certificate NotAfter -> " + cert.getNotAfter().toString() );
//-- added for SubjectAltName
try { Collection<List<?>> alternativeNames = cert.getSubjectAlternativeNames();
if ( alternativeNames != null ) {
for ( List<?> alternativeName : alternativeNames ) {
if ( (Integer) alternativeName.get(0) == 0 ) {
Object generalName = alternativeName.get(1);
if ( generalName instanceof String) {
System.out.println(generalName);
} else {
HexFormat formatHex = HexFormat.ofDelimiter("").withUpperCase();
String tmp = new String( formatHex.formatHex((byte[]) generalName));
if ( tmp.indexOf("2A83088C9B5508050501") > 0 ) { //JPKI OID Common Name 1.2.392.200149.8.5.5.1
String tmp1 = new String(Arrays.copyOfRange((byte[]) generalName,18, Array.getLength(generalName)) );
System.out.println("署名者名前: " + tmp1) ;
}
if ( tmp.indexOf("2A83088C9B5508050505") > 0 ) { //JPKI OID Address 1.2.392.200149.8.5.5.5
String tmp1 = new String(Arrays.copyOfRange((byte[]) generalName,18, Array.getLength(generalName)) );
System.out.println("署名者住所: " + tmp1) ;
}
} // else
}
} // for
}
} catch (CertificateParsingException e) { System.out.println("CertificateParsing ");}
if (algEquals(sm.getAlgorithm(), pk.getAlgorithm())) {
return new SimpleKeySelectorResult(pk);
}
}
}
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) {
//-- added(署名に指定されたアルゴリズム)
System.out.println("Algorithm: " + algURI );
if (algName.equalsIgnoreCase("DSA")
&& algURI.equalsIgnoreCase(SignatureMethod.DSA_SHA1)) {
return true;
} else if (algName.equalsIgnoreCase("RSA")
&& algURI.equalsIgnoreCase(SignatureMethod.RSA_SHA256)) {
return true;
//-- added (sha1も対象とする設定)
} else if (algName.equalsIgnoreCase("RSA")
&& algURI.equalsIgnoreCase(SignatureMethod.RSA_SHA1)) {
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;
}
}
}
署名に使用された証明書の「SubjectAltName」に記載された「名前」と「住所」を表記するようにした。ASN.1で抽出されるのでASN.1を扱うライブラリィを使うのが簡単だが、JDKの標準パッケージだけで抽出して表示してみた。
署名に使用された証明書の有効性確認が出来ていない。
二つの署名ファイル検証
$ java -Djava.util.logging.config.file=./logging.properties -Djava.security.properties=./java.security Validate /Users/someone/Documents/関連/登記関連/署名テスト/複数署名/協議書/協議書/協議書.xml
setBaseURI-> file:///Users/someone/Documents/関連/登記関連/署名テスト/複数署名/協議書/協議書/
Signature block(s) = 2
Signature id = Signature
X509Certificate Subject -> CN=202299999999999999999999993B,L=XXXX,L=Tokyo-to,C=JP
X509Certificate NotAfter -> Mon Jan 99 23:59:59 JST 2027
署名者名前: 松山 一郎
署名者住所: 東京都目黒区三田一丁目4番1号
Algorithm: http://www.w3.org/2001/04/xmldsig-more#rsa-sha256
Signature passed core validation
Signature id = Signature1
X509Certificate Subject -> CN=202299999999999999999999991B,L=XXXX,L=Tokyo-to,C=JP
X509Certificate NotAfter -> Thu Feb 99 23:59:59 JST 2027
署名者名前: 鈴木 洋子
署名者住所: 東京都文京区関口ニ丁目10番8号
Algorithm: http://www.w3.org/2001/04/xmldsig-more#rsa-sha256
Signature passed core validation
(一部データを秘匿)
「法務省 不動産登記の電子署名をValidate.javaで検証してみた」
「法務省 不動産登記の電子署名を確認してみた」
「法務省 不動産登記の複数人電子署名をValidate.javaで検証してみた」
「法務省 不動産登記の電子署名用証明書をValidate.javaで検証してみた」