Current state
I am working on web-based application which is used to manage PDF forms and allows users to fill in the form, process approval and sign the PDF.
Application uses Adobe LiveCycle to calculate document digest and runs external application (call signer application) for signing the hash. Then the hash is sent back to LiveCycle where it is embedded to PDF document.
Signer application receives precalculated message digest and generates PKCS#7 signature. Windows certificate store is used to find the signing certificate and private key. We have to support usage of USB eTokens where the private key is stored.
Currently we have fully functional signer application written in Java. It uses BouncyCastle library and SunMSCAPI cryptographic provider to access Windows certificate store. Following is the code snippet responsible for signing.
protected byte[] signCMS(byte[] hash, PrivateKey key, X509Certificate[] cert) throws CertStoreException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, IOException, CMSException, OperatorCreationException, CertificateException {
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
ASN1EncodableVector signedAttrs = new ASN1EncodableVector();
signedAttrs.add(new Attribute(CMSAttributes.messageDigest, new DERSet(new DEROctetString(hash))));
ContentSigner sha256Signer = new JcaContentSignerBuilder("SHA256withRSA").setProvider("SunMSCAPI").build(key);
gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(new BouncyCastleProvider()).build()).setSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator(new AttributeTable(signedAttrs))).build(sha256Signer, cert[0]));
gen.addCertificates(generatedCertStore(cert));
return gen.generate(new CMSAbsentContent()).toASN1Structure().getEncoded();
}
Above code uses SunMSCAPI cryptographic provider and fully supports usage of USB eTokens without need of exporting the private key.
Needed state
Customer does not agree with usage of Java on the client side. I'm working on C# variant of the signer application.
Problems
I don't know how to generate PKCS#7 signature from externally calculated message digest. I always looks like the message digest is hashed twice. Acrobat Reader then reports the document was modiffied after signature.
I tried using BouncyCastle for .NET and was able to process working signature when using BouncyCastle library for .NET, but it requires the private key to be exported from the Windows certificate store. This solution does not support usage of USB eTokens.
Current non-working C# variant
public static byte[] Sign(byte[] hash, X509Certificate2 cert2)
{
List<byte> encodedMessageDigestList = new List<byte>(hash.Length + 2);
encodedMessageDigestList.Add(4);
encodedMessageDigestList.Add(checked((byte)(hash.Length)));
encodedMessageDigestList.AddRange(hash);
byte[] encodedMessageDigest = encodedMessageDigestList.ToArray();
Pkcs9MessageDigest md = new Pkcs9MessageDigest();
Pkcs9AttributeObject pAttribute = new Pkcs9AttributeObject("1.2.840.113549.1.9.4", encodedMessageDigest);
md.CopyFrom(pAttribute);
SignedCms cms = new(new ContentInfo(md.Oid, md.RawData));
CmsSigner cmsSigner = new();
cmsSigner.Certificate = cert2;
cmsSigner.IncludeOption = X509IncludeOption.WholeChain;
cms.ComputeSignature(cmsSigner, false);
return cms.Encode();
}
We can provide a precomputed Pkcs9MessageDigest (with the digest hash already computed) into the SignedAttribues collection of the CmsSigner. This will override the default behavior of automatically generating one.
https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.pkcs.pkcs9messagedigest?view=dotnet-plat-ext-7.0
Related
I have a scenario in which I need to create data to be signed hash and send it to the signing server after which signer certificate details are received from the signing server which needs to be used in the signature appearance object that was created in the first step and create rehash data to be signed bytes and respond to signing server.
The resultant signature is invalid in this scenario.
Could someone help me out in this regard?
My code base is following:
Stream readerStream = new MemoryStream(documentBytes);
PdfReader pdfReader = new PdfReader(readerStream);
pdfSigner = new PdfSigner(pdfReader,outputStream,new StampingProperties());
pdfSigner.SetFieldName("Signature1");
signatureAppearance = pdfSigner.GetSignatureAppearance();
signatureAppearance.SetLayer2Text("test");
signatureAppearance.SetLocation("test");
signatureAppearance.SetContact("test");
signatureAppearance.SetPageNumber(1);
signatureAppearance.SetRenderingMode(PdfSignatureAppearance.RenderingMode.DESCRIPTION);
PreSigning external = new PreSigning(PdfName.Adobe_PPKLite,PdfName.ETSI_CAdES_DETACHED,"SHA256");
pdfSigner.SignExternalContainer(external,estimatedSize);
docHash = external.getHash();
Problem Statement:
After sending the above hash to the signing server I got the signer certificate details which need to be updated in signature appearance layer2 text and I need to compute the hash again which results in an invalid signature.
This is first time I try NACl.NET which are desribed here well.
Nuget from here
https://www.nuget.org/packages/NaCl.Net/
I take code usage from here
https://github.com/somdoron/nacl.net
I need to exactly do what this guy was trying to do
Sign a message with as small as possible digital signature in c#
He post a very nice answer. But without code.
From Github code I copied it the same for test. (Everything is OK for now)
var rng = RandomNumberGenerator.Create();
Curve25519XSalsa20Poly1305.KeyPair(out var aliceSecretKey, out var alicePublicKey);
Curve25519XSalsa20Poly1305.KeyPair(out var bobSecretKey, out var bobPublicKey);
Curve25519XSalsa20Poly1305 aliceBox = new Curve25519XSalsa20Poly1305(aliceSecretKey, bobPublicKey);
Curve25519XSalsa20Poly1305 bobBox = new Curve25519XSalsa20Poly1305(bobSecretKey, alicePublicKey);
// Generating random nonce
byte[] nonce = new byte[Curve25519XSalsa20Poly1305.NonceLength];
rng.GetBytes(nonce);
// Plaintext message
byte[] message = Encoding.UTF8.GetBytes("Hey Bob");
// Prepare the buffer for the ciphertext, must be message length and extra 16 bytes for the authentication tag
byte[] cipher = new byte[message.Length + Curve25519XSalsa20Poly1305.TagLength];
// Encrypting using alice box
aliceBox.Encrypt(cipher, message, nonce);
// Decrypting using bob box
byte[] plain = new byte[cipher.Length - Curve25519XSalsa20Poly1305.TagLength];
bool isVerified = bobBox.TryDecrypt(plain, cipher, nonce);
var originalmessage = Encoding.UTF8.GetString(plain);
As we all know the RSA, ECC algorithms given private key, public key.
The secure way is that public-key kept for verify signature in the Client application only
While private-key kept for create signature in the License Server only
Now the above library made me mad. It give following keys
aliceSecretKey, alicePublicKey and bobSecretKey, bobPublicKey
I need to give example above for License Server and a Client Application
Assume that Alice is a License Server. Bob is a Client application
So which keys should stored in Client application?
Is it bobSecretKey, alicePublicKey keys that should stored in client
application side?
Please accept my apologies,
I don't even know how this strange algorithm works!
I have iText7 functions which I am using,
right now, I am trying to encrypt my PDF file using a certificate in .pfx format with password.
The thing is, the function cannot read .pfx because it does not provide the password as shown below
using System.IO;
using iText.Kernel.Pdf;
using Org.BouncyCastle.X509;
namespace iText.Samples.Sandbox.Security
{
public class EncryptWithCertificate
{
public static readonly String DEST = "results/sandbox/security/encrypt_with_certificate.pdf";
public static readonly String SRC = "../../../resources/pdfs/hello.pdf";
public static readonly String PUBLIC = "../../../resources/encryption/test.cer";
public static void Main(String[] args)
{
FileInfo file = new FileInfo(DEST);
file.Directory.Create();
new EncryptWithCertificate().ManipulatePdf(DEST);
}
public X509Certificate GetPublicCertificate(String path)
{
using (FileStream stream = File.Open(path, FileMode.Open))
{
X509CertificateParser parser = new X509CertificateParser();
X509Certificate readCertificate = parser.ReadCertificate(stream);
return readCertificate;
}
}
protected void ManipulatePdf(String dest)
{
// The file created by this example can not be opened, unless
// you import the private key stored in test.p12 in your certificate store.
// The password for the p12 file is kspass.
X509Certificate cert = GetPublicCertificate(PUBLIC);
PdfDocument document = new PdfDocument(new PdfReader(SRC), new PdfWriter(dest,
new WriterProperties().SetPublicKeyEncryption(
new[] {cert},
new[] {EncryptionConstants.ALLOW_PRINTING},
EncryptionConstants.ENCRYPTION_AES_256)));
document.Close();
}
}
}
If i try to load a normal .cer file, it goes through normally for GetPublicCertificate. No issue there. But I am trying to encrypt it with .pfx file as adobe acrobat can only register Digital ID using .p12/.pkf format and the function does throws error.
The error
Org.BouncyCastle.Security.Certificates.CertificateException
HResult=0x80131500
Message=Failed to read certificate
Inner Exception 1:
ArgumentException: Unknown object in GetInstance: Org.BouncyCastle.Asn1.DerInteger
Parameter name: obj
I am hoping to encrypt the pdf using cert as the cert can be set to expire anytime according to what I set it to be and user can only view the PDF file based on the cert expiry.
Thanks in advance.
As per your original post, your intention is:
I am hoping to encrypt the pdf using cert as the cert can be set to expire anytime according to what I set it to be and user can only view the PDF file based on the cert expiry.
Basically, you want to grant a time-limited access to PDF to authorized user. The solution you try to build in code sample doesn't solve the problem. Certificate validity for data encryption is irrelevant, because certificate validity is not checked during decryption. In fact, even certificate is not necessary, it is sufficient to have just a private key to decrypt the data. In other words, certificate-based encryption is equal to password-based encryption. What certificate adds -- an easier way to locate decryption key (secret), nothing else.
In addition, once data is decrypted, a client can save data in an unencrypted form thus your restrictions are useless. Even if you try to put time constraints within JavaScript or whatever else locally (and JavaScript is executed only locally), it isn't a solution. As long as client can manipulate date/time on a device, client always can set desired date/time to violate your restrictions.
Your problem cannot be solved without inventing a 3rd party entity that will make decisions whether the requested operation is allowed, apply necessary restrictions and minimize chances that the data will leak in an unencrypted form (only minimize, not prevent). Such functionality is implemented in Digital Rights Management (DRM) or Rights Management Service (RMS) and you need to build your solution around these tools, not attempt to integrate them in your solution. There are plenty of vendors that offer DRM/RMS solutions you can look into and utilize their functionality to build the solution for your requirements.
I'm attempting to apply an rsa-sha512 signature to a message, using a certificate on the local HDD.
The final SignData raises a cryptographic exception "Invalid algorithm specified".
However, if I use SignData on a new instance of RSACryptoServiceProvider (created by importing an export of the original RSACryptoServiceProvider), I don't get that exception.
Is there some reason that the original version raises the exception? Since the "copy" is evidently different, I'd prefer to use the original.
The c# code I'm using is as follows:
X509Certificate2 cert = new X509Certificate2("C:\\Certs\\" + certName + ".p12", certPassword, X509KeyStorageFlags.Exportable);
RSACryptoServiceProvider csp = (RSACryptoServiceProvider)cert.PrivateKey;
UTF8Encoding ByteConverter = new UTF8Encoding();
byte[] unsignedBytes = ByteConverter.GetBytes(unsignedText);
byte[] signature;
//This raises an exception, "Invalid algorithm specified."
signature = csp.SignData(unsignedBytes, new SHA512CryptoServiceProvider());
//But if I make a copy of the RSACryptoServiceProvider, no exception is raised
RSACryptoServiceProvider cspCopy = new RSACryptoServiceProvider();
RSAParameters Key = csp.ExportParameters(true);
cspCopy.ImportParameters(Key);
signature = cspCopy.SignData(unsignedBytes, new SHA512CryptoServiceProvider());
To quote myself from a previous answer:
Software-backed RSACryptoServiceProvider is only capable of doing RSA signature using a SHA-2 digest algorithm when the CSP ProviderType value is 24 (PROV_RSA_AES) and the ProviderName is "Microsoft Enhanced RSA and AES Cryptographic Provider" (MS_ENH_RSA_AES_PROV). Hardware may or may not require PROV_RSA_AES, just depends on the hardware.
In this case, the PFX identifies the private key to belong to an older CSP (or, maybe, it has no CSP identifier and the PFX import is picking bad defaults). For software keys it's possible to extract the CspParameterInfo data from the key and re-open it using ProviderType 24, which is one way to work around the problem. Exporting the raw RSA parameters and importing it into a new object (which defaults to ProviderType 24) is a more aggressive workaround.
The better way to work around the problem is to abandon RSACryptoServiceProvider. Instead of using cert.PrivateKey, use cert.GetRSAPrivateKey(), which will almost always return an RSACng instance, which doesn't have this problem (but don't cast it if you can avoid it (if nothing else, see "almost" always)).
byte[] signature;
using (RSA rsa = cert.GetRSAPrivateKey())
{
signature = rsa.SignData(
unsignedBytes,
HashAlgorithmName.SHA512,
RSASignaturePadding.Pkcs1);
}
The using statement is correct for GetRSAPrivateKey, because it returns a distinct object per call.
RSACng and GetRSAPrivateKey both require .NET 4.6, but that's more than two years old at this point (and 4 (and a half) newer releases have happened in that time), so shouldn't cause you difficulty as a dependency.
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).