I have two Smart Cards
:
- YubiKey 5C NFC from Yubico
- SafeNet eToken 5110 Series
I installed all necessary drivers and tools for both Smart Cards
.
Now the goal is to create a C# method
that signs an unsigned XML Claim File
with the private key
of those Smart Cards
. This signed XML Claims File
should then be send to an API
with a POST request
that verifies the signature by using the public key
.
We can upload the public key to the API
by using the Yubikey
with FIDO2
and then follow a procedure which then makes it possible to upload PIV
certificates to the API. However this API currently exclusively supports Yubikey from Yubico
and no other Smart Cards
.
The unsigned XML Claims File:
<?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>
The signed XML Claims File:
<?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>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</X509Certificate>
</X509Data>
<KeyValue>
<RSAKeyValue>
<Modulus>Fzbsmd8Qt0R9Ko4O6UVvn3EnrDaTGW1g0udvbQi7Ozuk17kC8T6oMbBb95AYwtONxJ4j5v6Bbaxwslpxu2NpNVH6O8I5S6gRLK1xofa4AnHehCEmUaetjmVOTBZuR5vTc8W3WdP6ZSkW2wiYxBWT5bGogQdYlqNRp5T9NhPlZfchKpYmamLdHzY7LbyRVhNKKYovmvxXKOD1eIitINp7ng5tt6sOlWKhOExQ3G8pJq9A8QhNEhSLeEWUjY0K8lkRQAZ0PeVnhxGr2GcH1f6MDRrJucC3OTaNXftOSzX5umfEwURITw98yHhs8EMg6ND4YRo6C3NBz7qYqYnViJz4skLobbnq6I6pIL4xg2yp37bWmbH4Qqorh6lnZmmEeXcNMMEVcJ9Z9Qab5zIfAvkJWw8vb8yUhDQVq9EKJL3G4dDbF5IvbdwdBEzUvKJ8Bp2gEJMSI7FjjvUoXxqJhz4pAfTbozprOw1YFpZc==</Modulus>
<Exponent>AQAB</Exponent>
</RSAKeyValue>
</KeyValue>
</KeyInfo>
</Signature>
</claim>
Don't be surprised about the weird looking characters. I replaced them with random characters for privacy.
Now here is how the unsigned XML Claims File
gets signed:
- Check the Test-Method a bit lower. It accesses the methods from the
XMLSign.cs
Class. So I execute theSign_XML_With_Certificate()
Test Method. - Then it accesses the Windows Certificate Store and gets the certificate of the
Smart Card
. - Now the problem is the PIN Prompt only appears with the
SafeNet eToken 5110 Series
Smart Card
. Next the PIN Prompt opens up which you can see as an image in the next step. - PIN Prompt for SafeNet eToken 5110 Series
- If the PIN is correct the signed XML Claims File gets saved in the path that was declared in the
signedClaimsLocation
string variable inside theSign_XML_With_Certificate()
Test Method. You can check out how a signed Claims File looks like when you scroll a bit further up in this post. - You can check out how a signed XML Claims File looks a bit further up in this post.
The Problem:
This doesn't work with the Yubikey from Yubico
. When I execute the Test Method no prompt appears that asks for the PIN of the Yubikey. Then an exception occurs which basically says that it couldn't get the private key of course.
Now when we try to send the signed XML Claims file to the API we get an error back that says that the signature could not be verified of course.
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");
}
}
}
Test Method
[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);
}
Since the API currently only supports the YubiKey from Yubico
my main question is how can I sign the XML File
successfully with YubiKey? A hybrid solution by using both Smart Cards would work, however I want to do it only with the YubiKey. The YubiKey supports generating self-signed certificates and also CSR to sign to a Certificate Authority. I can also export the certificate from SafeNet eToken 5110 Series
and import it to the YubiKey
. The YubiKey Manager allows to generate PIV certificates for signatures.
So a quick fix would be importing the certificate from the SafeNet eToken 5110 Series
to the YubiKey from Yubico
and register this certificate with the YubiKey on the API. And signing files would be done with the SafeNet eToken 5110 Series
. However we want to have a solution where we only need the YubiKey from Yubico
.
The main problem is that I can't sign the XML Claims File with the YubiKey from Yubico
. The PIN prompt just doesn't appear and I can't get access to the private key.
Also there is a Yubico .NET SDK which I tried to use, however I didn't come far.
Does anyone know how I can sign the unsigned XML Claims File with the YubiKey from Yubico
. Ideally on the same way like I described it for the SafeNet eToken 5110 Series
Smart Key.