如何使用 Yubico 的 Yubikey 通过 C# 对 XML 文件进行签名?

问题描述 投票:0回答:1

我有两个

Smart Cards
:

  1. Yubico 的 YubiKey 5C NFC
  2. SafeNet eToken 5110 系列

我安装了所有必要的驱动程序和工具

Smart Cards

现在的目标是创建一个

C# method
,用这些
unsigned XML Claim File
中的
private key
签署
Smart Cards
。然后,应将此
signed XML Claims File
发送到带有
API
POST request
,后者使用
public key
验证签名。 我们可以使用
API
Yubikey
将公钥上传到
FIDO2
,然后按照一个过程将
PIV
证书上传到 API。然而,此 API 目前仅支持
Yubikey from Yubico
,不支持其他
Smart Cards

未签名的 XML 声明文件:

<?xml version="1.0"?>
<claim xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.[THE-API].net/claim/"
    xsi:schemaLocation="http://www.[THE-API].ch/claim/ file:/C:/path/to/claim.xsd">
    <domain>[THE-API]</domain>
    <canton>ZH</canton>
    <authTokens>
        <authToken>22291119-8888-8361-12h6-fhfaw1g2c666</authToken>
    </authTokens>
</claim>

已签名的 XML 索赔文件:

<?xml version="1.0"?>
<claim xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.[THE-API].net/claim/"
    xsi:schemaLocation="http://www.[THE-API].ch/claim/ file:/C:/path/to/claim.xsd">
    <domain>[THE-API]</domain>
    <canton>ZH</canton>
    <authTokens>
        <authToken>22291119-8888-8361-12h6-fhfaw1g2c666</authToken>
    </authTokens>
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="">
        <Transforms>
          <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
        </Transforms>
        <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
        <DigestValue>zL9#Rg&5QwPfS!v2XcY@p7qG%mK4lEh$A3oT8u6bM+dH1eZsF</DigestValue>
      </Reference>
    </SignedInfo> <SignatureValue>Kpdz5AVasU6nOMaFrhxHbGT8Dj1WcmvJItEZC92ilRFNusmqDxPwSvzxKrgH6JQeOTuzI7A51LGE8Dh7plgnOKw5uoBEdKPLMQd9TMyjvzeVZ6sOjAcHx1o7oavkRJi8RWuLp2YRr2iozOJ03hMz6aa6DP10b5HOLqbdwwXouHdKzqz6NNeEIRnY6vF2v4UsUtxyXj5jrUjsw3cOfIjRzVRBfzUUT8yYpmqfNNFWLokkxz3s9Vd06yex3kryMbs3QQ3ynDf8gBWzBke6wbB6Xn3MihsCT6xZ4DG2fhcaTEfMDCe66mBpTkEz2vbgBbcEfC1LbQzCjXNoVdYiQcwwLV2ukvXpB1WHTf6XxXVFAJQMs88j7nQFFkl4vQMy25asqEwM==</SignatureValue>
    <KeyInfo>
      <X509Data>
        <X509SubjectName>CN=Max Mustermann, G=Max, SN=Mustermann, O=Paper Company, OID.1.1.1.11=CHE-111.111.111, C=CH</X509SubjectName>
        <X509Certificate>vSdBE1NKMGYbejmfGbrnc4DSzS8YPxpyBVEJEPv0ytqn9BcV4pVqyJawR4R4Qaa8zVUh4V1cagTXKTdAuH2qFUiZuGhVFeJ8q1nY6TBpexUXe4AQ46HNknEfGSJEBRqrPHGKnju5GLFkmiTcBema7kVh8vDEi4Zn248YeNRhQHrq2pnM3FwqTLGjy03RYR51bxS4YMEY28b20k5KZ7L23veMAE98cd7w7nGtbjK7P8YKABxFQyXuLSVzkpu7hwyYXZ6tcgdRULFgYc6jzeKPz6ZTt1eNU0rzJ1frESJ8CG6eV89SxLC8t5GNdPbMXKurTu0ATXxpdw3ftHt80PAX8iejbPtMBAFTfead6GERMZgdDNKTEtWDAquB70WcRSek6vxHn9gYfg0qkdDce0avYd9Pu8VKttwtMYywew9hQQH4GGtXqQ1n8DbhLr3yML7iWUF8P9YjnuPJDGqHxdSaB42C0QkwSGbG3BH3kCYzDJJpxV4L8F32RKGxYhm2FqzJ5egHHrVTMAu5Lv8HGLQebX4Se3DhUNaf7zYDPUXHBUhqFF3jxYhAB4quBEYN552kDhW6HeeYF5PdUF7ARijqwQiWCE6k1MVfedGXRbNh3xVVzJbXBpUHYQJp4DNVMiYBVa4WSZch1tNCwuiXB6V05VNmhwxeku084GXFxmxGg1Y837ywRFURyBim2WyYWdXJdFFAtV2CpvvgjiZaRCXpeynixLSEbf3d0fBgmm1gb1SCQXAmdck2XNVMhJVwnbdEwJ4bPELJS8Rz2yM8nHPmL5UzpMnwN1wiJPMGiEMzjV0hG3kBn1DjW1gDJtPREbEpj2uSE8EfhiBU1A4kJ1i6zfQXNKFWZBFhxWUDFLg0cBq0xzXgMBSvMtiqA9aQwSKUx9niUGigxR4SCFFZJXgbYxFxvWt0KaDVkLWzYLPCddVL5HP4BEKj1PRH9pD0DKxga55hwDYVzHC6KfZF7qmKqTLAY4c6wtpdSvtwZjBGuh81TT0Qg0pMfCA7TrGjWnrLtNHb7D0QrixFepuqKFpe4BkaSuhxxhiEUYDFjrWnbz9p5afc3MZSLtpy1gwkUUmvLFRVx9Lb7wpdGeCFVgx7pnkxJP327fnQEa4J5qZGSaBWWWcBwaJWJL0NB0i691KkjWxJNpnA7LSn6NaCMbx9nKLkRFL78iHRgFZnmASAkhiGf3DkdFffSYPMYGF2Nc55qFa70yw5reBYEvmKY61gDHJY4C38HqEnaQetcUNqxprvKy8DpZKAYV0fayeGvx1DdPeVRJZGfNQUGBQJXzEgBCuy2d1aCxp400ffUemRCJ6EQV97ZnHhZmPj2fAKBSa1x6J995ABj4FFF7ZL7Ay28v6zjrRjamThHP6ErnznF2Nqg0eWMz81MY5dktXw2cALbHbdX2md85WdJBHhFxbrCwfujgPeXkWZDaWh7rf1b0kQB3Fg5qyuyBFpEDmgwhBuqVJUS65yPXJnTyKGwfMNwDjReLSEmwqV7J19cDkkgQk8QhFyC7AmmCFid9g4g25umWj7KvbEJgACPnhgEcDbLuEuu7qDnENvZqKeU682bLjCLXVjbm53qSentHMkCaTw8YyESnfTLvwWtYXkgv9bNULZTwDPUmqUHCHQKbZtFnKYH4KnJT1BaDHa4LYBaqbywt8FTXgDA6qEhqHDABuV0WQnwhPvWeg1nfWH102DAxfY027mmP90ZZqw7PYMLp8EBwpANdm1RrwiKLLNpHrfZjxw0FZtm3cxACdFTJ3vefbhVEtbfqAjmgM6WSU9m2LbhMTBFYvCWCZ82trUwC4wCnzyCVRBcdJ3Sq7kTpWRFNubPjWCUjuX4br0aYvMu6Tq6h4nYrpyjW1EeADeyLDneneUZKZnBeqh73FThrhgzykEmgVL21exwb9jN2qfCKfx53VU5vyHDnnLeibrW4WxG3XpkZxLERRwJaS5xQ25NmANAyghERvUbrNCm86YJ7hbwfLvuDmJLhxP2ekRvycbjULtN6zfL2gJZVw1fKJhz7i5vAQZU8RHTHuT0g5SZc9zQFfQMRztxv69iW68D2f35c83NCYgQ2eFSH23zmKDBLTCVuBqG1NhxwT0mA7p3L6vQr96tYDqyBp2a4i0UjtiRgBvUM5LS2vLvTdz22AZX4mgEe3QHE8DVNDCGz2iKiKQuN5Drfvj8fQEWQ8V3kvEtwrXwz4i35H1WvnqRHW7W7cd3q9w0a9hXt6xyPbqDGcefmuthApQ1RWWevrFffvFzSLH5cp8Gu4fpyhkBM975ihx1xwSFtZTi98hDdFbP3ZzP67aHVP7W9pguHfqEKztv0uQn9ui9buNF905nGzSXXWx9yrtyEg7eSUjrgwWCYwwPQcgMLBS1mViY0k2L8UtnzPJ2fL41qfDSvRcuzQWBz1KzYEA1WwNUMzgg1K5VrfDQzepir9RbJbUE7==</X509Certificate>
      </X509Data>
      <KeyValue>
        <RSAKeyValue>
       <Modulus>Fzbsmd8Qt0R9Ko4O6UVvn3EnrDaTGW1g0udvbQi7Ozuk17kC8T6oMbBb95AYwtONxJ4j5v6Bbaxwslpxu2NpNVH6O8I5S6gRLK1xofa4AnHehCEmUaetjmVOTBZuR5vTc8W3WdP6ZSkW2wiYxBWT5bGogQdYlqNRp5T9NhPlZfchKpYmamLdHzY7LbyRVhNKKYovmvxXKOD1eIitINp7ng5tt6sOlWKhOExQ3G8pJq9A8QhNEhSLeEWUjY0K8lkRQAZ0PeVnhxGr2GcH1f6MDRrJucC3OTaNXftOSzX5umfEwURITw98yHhs8EMg6ND4YRo6C3NBz7qYqYnViJz4skLobbnq6I6pIL4xg2yp37bWmbH4Qqorh6lnZmmEeXcNMMEVcJ9Z9Qab5zIfAvkJWw8vb8yUhDQVq9EKJL3G4dDbF5IvbdwdBEzUvKJ8Bp2gEJMSI7FjjvUoXxqJhz4pAfTbozprOw1YFpZc==</Modulus>
          <Exponent>AQAB</Exponent>
        </RSAKeyValue>
      </KeyValue>
    </KeyInfo>
  </Signature>
