I've been give a private key, public key and a certificate to try and generate a signature for an SSO application, I've been struggling with this for a while now and I've think I've finally managed to get some code close to working as needed. I made a post a while back here: iDP connecting to SP SAML / SSO which has helped me get in the right direction.
However I am still unsure on how I meant to be signing this signature, all the code I seem to come by says I need to use the .Net class X509Certificate which usually tried to load in another file, however the certificate is in the assertion file itself.
<ds:X509Data>
<ds:X509Certificate>X509Certificate Goes Here</ds:X509Certificate>
</ds:X509Data>
I have this method here:
private SignedXml SignSignature(XmlDocument assertionDoc, string uri, string digest)
{
CspParameters cspParams = new CspParameters();
cspParams.KeyContainerName = "XML_DSIG_RSA_KEY";
RSACryptoServiceProvider rsaKey = new RSACryptoServiceProvider(cspParams);
SignedXml signedXml = new SignedXml(assertionDoc);
Reference reference = new Reference();
signedXml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl;
signedXml.SignedInfo.SignatureMethod = SignedXml.XmlDsigRSASHA1Url;
reference.Uri = uri;
reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
reference.AddTransform(new XmlDsigExcC14NTransform());
reference.DigestMethod = SignedXml.XmlDsigSHA1Url;
reference.DigestValue = Encoding.ASCII.GetBytes(digest);
signedXml.AddReference(reference);
signedXml.SigningKey = rsaKey;
HMACSHA256 key = new HMACSHA256(Encoding.ASCII.GetBytes(PrivateKey));
signedXml.ComputeSignature(key);
return signedXml;
}
Which is what I am using to sign SignatureValue of the document, however I am only making use of the private key in the SHA256 class and not the certificate, I'm not even sure if I am using the private key correctly, overall I think I am making this more complicated than it needs to be and hopefully someone from here can assist me.
When you sign an assertion, you only need to use the private key of a certificate which is the case of your code.
However, usually the public key of the certificate is inserted as keyinfo into the signature to notify a receiver. This ends up as the ds:X509Data section you mentioned above. In order to do that, you need to add some more lines of code into the method above. You can find sample code at: https://github.com/Safewhere/CHTestSigningService/blob/86a66950d1ffa5208b8bf80d03868a073ba29f12/Kombit.Samples.CHTestSigningService/Code/TokenSigningService.cs#L344 (notice line 361, 362, and 368).
Related
I'm trying to check the certificate with its revocation list (crl-file). In BouncyCustle library there is a method x509Crl.IsRevoked(), that should be used for this. The point is that it gets x509Certificate object as a parameter, but I can't understand how to create this x509Certificate object.
I used DotNetUtilities.FromX509Certificate() for converting from System.Security.Cryptography.X509Certificates.x509Certificate2 object to Org.BouncyCastle.X509.X509Certificate object, but I faced the problem - method IsRevoked() always returns true - for all crl's I tested.
Question: how to create Org.BouncyCastle.X509.X509Certificate object directly from binary without converting from System.Security.Cryptography.X509Certificates.x509Certificate2?
My code for checking certificate with it's crl-file:
static public void RevocationChecker(string certPath, string crlPath)
{
X509Certificate2 cert = new X509Certificate2();
cert.Import(File.ReadAllBytes(certPath));
Org.BouncyCastle.X509.X509Certificate bouncyCert = DotNetUtilities.FromX509Certificate(cert);
X509CrlParser crlParser = new X509CrlParser();
X509Crl crl = crlParser.ReadCrl(File.ReadAllBytes(crlPath));
bool rezult = crl.IsRevoked(bouncyCert);
Console.WriteLine(rezult);
}
Give this a shot:
System.Security.Cryptography.X509Certificates.X509Certificate cert = new System.Security
.Cryptography.X509Certificates.X509Certificate(File.ReadAllBytes(certPath));`
Org.BouncyCastle.X509.X509Certificate bouncyCert = new Org.BouncyCastle.X509
.X509CertificateParser().ReadCertificate(cert.GetRawCertData());
I need to convert a EC private key generated by BouncyCastle to a CngKey in C#. Ultimately, I'm trying to create a PKCS12 that can be imported into the Windows Key Store and am following the information and code example found here.
The EC key pair is generated as follows:
var ecKeyPairGenerator = new ECKeyPairGenerator("ECDSA");
ECKeyGenerationParameters ecKeyGenParams = new ECKeyGenerationParameters(SecObjectIdentifiers.SecP384r1, new SecureRandom());
AsymmetricCipherKeyPair pair = ecKeyPairGenerator.GenerateKeyPair();
To create a CngKey:
PrivateKeyInfo privKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(pair.Private);
CngKey cngPrivKey = CngKey.Import(privKeyStruct.GetDerEncoded(), CngKeyBlobFormat.Pkcs8PrivateBlob);
Searching on the web, the above should work, e.g., see here. Instead, I'm getting an Unknown error exception
(CryptographicException) at
System.Security.Cryptography.NCryptNative.ImportKey(). If I pass
CngKeyBlobFormat.EccPrivateBlob to CngKey.Import(), I get an
invalid data exception.
As a new newbie to both .NET, CNG, and Cryto, I feel I'm overlooking something. Any ideas would be appreciated.
Thanks!
I am trying to digitally sign a XML document using SHA256.
I am trying to use Security.Cryptography.dll for this.
Here is my code -
CryptoConfig.AddAlgorithm(typeof(RSAPKCS1SHA256SignatureDescription),"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");
X509Certificate2 cert = new X509Certificate2(#"location of pks file", "password");
XmlDocument doc = new XmlDocument();
doc.PreserveWhitespace = true;
doc.Load(#"input.xml");
SignedXml signedXml = new SignedXml(doc);
signedXml.SigningKey = cert.PrivateKey;
signedXml.SignedInfo.SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
//
// Add a signing reference, the uri is empty and so the whole document
// is signed.
Reference reference = new Reference();
reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
reference.AddTransform(new XmlDsigExcC14NTransform());
reference.Uri = "";
signedXml.AddReference(reference);
//
// Add the certificate as key info, because of this the certificate
// with the public key will be added in the signature part.
KeyInfo keyInfo = new KeyInfo();
keyInfo.AddClause(new KeyInfoX509Data(cert));
signedXml.KeyInfo = keyInfo;
// Generate the signature.
signedXml.ComputeSignature();
But i am getting "Invalid algorithm specified." error at signedXml.ComputeSignature();. Can anyone tell me what I am doing wrong?
X509Certificate2 loads the private key from the pfx file into the Microsoft Enhanced Cryptographic Provider v1.0 (provider type 1 a.k.a. PROV_RSA_FULL) which doesn't support SHA-256.
The CNG-based cryptographic providers (introduced in Vista and Server 2008) support more algorithms than the CryptoAPI-based providers, but the .NET code still seems to be working with CryptoAPI-based classes like RSACryptoServiceProvider rather than RSACng so we have to work around these limitations.
However, another CryptoAPI provider, Microsoft Enhanced RSA and AES Cryptographic Provider (provider type 24 a.k.a. PROV_RSA_AES) does support SHA-256. So if we get the private key into this provider, we can sign with it.
First, you'll have to adjust your X509Certificate2 constructor to enable the key to be exported out of the provider that X509Certificate2 puts it into by adding the X509KeyStorageFlags.Exportable flag:
X509Certificate2 cert = new X509Certificate2(
#"location of pks file", "password",
X509KeyStorageFlags.Exportable);
And export the private key:
var exportedKeyMaterial = cert.PrivateKey.ToXmlString(
/* includePrivateParameters = */ true);
Then create a new RSACryptoServiceProvider instance for a provider that supports SHA-256:
var key = new RSACryptoServiceProvider(
new CspParameters(24 /* PROV_RSA_AES */));
key.PersistKeyInCsp = false;
And import the private key into it:
key.FromXmlString(exportedKeyMaterial);
When you've created your SignedXml instance, tell it to use key rather than cert.PrivateKey:
signedXml.SigningKey = key;
And it will now work.
Here are the list of provider types and their codes on MSDN.
Here's the full adjusted code for your example:
CryptoConfig.AddAlgorithm(typeof(RSAPKCS1SHA256SignatureDescription), "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");
X509Certificate2 cert = new X509Certificate2(#"location of pks file", "password", X509KeyStorageFlags.Exportable);
// Export private key from cert.PrivateKey and import into a PROV_RSA_AES provider:
var exportedKeyMaterial = cert.PrivateKey.ToXmlString( /* includePrivateParameters = */ true);
var key = new RSACryptoServiceProvider(new CspParameters(24 /* PROV_RSA_AES */));
key.PersistKeyInCsp = false;
key.FromXmlString(exportedKeyMaterial);
XmlDocument doc = new XmlDocument();
doc.PreserveWhitespace = true;
doc.Load(#"input.xml");
SignedXml signedXml = new SignedXml(doc);
signedXml.SigningKey = key;
signedXml.SignedInfo.SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
//
// Add a signing reference, the uri is empty and so the whole document
// is signed.
Reference reference = new Reference();
reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
reference.AddTransform(new XmlDsigExcC14NTransform());
reference.Uri = "";
signedXml.AddReference(reference);
//
// Add the certificate as key info, because of this the certificate
// with the public key will be added in the signature part.
KeyInfo keyInfo = new KeyInfo();
keyInfo.AddClause(new KeyInfoX509Data(cert));
signedXml.KeyInfo = keyInfo;
// Generate the signature.
signedXml.ComputeSignature();
Exporting and re-importing has already been given as an answer, but there are a couple other options that you should be aware of.
1. Use GetRSAPrivateKey and .NET 4.6.2 (currently in preview)
The GetRSAPrivateKey (extension) method returns an RSA instance of "the best available type" for the key and platform (as opposed to the PrivateKey property which "everyone knows" returns RSACryptoServiceProvider).
In 99.99(etc)% of all RSA private keys the returned object from this method is capable of doing SHA-2 signature generation.
While that method was added in .NET 4.6(.0) the requirement of 4.6.2 exists in this case because the RSA instance returned from GetRSAPrivateKey didn't work with SignedXml. That has since been fixed (162556).
2. Re-open the key without export
I, personally, don't like this approach because it uses the (now-legacy) PrivateKey property and RSACryptoServiceProvider class. But, it has the advantage of working on all versions of .NET Framework (though not .NET Core on non-Windows systems, since RSACryptoServiceProvider is Windows-only).
private static RSACryptoServiceProvider UpgradeCsp(RSACryptoServiceProvider currentKey)
{
const int PROV_RSA_AES = 24;
CspKeyContainerInfo info = currentKey.CspKeyContainerInfo;
// WARNING: 3rd party providers and smart card providers may not handle this upgrade.
// You may wish to test that the info.ProviderName value is a known-convertible value.
CspParameters cspParameters = new CspParameters(PROV_RSA_AES)
{
KeyContainerName = info.KeyContainerName,
KeyNumber = (int)info.KeyNumber,
Flags = CspProviderFlags.UseExistingKey,
};
if (info.MachineKeyStore)
{
cspParameters.Flags |= CspProviderFlags.UseMachineKeyStore;
}
if (info.ProviderType == PROV_RSA_AES)
{
// Already a PROV_RSA_AES, copy the ProviderName in case it's 3rd party
cspParameters.ProviderName = info.ProviderName;
}
return new RSACryptoServiceProvider(cspParameters);
}
If you already have cert.PrivateKey cast as an RSACryptoServiceProvider you can send it through UpgradeCsp. Since this is opening an existing key there'll be no extra material written to disk, it uses the same permissions as the existing key, and it does not require you to do an export.
But (BEWARE!) do NOT set PersistKeyInCsp=false, because that will erase the original key when the clone is closed.
If you run into this issue after upgrading to .Net 4.7.1 or above:
.Net 4.7 and below:
SignedXml signedXml = new SignedXml(doc);
signedXml.SigningKey = cert.PrivateKey;
.Net 4.7.1 and above:
SignedXml signedXml = new SignedXml(doc);
signedXml.SigningKey = cert.GetRSAPrivateKey();
Credits to Vladimir Kocjancic
In dotnet core I had this:
var xml = new SignedXml(request) {SigningKey = privateKey};
xml.SignedInfo.CanonicalizationMethod =
SignedXml.XmlDsigExcC14NTransformUrl;
xml.SignedInfo.SignatureMethod = SignedXml.XmlDsigSHA256Url;
xml.KeyInfo = keyInfo;
xml.AddReference(reference);
xml.ComputeSignature();
which did not work. Instead i used this
var xml = new SignedXml(request) {SigningKey = privateKey};
xml.SignedInfo.CanonicalizationMethod =
SignedXml.XmlDsigExcC14NTransformUrl;
xml.SignedInfo.SignatureMethod = SignedXml.XmlDsigRSASHA256Url;
xml.KeyInfo = keyInfo;
xml.AddReference(reference);
xml.ComputeSignature();
changed signature method => xml.SignedInfo.SignatureMethod = SignedXml.XmlDsigRSASHA256Url
I'm new to the .NET CryptoProvider space, and am a little concerned by what I have seen regarding the repeated creation of the same key by the RSACryptoProvider.
I am using a container because I am storing the key off to file on the server, like so (I export the CspBlob subsequent to this creation and reimport it later)...
_cp = new CspParameters { KeyContainerName = ContainerName };
In this case the ContainerName has a hardcoded value that I reference the container by.
What's bothering me is that when I create the RSACryptoProvider, and by exentsion the key pair, the generated key values are always the same!
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(RSAKeySize, _cp);
If I change the name of the container, the key changes. There must be SOME other source of randomness than the container name when you create an RSACryptoProvider, right? Otherwise that makes the name of the container a password, which is not my intention.
It's the name of a container, not of a generator.
If you want different keys each time, just create a new CryptoServiceProvider w/o referencing a container( == stored key-pair).
Following code will delete the key(if exist) related with the containername.
After you delete the key; you can create a new one with the same conatiner name and you will get new random key.
CspParameters cspParams = new CspParameters();
// Specify the container name using the passed variable.
cspParams.KeyContainerName = ContainerName;
//Create a new instance of RSACryptoServiceProvider.
//Pass the CspParameters class to use the
//key in the container.
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(cspParams);
//Delete the key entry in the container.
rsa.PersistKeyInCsp = false;
//Call Clear to release resources and delete the key from the container.
rsa.Clear();
I'm trying to asymetrically encrypt a message of arbitrary length with bouncycastle. (1.4+ with C#)
This is the code I have right now. It is supposed to (but doesn't) generate a CMS message where the data itself is encrypted with AES256 with a random key and the key is encrypted with the public key from keyPair.
keyPair is an RSA-Key (RsaKeyParameters)
public static byte[] Encrypt(byte[] input, AsymmetricCipherKeyPair keyPair)
{
CmsEnvelopedDataGenerator generator = new CmsEnvelopedDataGenerator();
// those two lines are certainly wrong.
// I have no idea what the subKeyID parameter does
byte[] subKeyId = new byte[] {};
generator.AddKeyTransRecipient(keyPair.Public, subKeyId);
CmsProcessableByteArray cmsByteArray = new CmsProcessableByteArray(input);
CmsEnvelopedData envelopeData =
generator.Generate(cmsByteArray, CmsEnvelopedDataGenerator.Aes256Cbc);
return envelopeData.GetEncoded();
}
What is the subKeyId parameter in the Encrypt method for and what value does it need to have?
aaronls is being a little unfair to the author of "Beginning cryptography with Java", who after all wrote all the unit tests himself in the first place...
As other commenters have pointed out, CMS works with certificates, you can't just pass a public key; it must be possible to refer to the key either by "SubjectKeyIdentifier" or by "IssuerAndSerialNumber". The two alternatives of AddKeyTransRecipient allow this. If these terms don't mean anything to you, you probably need to do some background reading on X.509.
Look at the function TryKekAlgorithm in the EnvelopedDataTest.cs file of the BouncyCastle source. Instead of doing AddKeyTransRecipient, they are doing AddKekRecipient.
public static byte[] Encrypt(byte[] input, AsymmetricCipherKeyPair keyPair)
{
CmsEnvelopedDataGenerator generator = new CmsEnvelopedDataGenerator();
DerObjectIdentifier algOid = //initialize
//Still trying to figure out kekId here.
byte[] kekId = new byte[] { 1, 2, 3, 4, 5 };
string keyAlgorithm = ParameterUtilities.GetCanonicalAlgorithmName("AES256");
generator.AddKekRecipient(keyAlgorithm, keyPair.Public, kekId);
CmsProcessableByteArray cmsByteArray = new CmsProcessableByteArray(input);
CmsEnvelopedData envelopeData =
generator.Generate(cmsByteArray, CmsEnvelopedDataGenerator.Aes256Cbc);
return envelopeData.GetEncoded();
}
Edit: I think the kekId is just a unique identifier used to reference the key. Just a way to "name" the key. So you can have a book of keys, and each one has an identifier. When you send an encrypted message, the unencrypted key identifier tells you which of the keys was used to encrypt the message.
Here is a pretty good explanation of key identifiers on page 140:
[http://books.google.com/books?id=Pgg-Es2j3UEC&pg=PA140&lpg=PA140&dq=understanding+key+identifiers+encrypt&source=bl&ots=nFg0BzM2ht&sig=Ux5sreXMKyuEEZu0uaxE7cXC1VI&hl=en&ei=JKKJStbHGJivtgffsNznDA&sa=X&oi=book_result&ct=result&resnum=6#v=onepage&q=&f=false][1]
And here is another book that is using BouncyCastleCrypto, but it looks like they did little more than rip off the unit test source code. They have explained it a little:
[http://books.google.com/books?id=WLLAD2FKH3IC&pg=PA343&lpg=PA343&dq=CmsEnvelopedDataGenerator+AddKekRecipient&source=bl&ots=O9HinJm3yB&sig=K5Z99DIVWW4-0abPIFR7x4lzBhU&hl=en&ei=g6aJSrjeDuHktgennNjnDA&sa=X&oi=book_result&ct=result&resnum=6#v=onepage&q=CmsEnvelopedDataGenerator%20AddKekRecipient&f=false][2]
To use AES, it is not enough to use a AsymmetricCipherKeyPair.
You should use a X509 certificate, where the public key is signed by an certificate authority (CA).
the subKeyId is an attribute of the certificate, the subject Key Identifier:
(X509Certificate) cert.getSubjectUniqueID()
To encrypt a message of artrary length, you should use AES only to exchange a symmetric Keypassword and use this key for symmetric encryption.