備忘ぶ録-新犬小屋

ココログ「備忘ぶ録(https://kotatuinu.cocolog-nifty.com/blog/)」のコピー場所です。

PKCS#12形式ファイルを使ってXML文書に署名付与したり、署名検証したり

PKCS#12(拡張子.p12のファイル)を使ってXML文書にXML署名(Enveloped署名)を行うのと署名検証するプログラムをC#で作ってみた。

  • 基本、SignedXmlクラスに載っているコードを使った。(2つあるけど、上の方は署名の公開鍵を埋め込まない。下の方はRSAKeyValueを埋め込んでいる)
    でも、このコードは、証明書(RSA)を生成してそれをKeyInfo要素にRSAKeyValue要素が設定する。
    PKCS#12形式ファイルを読み込んで使いたいし、x509Certificate要素を入れたい。
  • PKCS#12形式ファイルのprivateKeyを取り出すには、手順がある。
    • PKCS#12ファイルを、new X509Certificate2(<filename>, <password>, X509KeyStorageFlags.Exportable);で読み込む。
    • そのx509からprivateKeyを取り出すには、(RSACryptoServiceProvider)x509.PrivateKey;とやる。
      この使おうとするとsignedXml.ComputeSignature()で「無効なアルゴリズムが指定されました」例外になってしまう・・・。PersistKeyInCspがtrueの所為?
    • 上記の例外を回避するには、別のRSACryptoServiceProvider()インスタンスに対して、ImportParameters(privateKey.ExportParameters(true));とやって、先ほどのprivateKeyを食わせる。するとPersistKeyInCspがfalseになっているね。
  • KeyInfo要素にRSAKeyValue要素が入るのは、keyInfo.AddClause(new RSAKeyValue( (RSA)Key ));とやっている。
    x509Certificate要素を入れるのには、keyInfo.AddClause(new KeyInfoX509Data(x509));とやる。

●引数:

  • 第1引数:PKCS#12ファイル
  • 第2引数:PKCS#12ファイルのパスワード
  • 第3引数:署名を付与するXML文書
  • 第4引数:署名を付与する要素(id属性の値を設定)
  • 第5引数:出力する署名済XML文書
C:\test>VerfySignedXML.exe my-identity.p12 test test.xml xxxx test_signed.xml
The XML signature is valid.

 

 
●署名対象のXML : test.xml
<?xml version="1.0" encoding="utf-8" ?>
<root>
<creditcard id="xxxx">
<number>19834209</number>
<expiry>02/02/2002</expiry>
</creditcard>
</root>

 

●署名付与したXML : test_signed.xml
<?xml version="1.0" encoding="utf-8"?>
<root>
<creditcard id="xxxx">
<number>19834209</number>
<expiry>02/02/2002</expiry>
</creditcard>
<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="#xxxx"><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" /></Transforms><DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" /><DigestValue>7WFHBFyOfyEEGK6n7OHvdA6K345HSlAVwPFaEWgXcX0=</DigestValue></Reference></SignedInfo><SignatureValue>JWPhDDxFQalUmtWPdd8o55To3p5RI7TJvnXz79H2tb60J4B1lg4TeTvTOtHZuiJJpHZMeNyuARtwNRX+etDr9j4UmXdUJ/CXvxJFXajoYXRwTGYK6i/RF0QDeoOexSFYrK+JZeHrUV6T7UMU6O5ZxrmuU+c7U7Ki0dlHypdjQac=</SignatureValue><KeyInfo><X509Data><X509Certificate>MIIClTCCAf4CCQCBK5YXcZkuqjANBgkqhkiG9w0BAQsFADCBjjELMAkGA1UEBhMCSlAxETAPBgNVBAgMCEtBTkFHQVdBMREwDwYDVQQHDAhZT0tPU1VLQTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRIwEAYDVQQDDAlLT1RBVFVJTlUxIjAgBgkqhkiG9w0BCQEWE2tvdGF0dWludUBuaWZ0eS5jb20wHhcNMTcxMjA5MTE1NTQ3WhcNMjcxMjA3MTE1NTQ3WjCBjjELMAkGA1UEBhMCSlAxETAPBgNVBAgMCEtBTkFHQVdBMREwDwYDVQQHDAhZT0tPU1VLQTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRIwEAYDVQQDDAlLT1RBVFVJTlUxIjAgBgkqhkiG9w0BCQEWE2tvdGF0dWludUBuaWZ0eS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMPnNgYbe5x2xJyEshyH768ZU8Un5e5j5uKsihiTOya9wAdxNJeWINB21zXe+mjKxnOsmATPdGZ0DH82xqz+2mX//JKqwDMsG8nMacUI4BiCvYGvlTyxWkRQw3LKU2ufQQ+GYboypwQE51X3S+rRDLGoLuG0BtRooAvah1adJfhdAgMBAAEwDQYJKoZIhvcNAQELBQADgYEAClBDgTCsbe19+CYomJC0PciFw/tvk6/TiC6VSNuQmFg61KrTaU4XFmj/u8qbD8ESyXaJKavcZmjWhKk6F6JSx9YYBz0b1dXz/9l0po6AwUooDk3yp5NtRGiErAcM6MCZ5LPM9+255hMa3fxX26Y7xpPxH84jv6j2fiYcsdYknaQ=</X509Certificate></X509Data></KeyInfo></Signature></root>
using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography.Xml;
using System.Xml;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography.Xml;
using System.Xml;

public class VerifySignedXML
{
    // 引数
    //  1:PKCS12ファイル
    //  2:パスワード
    //  3:署名前XMLファイル(入力)
    //  4:署名付与対象(ID属性の値)
    //  5:署名済XMLファイル(出力)
    public static void Main(String[] args)
    {
        if (args.Length != 5)
        {
            return;
        }
        sign(args[0], args[1], args[2], args[3], args[4]);
        verfy(args[4]);
    }

    public static void sign(String filename, string password, string xmlFilename, string id, string signedXmlFilename)
    {
        try
        {
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.PreserveWhitespace = true;
            xmlDoc.Load(xmlFilename);
            XmlNodeList list = xmlDoc.SelectNodes(string.Format(@"//*[@id='{0}']", id));
            if (list.Count == 0)
            {
                return;
            }

            X509Certificate2 x509 = new X509Certificate2(filename, password, X509KeyStorageFlags.Exportable);
            SignXml(xmlDoc, x509, id);
            xmlDoc.Save(signedXmlFilename);
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
    }

    public static void SignXml(XmlDocument xmlDoc, X509Certificate2 x509, string uri)
    {
        if (xmlDoc == null)
        {
            throw new ArgumentException("xmlDoc");
        }
        if (x509 == null)
        {
            throw new ArgumentException("x509");
        }

        SignedXml signedXml = new SignedXml(xmlDoc);

        RSACryptoServiceProvider privateKey = (RSACryptoServiceProvider)x509.PrivateKey;
        RSACryptoServiceProvider privateKey1 = new RSACryptoServiceProvider();
        privateKey1.ImportParameters(privateKey.ExportParameters(true));
        signedXml.SigningKey = privateKey1;

        Reference reference = new Reference();
        reference.Uri = "#" + uri;

        XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
        reference.AddTransform(env);

        signedXml.AddReference(reference);

        KeyInfo keyInfo = new KeyInfo();
        keyInfo.AddClause(new KeyInfoX509Data(x509));
        signedXml.KeyInfo = keyInfo;

        signedXml.ComputeSignature();

        XmlElement xmlDigitalSignature = signedXml.GetXml();

        xmlDoc.DocumentElement.AppendChild(xmlDoc.ImportNode(xmlDigitalSignature, true));

    }

    public static void verfy(string signedXmlFilename)
    {
        try
        {
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.PreserveWhitespace = true;
            xmlDoc.Load(signedXmlFilename);

            bool result = VerifyXml(xmlDoc);
            if (result)
            {
                Console.WriteLine("The XML signature is valid.");
            }
            else
            {
                Console.WriteLine("The XML signature is not valid.");
            }

        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
    }

    public static Boolean VerifyXml(XmlDocument Doc)
    {
        SignedXml signedXml = new SignedXml(Doc);
        XmlNodeList nodeList = Doc.GetElementsByTagName("Signature");

        if (nodeList.Count <= 0)
        {
            throw new CryptographicException("Verification failed: No Signature was found in the document.");
        }
        if (nodeList.Count >= 2)
        {
            throw new CryptographicException("Verification failed: More that one signature was found for the document.");
        }

        signedXml.LoadXml((XmlElement)nodeList[0]);
        return signedXml.CheckSignature();
    }
}