</claim>

不要对长相怪异的角色感到惊讶。为了保护隐私,我用随机字符替换了它们。

现在这是

unsigned XML Claims File
的签名方式:

  1. 稍微检查一下测试方法。它访问
    XMLSign.cs
    类中的方法。所以我执行
    Sign_XML_With_Certificate()
    测试方法。
  2. 然后它访问 Windows 证书存储并获取
    Smart Card
    的证书。
  3. 现在的问题是 PIN 提示仅与
    SafeNet eToken 5110 Series
    Smart Card
    一起出现。接下来,PIN 提示将打开,您可以在下一步中将其视为图像。
  4. PIN Prompt for SafeNet eToken 5110 Series
  5. 如果 PIN 正确,则签名的 XML 声明文件将保存在
    signedClaimsLocation
    测试方法内的
    Sign_XML_With_Certificate()
    字符串变量中声明的路径中。当您在这篇文章中进一步滚动时,您可以查看签名的索赔文件的样子。
  6. 您可以在这篇文章中进一步查看签名的 XML 声明文件的外观。

问题: 这不适用于

Yubikey from Yubico
。当我执行测试方法时,没有出现要求输入 Yubikey PIN 码的提示。然后发生异常,基本上就是说它当然无法获取私钥。

现在,当我们尝试将签名的 XML 声明文件发送到 API 时,我们收到一条错误消息,表明签名无法验证。

