Rehash PDF document using iText7 - c#

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.

Related

Which are secrets keys should stored in client application and which stored in the server? Using NACl.Net/Salt

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!

PKCS#7 signature of externally calculated hash in C#

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

Get/Read Public Certificate for PDF Encryption using .pfx file with password through itext7 library/method

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.

External signing PDF with iTextSharp (SignDeferred) - Invalid Signature Problem (altered/corrupted document)

The goal is to implement a PDF signing process in which the server provides the hash to be signed to the client on request. The client then signs the given hash using a private key obtained from a smart card through a PKCS#11 interface. The signature is then sent back to the server for attaching into the PDF file using iTextSharp 5.5.4.
On reviewing the signature in Acrobat Reader, it gives me the error "The document has been altered or corrupted since the signature was applied".
Here is the method i used to calculate hash on the server.
public byte[] GetHashToSign(byte[] unsignedPdfBytes, out string signatureFieldName, out byte[] tempFile)
{
byte[] result = null;
using (PdfReader reader = new PdfReader(unsignedPdfBytes))
{
using (MemoryStream stream = new MemoryStream())
{
IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ETSI_CADES_DETACHED);
PdfStamper stamper = PdfStamper.CreateSignature(reader, stream, '\0');
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
signatureFieldName = appearance.FieldName;
MakeSignature.SignExternalContainer(appearance, external, 30000);
result = SHA256Managed.Create().ComputeHash(appearance.GetRangeStream());
tempFile = stream.ToArray();
}
return result;
}
}
After the client signs the given hash, i embed signed hash to pdf on the server. Here is the method i used to embed signed hash.
public void EmbedSignature(byte[] tempPdfBytes, string signatureFieldName, byte[] signedBytes, string signedPdfPath)
{
using (PdfReader pdfReader = new PdfReader(tempPdfBytes))
{
using (FileStream os = File.OpenWrite(signedPdfPath))
{
IExternalSignatureContainer external = new MyExternalSignatureContainer(signedBytes);
MakeSignature.SignDeferred(pdfReader, signatureFieldName, os, external);
}
}
}
On the other hand, when i use the SHA-1, i did not get any error. Acrobat Reader verifies the signature as follows. But i have to use SHA256.
Here is the method i used to calculate hash with SHA-1 on the server.
public byte[] GetHashToSign(byte[] unsignedPdfBytes, out string signatureFieldName, out byte[] tempFile)
{
byte[] result = null;
using (PdfReader reader = new PdfReader(unsignedPdfBytes))
{
using (MemoryStream stream = new MemoryStream())
{
IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_SHA1);
PdfStamper stamper = PdfStamper.CreateSignature(reader, stream, '\0');
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
signatureFieldName = appearance.FieldName;
MakeSignature.SignExternalContainer(appearance, external, 30000);
result = SHA1Managed.Create().ComputeHash(appearance.GetRangeStream());
tempFile = stream.ToArray();
}
return result;
}
}
EDIT: Signed PDF - Invalid signature with SHA256
Signed PDF - valid signature with SHA1
I research all related entries but I did not get any result to handle this problem.
What am I missing or doing wrong?
In Short
There are different sub-types of PDF signatures which have different requirements on the signature to embed. In your switch away from SHA1 usage you change the type and, therefore, not only have to replace SHA1 by SHA256 everywhere, you also have to generate your signature containers differently.
In Detail
You used to generate PDF signatures of subtype adbe.pkcs7.sha1:
external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_SHA1);
This subtype is defined as:
The SHA1 digest of the document’s byte range shall be encapsulated in the CMS SignedData field with ContentInfo of type Data. The digest of that SignedData shall be incorporated as the normal CMS digest. The value adbe.pkcs7.sha1 for the SubFilter key has been deprecated with PDF 2.0. To support backward compatibility, PDF readers should process this value for this key, but PDF writers shall not use this value.
(ISO 32000-2 section 12.8.3.3 "CMS (PKCS #7) signatures")
As SHA1 in this subtype is used without alternative, you must use a different subtype when replacing SHA1 by SHA256. Furthermore, this subtype is deprecated, so there is even more cause to switch.
You chose to switch to the subtype ETSI.CAdES.detached:
external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ETSI_CADES_DETACHED);
This subtype is specified as:
12.8.3.4.2 Signature dictionary for PAdES signatures
When the SubFilter value is ETSI.CAdES.detached, the value of Contents shall be a DER-encoded CMS SignedData binary data object containing the signature. The signature dictionary shall follow the specification given in 12.7.5.5, "Signature fields" with the following additional restrictions/constraints:
The ByteRange shall cover the entire file, including the signature dictionary but excluding the Contents entry.
The signature dictionary shall not contain a Cert entry.
Either the time of signing may be indicated by the value of the M entry in the signature dictionary or the signing-time attribute may be used, but not both.
12.8.3.4.3 Attributes for PAdES signatures
The attributes in the SignedData object used as the value of the signature dictionary Contents key shall obey the following rules:
a) content-type: shall be present and shall always have the value "id-data".
b) Signature timestamp: A timestamp from a trusted timestamp server should be present as a unsigned attribute. The timestamp should be applied on the digital signature immediately after the signature is created so the timestamp specifies a time as close as possible to the time at which the document was signed. The rules from clause 5.3 in ETSI EN 319 122-1 shall apply.
c) content timestamp: may be present. If the content timestamp attribute is present, it shall be used in the same way as defined in clause 5.2.8 in ETSI EN 319 122-1.
d) exactly one single SignerInfo attribute shall be present.
e) message-digest: shall be present and shall be used as defined in CMS (Internet RFC 5652).
f) signing-certificate or signing-certificate-v2: shall be used as a signed attribute as described in the ESS signing-certificate attribute or as described in the ESS signing-certificate-v2 attribute as defined in clause 5.2.2 in ETSI EN 319 122-1. The details of the signing certificate attribute can be found in Internet RFC 5035.
g) signing-time: may be present. If present, it contains a UTC time. If the signing-time attribute is present, the time of signing shall not be indicated by the value of the M entry in the signature dictionary.
h) signer-location, as defined in clause 5.2.5 in ETSI EN 319 122-1, may be present. In such a case, the Location entry in the signature dictionary shall not be present.
i) these attributes shall not be used: counter-signature, content-reference, content-identifier, and content-hints.
j) signer-attributes-v2: may be used and shall follow the definition given in clause 5.2.6.1 of ETSI EN 319 122-1.
k) Unsigned signature attributes not explicitly noted here may be ignored.
(ISO 32000-2 section 12.8.3.4 "CAdES signatures as used in PDF")
As you see, the ETSI.CAdES.detached subtype has far more specific requirements than the deprecated adbe.pkcs7.sha1 subtype. The main difference is, though,
that in adbe.pkcs7.sha1 the SHA1 digest of the signed byte ranges is the message signed by the signature container, and this message is encapsulated therein
while in ETSI.CAdES.detached the signed byte ranges themselves are signed directly by the signature container but are not encapsulated therein.
Thus, you do not merely have to switch the hashing algorithm in your client side code but you also have to use the hash value returned from your GetHashToSign method differently, not anymore as abstract data message to sign but instead already as the hash of the data to sign.
In your example "invalid signature with SHA256.pdf" the hash of the signed byte ranges still is used as abstract message to sign which is why it's invalid.

Securing a license key with RSA key

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).

Categories