I am trying to create an X509Certificate2 object in C# from an XML file. The XML file is a SAML metadata file that we received from a vendor.
I am trying to extract the public key from these XML Elements:
<X509Data>
<X509Certificate>
MIIB7DCCAVmgAwIBAgIQPjHcBTL63bBLuJZ88RcrCjAJBgUrDgMCHQUAMBExDzANBgNVBAMT
BnJvbWVvazAgFw0xMDAzMTUwMjI1MjZaGA8yMTEwMDIxOTAyMjUyNlowETEPMA0GA1UEAxMG
cm9tZW9rMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDAu/sBh13A27rR7gJpZsI6zCee
TXNohQWlq2z6Zg8Oxzsy5JoVV
</X509Certificate>
</X509Data>
Is there a way in C# to extract either the .cer file or public key from the XML element?
Randall's answer is correct. But in SAML Token the certificate I believe will always be Base64 encoded.
So for posterity, the solution that worked for me was:
var document = new XmlDocument();
document.LoadXml(txtXml.Text);
var certificateStr = document.SelectSingleNode("X509Data/X509Certificate").InnerText;
byte[] data = Convert.FromBase64String(certificateStr);
var x509 = new X509Certificate2(data);
Console.WriteLine("Public Key Format: {0}", x509.PublicKey.EncodedKeyValue.Format(true));
This is a difficult question to answer without knowing how the X509Certificate is encoded, but assuming you have the encoding stuff, you can do something like the following:
var document = new XmlDocument();
document.LoadXml(txtXml.Text);
var cert = document.SelectSingleNode("X509Data/X509Certificate").InnerText;
/*...Decode text in cert here (may need to use Encoding, Base64, UrlEncode, etc) ending with 'data' being a byte array...*/
var x509 = new X509Certificate2(data);
Then you should be able to write the file to disk using standard File I/O logic.
Related
I'm having difficulty loading an X509Certificate2 from XML, using the FromXmlString method. The exception I'm getting is m_safeCertContext is an invalid handle.
System.Security.Cryptography.CryptographicException occurred
HResult=-2146233296
Message=m_safeCertContext is an invalid handle.
Source=System
StackTrace:
at System.Security.Cryptography.X509Certificates.X509Certificate2.get_HasPrivateKey()
at System.Security.Cryptography.X509Certificates.X509Certificate2.get_PrivateKey()
...
To create the XML, I'm loading the .pfx file and using ToXmlString;
var certificate = new X509Certificate2(
#"D:\public_privatekey.pfx",
(string)null,
X509KeyStorageFlags.Exportable
);
var exportedPrivate = certificate.PrivateKey.ToXmlString(true);
This generate XML which starts like this...
<RSAKeyValue><Modulus>y0iuejYHYajI...
To recreate the certificate, I'm using...
var certificate = new X509Certificate2();
certificate.PrivateKey.FromXmlString(xml);
Where xml is a string containing the XML content.
The exception is thrown on the FromXmlString call.
I'm new to using certificates, but my best guess is that the .pfx contains both public and private keys, and possibly some other important data, and that I need all of that in order to have a valid X509 certificate.
However I couldn't find ToXmlString and FromXmlString on the X509Certificate2 directly. How should I do this? Thanks for any advice.
An X.509 certificate is described in a structured binary format called ASN.1/DER encoding. ASN.1 is a language to describe the contents of the certificate and DER is the encoding of the contents that comply with that ASN.1 structure.
Encoding your in-memory certificate separately from the private key can be done using the Export method using the content type X509ContentType.Cert. You can also export the certificate and private key back into a "pfx" by specifying Pfx or Pkcs12. If you require XML then you can encode the byte array result using base 64. You can then store it into an XML CDATA element.
Usually a private key is also stored in a binary PKCS#8 container format, also defined using ASN.1 / DER. Microsoft has however chosen to store the key into a Microsoft-proprietary XML format by default.
I basically need to export a .pfx certificate as a Base64string, store it in a database and recover it later, converting from Base64string.
What I'm using at the moment is the X509Certificate2 class like the following:
To convert it and store in DB the cert64 string:
X509Certificate2 pfx = new X509Certificate2(#"C:\originalcert.pfx", "password", X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.UserKeySet);
string cert64 = Convert.ToBase64String(pfx.RawData);
And get it later from DB (I need to store it as a Base64string):
X509Certificate2 cert = new X509Certificate2();
cert.Import(Convert.FromBase64String(string64cert), "password", X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.UserKeySet);
File.WriteAllBytes(#"C:\copycert.pfx", cert.Export(X509ContentType.Pfx, "password"));
And it returns true when I compare C:\originalcert.pfx and C:\copycert.pfx using:
X509Certificate2.Equals
For the application I'm running that requires a certificate to work properly, I sometimes get an error with some different .pfx certificates provided to me that I use to work around importing/installing to the machine and exporting it via web browser, creat a new .pfx file and voilĂ .
Using the copycert.pfx file gives me the same error but when I try to install copycert.pfx through the file or import it using a web browser I get: "The import was successful" message, but can't find the installed certificate under the "Personal" tab as I would if I installed the original originalcert.pfx.
Also, it is important that I export from a .pfx file and import it later to a .pfx file.
What am I doing wrong/is missing in the code export/import?
Your solution doesn't ever work in a manner you describe. Here is why:
string cert64 = Convert.ToBase64String(pfx.RawData);
this line converts only public part of the certificate. No private key information is ever stored in RawData property. This means that you can't restore original PFX from this string. What you really should do is to read contents of the file and convert it to Base64 string without touching X509Certificate2 class. Replace first two lines of posted code with these two:
Byte[] rawCert = File.ReadAllBytes(#"C:\originalcert.pfx");
String cert64 = Convert.ToBase64String(bytes);
PFX certificates support only pure binary encoding (i.e. PFX cannot be stored in PEM format), so just read raw bytes and convert them.
var cert = new X509Certificate2();
cert.Import(Convert.FromBase64String(string64cert), "password",
X509KeyStorageFlags.Exportable |
X509KeyStorageFlags.PersistKeySet |
X509KeyStorageFlags.UserKeySet);
this command only imports certificate to an X509Certificate2 object and installs private key to CSP specified in PFX (or to default CSP if no provider information is stored in PFX). In order to install it to personal store, you need to do that:
var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadWrite);
store.Certificates.Add(cert);
store.Close();
starting with .NET 4.6, X509Store implements IDisposable, so you should use using clause to dispose the object:
using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser)) {
store.Open(OpenFlags.ReadWrite);
store.Add(cert);
}
Note that the code above is necessary only to install certificate to certificate store. In order to restore it from database to PFX file, just save binary data to a file:
Byte[] rawCert = Convert.FromBase64String(string64cert);
File.WriteAllBytes(#"C:\copycert.pfx");
Summary
Just to summarize all written. To convert PFX to Base64 string:
Byte[] rawCert = File.ReadAllBytes(#"C:\originalcert.pfx");
String cert64 = Convert.ToBase64String(bytes);
to restore PFX from Base64 string and save to a file:
Byte[] rawCert = Convert.FromBase64String(string64cert);
File.WriteAllBytes(#"C:\copycert.pfx");
We use C# code we build X509Certificate2 with .p12 file, in the constructor we insert the path to certificate, certificate's password. We also marked it as Exportable as shown below:
X509Certificate2 x509Certificate2 = new X509Certificate2
("...\\MyCerificate.p12", "P#ssw0rd", X509KeyStorageFlags.Exportable);
we get the private key as AsymmetricAlgorithm format by the following:
x509Certificate2.PrivateKey
Now, we want to get the private key from the certificate as Base64 format - but we don't have any idea how to do it, and its so important for us.
The important question is why base64 ?
If this is for your own application then you can keep the private key as an XML string (much easier :-).
string xml = x509Certificate2.PrivateKey.ToXmlString (true);
If you want base64 (again just for your application) you can export the key (RSAParameters) then concat every byte[] and turn the merged output to a base64 string.
But if you want to interop with other applications that requires a base64 private key then you need to know the format (inside the base64 string). E.g. in many case private keys are PEM encoded (which is base64 with a special header/footer, see an example for X509Certificate).
If that what's you're looking for then you'll need to encode the private key within a PKCS#8 structure first, then turn in into base64 and add the header/footer. You can find some helpful code to do so inside Mono.Security.dll (MIT.X11 licensed code from the Mono project).
You can simply use the PrivateKey property of X509Certificate2.
The actual returned private key implementation depends on the algorithm used in the certificate - usually this is RSA:
rsaObj = (RSACryptoServiceProvider)myCertificate.PrivateKey;
Afterwards you should be able to get the RSA key information from it's ExportParameters property.
You can do that with OpenSSL Library for .NET:
using DidiSoft.OpenSsl;
...
X509Certificate2 x509Certificate2 = new X509Certificate2
("...\\MyCerificate.p12", "P#ssw0rd", X509KeyStorageFlags.Exportable);
PrivateKey privKey = PrivateKey.Load(x509Certificate2.PrivateKey);
bool withNewLines = true;
string base64PrivateKey = privKey.ToBase64String(withNewLines);
If your only problem is to get the private key Base64 encoded, you can simply do like this:
var privateKey = x509Certificate2.PrivateKey;
var encoding = new System.Text.ASCIIEncoding();
var base64String = Convert.ToBase64String(encoding.GetBytes(privateKey.ToString()));
I want to create 2 really simple dlls:
1) that will sign an xml document
2) that will check that the xml document hasnt been modified.
I tried using the RSACryptoServiceProvider and a key container. But when I move to a different machine this does not work as the key is being stored in the machine.
I want to store the key in the dlls im creating (I know this is not reccomended) but I just cannot work out how to write some code to simply sign an xml document and then verify that it hasn't been changed.
So do I need to use symmetric key to do what I want is this possible?
Pete
You already mention the problems with storing the private key in the dll, so I won't repeat that.
Do this:
On your own machine run this code:
var key = new RSACryptoServiceProvider(2048);
string publicKey = key.ToXmlString(false);
string privateKey = key.ToXmlString(true);
Console.WriteLine(publicKey);
Console.WriteLine(privateKey);
this outputs two (long) lines. Copy those into your code:
Sign:
var privateKey = new RSACryptoServiceProvider();
privateKey.FromXmlString(/* insert the private-key XML string here */ );
privateKey.SignData(/*...*/);
Verify:
var publicKey = new RSACryptoServiceProvider();
publicKey.FromXmlString(/* insert the public-key XML string here */ );
publicKey.VerifyData(/*...*/);
If it is just about to verify that your xml document hasn't been modified a simple MD5 checksum (or any other good hashing algorithm) would be easier to implement and is what you need. It would be also verifyable on different machines.
it's late, I'm tired, and probably being quite dense....
I have written an application that I need to secure so it will only run on machines that I generate a key for.
What I am doing for now is getting the BIOS serial number and generating a hash from that, I then am encrypting it using a XML RSA private key. I then sign the XML to ensure that it is not tampered with.
I am trying to package the public key to decrypt and verify the signature with, but every time I try to execute the code as a different user than the one that generated the signature I get a failure on the signature.
Most of my code is modified from sample code I have found since I am not as familiar with RSA encryption as I would like to be. Below is the code I was using and the code I thought I needed to use to get this working right...
Any feedback would be greatly appreciated as I am quite lost at this point
the original code I was working with was this, this code works fine as long as the user launching the program is the same one that signed the document originally...
CspParameters cspParams = new CspParameters();
cspParams.KeyContainerName = "XML_DSIG_RSA_KEY";
cspParams.Flags = CspProviderFlags.UseMachineKeyStore;
// Create a new RSA signing key and save it in the container.
RSACryptoServiceProvider rsaKey = new RSACryptoServiceProvider(cspParams)
{
PersistKeyInCsp = true,
};
This code is what I believe I should be doing but it's failing to verify the signature no matter what I do, regardless if it's the same user or a different one...
RSACryptoServiceProvider rsaKey = new RSACryptoServiceProvider();
//Load the private key from xml file
XmlDocument xmlPrivateKey = new XmlDocument();
xmlPrivateKey.Load("KeyPriv.xml");
rsaKey.FromXmlString(xmlPrivateKey.InnerXml);
I believe this to have something to do with the key container name (Being a real dumbass here please excuse me) I am quite certain that this is the line that is both causing it to work in the first case and preventing it from working in the second case....
cspParams.KeyContainerName = "XML_DSIG_RSA_KEY";
Is there a way for me to sign/encrypt the XML with a private key when the application license is generated and then drop the public key in the app directory and use that to verify/decrypt the code? I can drop the encryption part if I can get the signature part working right. I was using it as a backup to obfuscate the origin of the license code I am keying from.
Does any of this make sense?
Am I a total dunce?
Thanks for any help anyone can give me in this..
I used this method to sign xml documents using a private key stored in an xml file that I then embedded into the application .dll as a resource. I think you may be struggling with permissions accessing the keystore, and this would also create hassles transferring the code to other servers etc.
Here is the code to get the private key as an embedded resource and sign the document:
(Sign is the name of the class this method is located in, Licensing.Private.Private.xml is a combination of the default namespace + folder + filename of the resource)
public static void SignDocument(XmlDocument xmldoc)
{
//Get the XML content from the embedded XML privatekey.
Stream s = null;
string xmlkey = string.Empty;
try
{
s = typeof(Sign).Assembly.GetManifestResourceStream("Licensing.Private.Private.xml");
// Read-in the XML content.
StreamReader reader = new StreamReader(s);
xmlkey = reader.ReadToEnd();
reader.Close();
}
catch (Exception e)
{
throw new Exception("Error: could not import key:",e);
}
// Create an RSA crypto service provider from the embedded
// XML document resource (the private key).
RSACryptoServiceProvider csp = new RSACryptoServiceProvider();
csp.FromXmlString(xmlkey);
//Creating the XML signing object.
SignedXml sxml = new SignedXml(xmldoc);
sxml.SigningKey = csp;
//Set the canonicalization method for the document.
sxml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigCanonicalizationUrl; // No comments.
//Create an empty reference (not enveloped) for the XPath transformation.
Reference r = new Reference("");
//Create the XPath transform and add it to the reference list.
r.AddTransform(new XmlDsigEnvelopedSignatureTransform(false));
//Add the reference to the SignedXml object.
sxml.AddReference(r);
//Compute the signature.
sxml.ComputeSignature();
// Get the signature XML and add it to the document element.
XmlElement sig = sxml.GetXml();
xmldoc.DocumentElement.AppendChild(sig);
}
Use the following code the generate the private.xml and public.xml keys. Keep the private.xml file secure, obviously.
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
File.WriteAllText(#"C:\privateKey.xml", rsa.ToXmlString(true)); // Private Key
File.WriteAllText(#"C:\publicKey.xml", rsa.ToXmlString(false)); // Public Key
Guess, the problem is that different users don't have access to the key that is stored for the 1st user (Please note: I am not a cryptography expert).