XMLSign.cs

namespace XMLSignModule
{
    public class XMLSign
    {
        public static void SignXmlFile(string fileName, string signedFileName, string certificateSubject)
        {
            // Load the XML file.
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.PreserveWhitespace = true;
            xmlDoc.Load(fileName);

            // Load the certificate.
            X509Certificate2 cert = GetCertificateBySubject(certificateSubject);

            // Sign the XML document.
            SignedXml signedXml = new SignedXml(xmlDoc);
            signedXml.SigningKey = cert.PrivateKey;
            Reference reference = new Reference();
            reference.Uri = "";
            signedXml.AddReference(reference);
            signedXml.KeyInfo = new KeyInfo();
            signedXml.KeyInfo.AddClause(new KeyInfoX509Data(cert));

            // Compute the signature and add it to the XML document.
            signedXml.ComputeSignature();
            xmlDoc.DocumentElement?.AppendChild(xmlDoc.ImportNode(signedXml.GetXml(), true));

            // Save the signed XML document.
            xmlDoc.Save(signedFileName + "_" + DateTime.Now.ToString("yyyyMMddHHmmssfff") + ".xml");
        }

        private static X509Certificate2 GetCertificateBySubject(string certificateSubject)
        {
            X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
            store.Open(OpenFlags.ReadOnly);

            X509Certificate2Collection certCollection = store.Certificates;
            X509Certificate2Enumerator enumerator = certCollection.GetEnumerator();

            while (enumerator.MoveNext())
            {
                X509Certificate2 cert = enumerator.Current;

                if (cert != null && cert.Subject == certificateSubject)
                {
                    return cert;
                }
            }
            throw new Exception("The certificate was not found");
        }
    }
}

测试方法

[TestMethod()]
public void Sign_XML_With_Certificate()
{
    const string claimsLocation = @"C:\DEV\TestXML\unsigned_claims.xml";
    const string signedClaimsLocation = @"C:\DEV\TestXML\signed_claims";
    const string certificateSubject = "CN=Max Mustermann, G=Max, SN=Mustermann, O=Paper Company, OID.1.1.1.11=CHE-111.111.111, C=CH";
            
    XMLSign.SignXmlFile(claimsLocation, signedClaimsLocation, certificateSubject);
}

由于 API 目前仅支持

YubiKey from Yubico
,我的主要问题是如何使用 YubiKey 成功签署
XML File
?使用两种智能卡的混合解决方案都可以,但我只想使用 YubiKey。 YubiKey 支持生成自签名证书以及 CSR 以向证书颁发机构签名。我还可以从
SafeNet eToken 5110 Series
导出证书并将其导入到
YubiKey
。 YubiKey Manager 允许生成用于签名的 PIV 证书。

因此,快速修复方法是将证书从

SafeNet eToken 5110 Series
导入到
YubiKey from Yubico
并使用 API 上的 YubiKey 注册该证书。签署文件将使用
SafeNet eToken 5110 Series
完成。然而,我们希望有一个只需要
YubiKey from Yubico
的解决方案。

主要问题是我无法使用

YubiKey from Yubico
签署 XML 声明文件。 PIN 提示不出现,我无法访问私钥。

还有一个 Yubico .NET SDK 我尝试使用它,但我没有走得太远。

有谁知道我如何使用

YubiKey from Yubico
签署未签名的 XML 声明文件。理想情况下,就像我为
SafeNet eToken 5110 Series
智能钥匙描述的那样。

c# certificate x509certificate smartcard yubikey
1个回答
0
投票

解决方案

我可以找到一个解决方案,您必须在代码中输入 PIN 作为参数,并且它使用出现的第一个 Yubikey。但它工作得很好。如果有人有更好的想法或想要优化代码,请随意这样做。

这是我使用 Yubikey by Yubico 进行签名的方法:

代码:

public XmlDocument SignYubikeyClaimsDocument(XmlDocument claimsDocument, X509Certificate2 cert, string pin)
    {
        Pkcs11InteropFactories factories = new Pkcs11InteropFactories();
        using (IPkcs11Library pkcs11Library = factories.Pkcs11LibraryFactory.LoadPkcs11Library(factories, @"C:\Program Files (x86)\OpenSC Project\OpenSC\pkcs11\opensc-pkcs11.dll", AppType.SingleThreaded))
        {
            // Find the YubiKey slot
            ISlot slot = pkcs11Library.GetSlotList(SlotsType.WithTokenPresent).FirstOrDefault();
            
            if (slot == null)
            {
                throw new Exception("YubiKey not found");
            }

            // Open a session with the YubiKey from Yubico
            using (ISession session = slot.OpenSession(SessionType.ReadOnly))
            {
                // Login (if required)
                session.Login(CKU.CKU_USER, pin);

                // Find the private key and perform signing
                // Define search template for private key
                List<IObjectAttribute> searchTemplate = new List<IObjectAttribute>
                {
                    factories.ObjectAttributeFactory.Create(CKA.CKA_CLASS, CKO.CKO_PRIVATE_KEY),
                    factories.ObjectAttributeFactory.Create(CKA.CKA_KEY_TYPE, CKK.CKK_RSA),
                    // Add other attributes to match the specific key if necessary
                };

                // Find the private key
                List<IObjectHandle> foundObjects = session.FindAllObjects(searchTemplate);
                if (foundObjects.Count == 0)
                {
                    throw new Exception("Private key not found");
                }
                
                IObjectHandle privateKeyHandle = foundObjects[0];
                YubiKeyRSA yubiKeyRsa = new YubiKeyRSA(session, privateKeyHandle, factories);
                SignedXml signedXml = new SignedXml(claimsDocument);
                signedXml.SigningKey = yubiKeyRsa;

                // Creating the SignedInfo Element
                signedXml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigCanonicalizationUrl;

                Reference reference = new Reference("");
                reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());

                signedXml.AddReference(reference);
                signedXml.KeyInfo = new KeyInfo();
                signedXml.KeyInfo.AddClause(new KeyInfoX509Data(cert));

                // Compute the Signature and add it to the Document
                signedXml.ComputeSignature();
                XmlElement xmlDigitalSignature = signedXml.GetXml();
                claimsDocument.DocumentElement?.AppendChild(
                    claimsDocument.ImportNode(xmlDigitalSignature, true));


                // Logout and close the session
                session.Logout();
            }
        }

        return claimsDocument; // Modify to include the signature
    }

如果对此代码或 Yubikey 签名有任何疑问,请随时与我联系。

© www.soinside.com 2019 - 2024. All rights reserved.