I need to sign pdf with external web service. But there is an error on signed pdf like Document has been altered or corrupted since it was signed.
static void Main(string[] args)
{
//StreamSign();
PdfReader reader = new PdfReader(#"E:\pdf-exampleSign\example.pdf");
//MemoryStream os = new MemoryStream();
FileStream os = File.OpenWrite(#"E:\pdf-exampleSign\example_tmp.pdf");
PdfStamper stamper = PdfStamper.CreateSignature(reader, os, '\0');
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
appearance.SetVisibleSignature(new iTextSharp.text.Rectangle(36, 748, 144, 780), 1, "Signature");
IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
MakeSignature.SignExternalContainer(appearance, external, 20000);
byte[] hash = SHA256Managed.Create().ComputeHash(appearance.GetRangeStream());
StringBuilder builder = new StringBuilder();
for (int i = 0; i < hash.Length; i++)
{
builder.Append(hash[i].ToString("x2"));
}
var hex = builder.ToString();
//HERE IS THE SERVER SIDE BASE64 STRING I GOT
string ServerSideSignedBytes = "MIAGCSqGSIb3DQEHAqCAMIACAQExDzANBglghkgBZQMEAgE.....";
byte[] signbytes = Convert.FromBase64String(ServerSideSignedBytes);
X509Certificate2 certt = new X509Certificate2(signbytes);
var cert = new Org.BouncyCastle.X509.X509CertificateParser().ReadCertificate(certt.GetRawCertData());
Org.BouncyCastle.X509.X509Certificate[] chain = new Org.BouncyCastle.X509.X509Certificate[1];
chain[0] = cert;
using (PdfReader prereader = new PdfReader(#"E:\pdf-exampleSign\example_tmp.pdf"))
{
using (FileStream preos = File.OpenWrite(#"E:\pdf-exampleSign\example_signed.pdf"))
{
IExternalSignatureContainer externallast = new MyExternalSignatureContainer(signbytes, chain);
MakeSignature.SignDeferred(prereader, "Signature", preos, externallast);
}
}
}
public class MyExternalSignatureContainer : IExternalSignatureContainer
{
protected byte[] sig;
protected Org.BouncyCastle.X509.X509Certificate[] chain;
public MyExternalSignatureContainer(byte[] sig, Org.BouncyCastle.X509.X509Certificate[] chain)
{
this.sig = sig;
this.chain = chain;
}
public byte[] Sign(Stream s)
{
return sig;
}
public void ModifySigningDictionary(PdfDictionary signDic) { }
}
Related
I add to pdf document X509certificate and visible signature element using iTextSharp. The code does not cause errors, but while i open signed document, appears message that the certificate is invalid because document was corrupted after signing. I added the certificate to the trusted list. What could be the problem?
The code I used here
public void Sign()
{
X509Certificate2 certificate = GetSert();
Org.BouncyCastle.X509.X509Certificate[] chain = new Org.BouncyCastle.X509.X509Certificate[] { Org.BouncyCastle.Security.DotNetUtilities.FromX509Certificate(certificate) };
PdfReader reader = new PdfReader(#"C:\ Test.pdf");
PdfDictionary dict = reader.GetPageN(reader.NumberOfPages);
IList<iTextSharp.text.Image> list = GetImagesFromPdfDict(dict, reader);
PdfStamper stp = PdfStamper.CreateSignature(reader, new
FileStream(NewFP, FileMode.Create), '\0', null, true);
iTextSharp.text.Rectangle cropBox = reader.GetCropBox(reader.NumberOfPages);
PdfSignatureAppearance sap = stp.SignatureAppearance;
iTextSharp.text.Rectangle signPosition = new iTextSharp.text.Rectangle(cropBox.GetLeft(reader.NumberOfPages) + 55, Y, cropBox.GetLeft(reader.NumberOfPages) + 260, Y - 80);
byte[] pk = certificate.GetRawCertData();
sap.SignDate = DateTime.Now;
sap.Acro6Layers = true;
sap.Layer4Text = "";
sap.Layer2Text = "";
sap.SignatureGraphic = list[0];
sap.Render =
PdfSignatureAppearance.SignatureRender.Graphic;
PdfSignature dic = new PdfSignature(PdfName.ADBE_X509_RSA_SHA1,
PdfName.ADBE_X509_RSA_SHA1);
dic.Cert = certificate.GetRawCertData();
dic.Date = new PdfDate(sap.SignDate);
dic.Name = PdfPKCS7.GetSubjectFields(chain[0]).GetField("CN");
sap.CryptoDictionary = dic;
int csize = pk.Length;
Dictionary<PdfName, int> ex = new Dictionary<PdfName, int>(1);
ex.Add(PdfName.CONTENTS, csize * 2 + 2);
exc[PdfName.CONTENTS] = csize * 2 + 2;
sap.CertificationLevel = PdfSignatureAppearance.CERTIFIED_FORM_FILLING_AND_ANNOTATIONS;
sap.SetVisibleSignature(signPosition, reader.NumberOfPages, null);
sap.SetCrypto(null, chain, null, PdfSignatureAppearance.WINCER_SIGNED);
sap.PreClose(ex);
byte[] outc = new byte[csize];
PdfDictionary dic2 = new PdfDictionary();
Array.Copy(pk, 0, outc, 0, pk.Length);
dic2.Put(PdfName.CONTENTS, new
PdfString(outc).SetHexWriting(true));
sap.Close(dic2);
}
And my signed document here
30.11.2020
I try to enctypted current range stream using next code
sap.PreClose(ex);
Stream s = sap.GetRangeStream();
MemoryStream ss = new MemoryStream();
int read = 0;
int len = (int)s.Length+1;
byte[] buff = new byte[len];
MessageBox.Show(s.Length.ToString());
while ((read = s.Read(buff, 0, len)) > 0)
{
ss.Write(buff, 0, read);
}
byte[] ToS = ss.ToArray();
ContentInfo content = new ContentInfo(ToS);
SignedCms signedCms = new SignedCms(content, false);
CmsSigner signer = new CmsSigner(
SubjectIdentifierType.SubjectKeyIdentifier,
certificate);
signedCms.ComputeSignature(signer);
byte[] signedbytes = signedCms.Encode();
// Set signature to document
byte[] outc = new byte[signedbytes.Length];
PdfDictionary dic2 = new PdfDictionary();
Array.Copy(signedbytes, 0, outc, 0, signedbytes.Length);
dic2.Put(PdfName.CONTENTS, new PdfString(outc).SetHexWriting(true));
sap.Close(dic2);
but it works worse than the previous one. I got error:
Maybe signing incorrectly? My certificate alghoritm is sha256RSA
//09.12.2020
I achieved success using the following code:
public void PrepareSignatureAndGetHash(X509Certificate2 cert)
{
using (var reader = new PdfReader(#"C:\Test.pdf"))
{
using (var fileStream = new
FileStream(#"C:\Output.pdf", FileMode.Create))
{
using (var stamper = PdfStamper.CreateSignature(reader, fileStream, '0', null, true))
{
var signatureAppearance = stamper.SignatureAppearance;
Rectangle cropBox = reader.GetCropBox(reader.NumberOfPages);
Rectangle signPosition = new Rectangle(cropBox.GetRight(0) - 20, cropBox.GetBottom(0), cropBox.GetRight(0) - 250, cropBox.GetBottom(0) + 80);
signatureAppearance.SetVisibleSignature(signPosition, reader.NumberOfPages, null);
signatureAppearance.Reason = "Sig";
signatureAppearance.Layer2Text = "";
signatureAppearance.Image = iTextSharp.text.Image.GetInstance(#"C:\Stamp.png");
if (!cert.HasPrivateKey) { MessageBox.Show("Не найдено закрытого ключа"); }
var keyPair = Org.BouncyCastle.Security.DotNetUtilities.GetKeyPair(cert.PrivateKey).Private;
Org.BouncyCastle.X509.X509Certificate bcCert = Org.BouncyCastle.Security.DotNetUtilities.FromX509Certificate(cert);
var chain = new List<Org.BouncyCastle.X509.X509Certificate> { bcCert };
IExternalSignature signature = new PrivateKeySignature(keyPair, "SHA-256");
MakeSignature.SignDetached(signatureAppearance, signature, chain, null, null, null, 0, CryptoStandard.CMS);
}
}
}
}
Using a low level code I couldn't sign correctly
Kindly help me to resolve this issue. It is showing error shows cannot access the closed stream.
I am using memory stream from RLDC and watermarked first then the process of digital sign begin.
I think there is an issue in the last 6 lines or maybe I am not closing the variables properly.
What am I doing wrong here?
public static byte[] AddWatermark(byte[] bytes, BaseFont baseFont, string watermarkText)
{
using (var ms = new MemoryStream(10 * 1024))
{
using (var reader = new PdfReader(bytes))
using (var stamper = new PdfStamper(reader, ms))
{
var pages = reader.NumberOfPages;
for (var i = 1; i <= pages; i++)
{
var dc = stamper.GetOverContent(i);
AddWaterMarkText(dc, watermarkText, baseFont, 100, 45, BaseColor.GRAY, reader.GetPageSizeWithRotation(i));
// Keypair Generator
RsaKeyPairGenerator kpGenerator = new RsaKeyPairGenerator();
kpGenerator.Init(new KeyGenerationParameters(new SecureRandom(), 2048));
// Create a keypair
AsymmetricCipherKeyPair kp = kpGenerator.GenerateKeyPair();
// Certificate Generator
X509V3CertificateGenerator cGenerator = new X509V3CertificateGenerator();
cGenerator.SetSerialNumber(BigInteger.ProbablePrime(120, new Random()));
cGenerator.SetSubjectDN(new X509Name("CN=" + "abc.com"));
cGenerator.SetIssuerDN(new X509Name("CN=" + "Aalok"));
cGenerator.SetNotBefore(DateTime.Now);
cGenerator.SetNotAfter(DateTime.Now.Add(new TimeSpan(365, 0, 0, 0))); // Expire in 1 year
cGenerator.SetSignatureAlgorithm(HashType.SHA256withRSA.ToString()); // See the Appendix Below for info on the hash types supported by Bouncy Castle C#
cGenerator.SetPublicKey(kp.Public); // Only the public key should be used here!
Org.BouncyCastle.X509.X509Certificate cert = cGenerator.Generate(kp.Private); // Create a self-signed cert
byte[] encoded = cert.GetEncoded();
try
{
FileStream outStream = new FileStream("abc.der", FileMode.Create, FileAccess.ReadWrite);
{
outStream.Write(encoded, 0, encoded.Length);
}
}
catch (FileNotFoundException)
{
Console.WriteLine("File not found");
}
// Create the PKCS12 store
Pkcs12Store store = new Pkcs12StoreBuilder().Build();
// Add a Certificate entry
X509CertificateEntry certEntry = new X509CertificateEntry(cert);
store.SetCertificateEntry(cert.SubjectDN.ToString(), certEntry); // use DN as the Alias.
// Add a key entry
AsymmetricKeyEntry keyEntry = new AsymmetricKeyEntry(kp.Private);
store.SetKeyEntry(cert.SubjectDN.ToString() + "_key", keyEntry, new X509CertificateEntry[] { certEntry }); // Note that we only have 1 cert in the 'chain'
// Save to the file system
try
{
var filestream = new FileStream(#"abc.pfx", FileMode.Create, FileAccess.ReadWrite);
{
store.Save(filestream, "123".ToCharArray(), new SecureRandom());
}
}
catch (FileNotFoundException)
{
Console.WriteLine("File not found");
}
String alias = "";
ICollection<Org.BouncyCastle.X509.X509Certificate> chain = new List<Org.BouncyCastle.X509.X509Certificate>();
// searching for private key
foreach (string al in store.Aliases)
if (store.IsKeyEntry(al) && store.GetKey(al).Key.IsPrivate)
{
alias = al;
break;
}
AsymmetricKeyEntry pk = store.GetKey(alias);
foreach (X509CertificateEntry c in store.GetCertificateChain(alias).ToList())
{
chain.Add(c.Certificate);
}
RsaPrivateCrtKeyParameters parameters = pk.Key as RsaPrivateCrtKeyParameters;
PdfReader read = new PdfReader(bytes);
// PdfStamper.CreateSignature(read, ms, '\0');
PdfStamper stamp = PdfStamper.CreateSignature(read, ms, '\0');
// Creating the appearance
PdfSignatureAppearance appearance = stamp.SignatureAppearance;
appearance.Reason = "My reason for signing";
appearance.Location = "The middle of nowhere";
// appearance.SignDate = DateTime.Now;
appearance.SetVisibleSignature(new Rectangle(36, 748, 144, 780), 1, "sig");
// Creating the signature
IExternalSignature pks = new PrivateKeySignature(parameters, DigestAlgorithms.SHA256);
MakeSignature.SignDetached(appearance, pks, chain, null, null, null, 0, CryptoStandard.CMS);
stamp.Close();
read.Close();
break;
}
//stamper.Close();
reader.Close();
ms.Close();
}
return ms.ToArray();
}
}
I have taken the document hash and get it signed from a signing server which returned me the CMS signature.
Following is the code snippet:
Stream resultStream = new MemoryStream();
Stream readerStream = new MemoryStream(documentBytes);
PdfReader pdfReader = new PdfReader(readerStream);
byte[] signedDocument;
PdfSigner pdfSigner = new PdfSigner(pdfReader, resultStream, new StampingProperties().UseAppendMode());
PdfAcroForm acroForm = PdfAcroForm.GetAcroForm(pdfSigner.GetDocument(), true);
PdfSignatureFormField signatureFormField = (PdfSignatureFormField)acroForm.GetField("Signature1");
var signatureRects = signatureFormField.GetPdfObject().GetAsRectangle(PdfName.Rect);
pdfSigner.SetFieldName("Signature1");
ImageData imageData = ImageDataFactory.Create(handSignatureBytes);
PdfSignatureAppearance sigAppearance = pdfSigner.GetSignatureAppearance();
sigAppearance.SetContact("ContactInfo");
sigAppearance.SetLocation("Location");
sigAppearance.SetPageNumber(1);
sigAppearance.SetReason("SigningReason");
sigAppearance.SetSignatureGraphic(imageData);
sigAppearance.SetSignatureCreator("Malik");
sigAppearance.SetPageRect(signatureRects);
sigAppearance.SetReuseAppearance(false);
pdfSigner.SetSignDate(signingTime);
pdfSigner.GetDocument().GetCatalog().SetModified();
int estimatedSize = 12480;
IExternalSignatureContainer signatureContainer = new ExternalContainer(document_id);
pdfSigner.SignExternalContainer(signatureContainer, estimatedSize);
signedDocument = ((MemoryStream)resultStream).ToArray();
Signature Container Implementation:
public string documentId;
public ExternalContainer(string docId)
{
documentId = docId;
}
public void ModifySigningDictionary(PdfDictionary signDic)
{
signDic.Put(PdfName.Filter, PdfName.Adobe_PPKLite);
signDic.Put(PdfName.SubFilter, PdfName.Adbe_pkcs7_detached);
}
public byte[] Sign(Stream data)
{
//Send HTTP call to signing server and get CMS signature base64
byte[] signedData = Convert.FromBase64String(result[0]);
return signedData;
}
After getting signed document from IExternalSignatureContainer implementation, Adobe says:
Signature is INVALID
The Document has been altered or corrupted since the signature was applied
The method receives pdf document as byte array that should be signed, certificate to sign with and TSA client and it returns signed document as byte array or null if there was error. Now it returns signed pdf document but it isnt LTV enabled.
Signed document must be LTV enabled. How do I make the document being returned LTV enabled? I'll be very grateful for any suggestions.
public byte[] Sign(byte[] document, X509Certificate2 certificate, ITSAClient tsaClient)
{
byte[] signedDocument = null;
IExternalSignature signature = new X509Certificate2Signature(certificate, "SHA-1");
Org.BouncyCastle.X509.X509CertificateParser cp = new Org.BouncyCastle.X509.X509CertificateParser();
Org.BouncyCastle.X509.X509Certificate[] chain = new Org.BouncyCastle.X509.X509Certificate[] { cp.ReadCertificate(certificate.RawData) };
PdfReader reader = new PdfReader(document);
MemoryStream ms = new MemoryStream();
PdfStamper st = PdfStamper.CreateSignature(reader, ms, '\0');
PdfSignatureAppearance sap = st.SignatureAppearance;
sap.CertificationLevel = PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED;
sap.SignatureCreator = "NAME";
sap.Reason = "REASON";
sap.Contact = "CONTACT";
sap.Location = "LOCATION";
sap.SignDate = DateTime.Now;
RectangleF rectangle = new RectangleF(400.98139f, 54.88828f, 530, 84.88828f);
sap.Layer2Font = iTextSharp.text.FontFactory.GetFont(BaseFont.TIMES_ROMAN, BaseFont.CP1257, 7f);
sap.Layer2Font.Color = iTextSharp.text.BaseColor.RED;
sap.Layer2Text = string.Format("Signed for testing: {0}", DateTime.Now.ToString("dd.MM.yyyy."));
sap.SignatureRenderingMode = PdfSignatureAppearance.RenderingMode.DESCRIPTION;
sap.SetVisibleSignature(new iTextSharp.text.Rectangle(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height), 1, null);
MakeSignature.SignDetached(sap, signature, chain, null, null, tsaClient, 0, CryptoStandard.CMS);
st.Close();
ms.Flush();
signedDocument = ms.ToArray();
ms.Close();
reader.Close();
return signedDocument;
}
In general you cannot expect the signature creation step to return a LTV-enabled signature.
Leonard Rosenthol (Adobe's prime PDF Guru) remarked on the iText mailing list in early 2013 that while it is possible to have the signature container itself already contain all the information required for a LTV enabled signature, this is very uncommon and not always possible.
(There are exceptions, e.g. there was a Swisscom signing service generating signature containers with all the extra information required for a LTV enabled integrated PDF signature.)
Thus, in general you'll have to add all the missing information in a second step.
Such a second step, on the other hand, means that such a second pass may interfere with signing with CertificationLevel = CERTIFIED_NO_CHANGES_ALLOWED - the current PDF specification requires that even for such a certification level incremental updates are allowed if they contain only signature validation information but I have not yet seen Adobe Reader not complain about in such a case. So you might have to loosen your certification level for LTV enabling.
For iText 5 / Java and iText 7 / Java helper classes that execute this second step can be found in this answer (iText 5) and this answer (iText 7).
I have ported the Java helper class for iText 5 to C#:
using iTextSharp.text;
using iTextSharp.text.error_messages;
using iTextSharp.text.pdf;
using iTextSharp.text.pdf.security;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Ocsp;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Ocsp;
using Org.BouncyCastle.X509;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Security.Cryptography;
using System.Text;
[...]
class AdobeLtvEnabling
{
/**
* Use this constructor with a {#link PdfStamper} in append mode. Otherwise
* the existing signatures will be damaged.
*/
public AdobeLtvEnabling(PdfStamper pdfStamper)
{
this.pdfStamper = pdfStamper;
}
/**
* Call this method to have LTV information added to the {#link PdfStamper}
* given in the constructor.
*/
public void enable(IOcspClient ocspClient, ICrlClient crlClient)
{
AcroFields fields = pdfStamper.AcroFields;
bool encrypted = pdfStamper.Reader.IsEncrypted();
List<String> names = fields.GetSignatureNames();
foreach (String name in names)
{
PdfPKCS7 pdfPKCS7 = fields.VerifySignature(name);
PdfDictionary signatureDictionary = fields.GetSignatureDictionary(name);
X509Certificate certificate = pdfPKCS7.SigningCertificate;
addLtvForChain(certificate, ocspClient, crlClient, getSignatureHashKey(signatureDictionary, encrypted));
}
outputDss();
}
//
// the actual LTV enabling methods
//
void addLtvForChain(X509Certificate certificate, IOcspClient ocspClient, ICrlClient crlClient, PdfName key)
{
if (seenCertificates.Contains(certificate))
return;
seenCertificates.Add(certificate);
ValidationData validationData = new ValidationData();
while (certificate != null)
{
Console.WriteLine(certificate.SubjectDN);
X509Certificate issuer = getIssuerCertificate(certificate);
validationData.certs.Add(certificate.GetEncoded());
byte[] ocspResponse = ocspClient.GetEncoded(certificate, issuer, null);
if (ocspResponse != null)
{
Console.WriteLine(" with OCSP response");
validationData.ocsps.Add(ocspResponse);
X509Certificate ocspSigner = getOcspSignerCertificate(ocspResponse);
if (ocspSigner != null)
{
Console.WriteLine(" signed by {0}\n", ocspSigner.SubjectDN);
}
addLtvForChain(ocspSigner, ocspClient, crlClient, getOcspHashKey(ocspResponse));
}
else
{
ICollection<byte[]> crl = crlClient.GetEncoded(certificate, null);
if (crl != null && crl.Count > 0)
{
Console.WriteLine(" with {0} CRLs\n", crl.Count);
foreach (byte[] crlBytes in crl)
{
validationData.crls.Add(crlBytes);
addLtvForChain(null, ocspClient, crlClient, getCrlHashKey(crlBytes));
}
}
}
certificate = issuer;
}
validated[key] = validationData;
}
void outputDss()
{
PdfWriter writer = pdfStamper.Writer;
PdfReader reader = pdfStamper.Reader;
PdfDictionary dss = new PdfDictionary();
PdfDictionary vrim = new PdfDictionary();
PdfArray ocsps = new PdfArray();
PdfArray crls = new PdfArray();
PdfArray certs = new PdfArray();
writer.AddDeveloperExtension(PdfDeveloperExtension.ESIC_1_7_EXTENSIONLEVEL5);
writer.AddDeveloperExtension(new PdfDeveloperExtension(PdfName.ADBE, new PdfName("1.7"), 8));
PdfDictionary catalog = reader.Catalog;
pdfStamper.MarkUsed(catalog);
foreach (PdfName vkey in validated.Keys)
{
PdfArray ocsp = new PdfArray();
PdfArray crl = new PdfArray();
PdfArray cert = new PdfArray();
PdfDictionary vri = new PdfDictionary();
foreach (byte[] b in validated[vkey].crls)
{
PdfStream ps = new PdfStream(b);
ps.FlateCompress();
PdfIndirectReference iref = writer.AddToBody(ps, false).IndirectReference;
crl.Add(iref);
crls.Add(iref);
}
foreach (byte[] b in validated[vkey].ocsps)
{
PdfStream ps = new PdfStream(buildOCSPResponse(b));
ps.FlateCompress();
PdfIndirectReference iref = writer.AddToBody(ps, false).IndirectReference;
ocsp.Add(iref);
ocsps.Add(iref);
}
foreach (byte[] b in validated[vkey].certs)
{
PdfStream ps = new PdfStream(b);
ps.FlateCompress();
PdfIndirectReference iref = writer.AddToBody(ps, false).IndirectReference;
cert.Add(iref);
certs.Add(iref);
}
if (ocsp.Length > 0)
vri.Put(PdfName.OCSP, writer.AddToBody(ocsp, false).IndirectReference);
if (crl.Length > 0)
vri.Put(PdfName.CRL, writer.AddToBody(crl, false).IndirectReference);
if (cert.Length > 0)
vri.Put(PdfName.CERT, writer.AddToBody(cert, false).IndirectReference);
vri.Put(PdfName.TU, new PdfDate());
vrim.Put(vkey, writer.AddToBody(vri, false).IndirectReference);
}
dss.Put(PdfName.VRI, writer.AddToBody(vrim, false).IndirectReference);
if (ocsps.Length > 0)
dss.Put(PdfName.OCSPS, writer.AddToBody(ocsps, false).IndirectReference);
if (crls.Length > 0)
dss.Put(PdfName.CRLS, writer.AddToBody(crls, false).IndirectReference);
if (certs.Length > 0)
dss.Put(PdfName.CERTS, writer.AddToBody(certs, false).IndirectReference);
catalog.Put(PdfName.DSS, writer.AddToBody(dss, false).IndirectReference);
}
//
// VRI signature hash key calculation
//
static PdfName getCrlHashKey(byte[] crlBytes)
{
X509Crl crl = new X509Crl(CertificateList.GetInstance(crlBytes));
byte[] signatureBytes = crl.GetSignature();
DerOctetString octetString = new DerOctetString(signatureBytes);
byte[] octetBytes = octetString.GetEncoded();
byte[] octetHash = hashBytesSha1(octetBytes);
PdfName octetName = new PdfName(Utilities.ConvertToHex(octetHash));
return octetName;
}
static PdfName getOcspHashKey(byte[] basicResponseBytes)
{
BasicOcspResponse basicResponse = BasicOcspResponse.GetInstance(Asn1Sequence.GetInstance(basicResponseBytes));
byte[] signatureBytes = basicResponse.Signature.GetBytes();
DerOctetString octetString = new DerOctetString(signatureBytes);
byte[] octetBytes = octetString.GetEncoded();
byte[] octetHash = hashBytesSha1(octetBytes);
PdfName octetName = new PdfName(Utilities.ConvertToHex(octetHash));
return octetName;
}
static PdfName getSignatureHashKey(PdfDictionary dic, bool encrypted)
{
PdfString contents = dic.GetAsString(PdfName.CONTENTS);
byte[] bc = contents.GetOriginalBytes();
if (PdfName.ETSI_RFC3161.Equals(PdfReader.GetPdfObject(dic.Get(PdfName.SUBFILTER))))
{
using (Asn1InputStream din = new Asn1InputStream(bc))
{
Asn1Object pkcs = din.ReadObject();
bc = pkcs.GetEncoded();
}
}
byte[] bt = hashBytesSha1(bc);
return new PdfName(Utilities.ConvertToHex(bt));
}
static byte[] hashBytesSha1(byte[] b)
{
SHA1 sha = new SHA1CryptoServiceProvider();
return sha.ComputeHash(b);
}
//
// OCSP response helpers
//
static X509Certificate getOcspSignerCertificate(byte[] basicResponseBytes)
{
BasicOcspResponse borRaw = BasicOcspResponse.GetInstance(Asn1Sequence.GetInstance(basicResponseBytes));
BasicOcspResp bor = new BasicOcspResp(borRaw);
foreach (X509Certificate x509Certificate in bor.GetCerts())
{
if (bor.Verify(x509Certificate.GetPublicKey()))
return x509Certificate;
}
return null;
}
static byte[] buildOCSPResponse(byte[] BasicOCSPResponse)
{
DerOctetString doctet = new DerOctetString(BasicOCSPResponse);
Asn1EncodableVector v2 = new Asn1EncodableVector();
v2.Add(OcspObjectIdentifiers.PkixOcspBasic);
v2.Add(doctet);
DerEnumerated den = new DerEnumerated(0);
Asn1EncodableVector v3 = new Asn1EncodableVector();
v3.Add(den);
v3.Add(new DerTaggedObject(true, 0, new DerSequence(v2)));
DerSequence seq = new DerSequence(v3);
return seq.GetEncoded();
}
//
// X509 certificate related helpers
//
static X509Certificate getIssuerCertificate(X509Certificate certificate)
{
String url = getCACURL(certificate);
if (url != null && url.Length > 0)
{
HttpWebRequest con = (HttpWebRequest)WebRequest.Create(url);
HttpWebResponse response = (HttpWebResponse)con.GetResponse();
if (response.StatusCode != HttpStatusCode.OK)
throw new IOException(MessageLocalization.GetComposedMessage("invalid.http.response.1", (int)response.StatusCode));
//Get Response
Stream inp = response.GetResponseStream();
byte[] buf = new byte[1024];
MemoryStream bout = new MemoryStream();
while (true)
{
int n = inp.Read(buf, 0, buf.Length);
if (n <= 0)
break;
bout.Write(buf, 0, n);
}
inp.Close();
var cert2 = new System.Security.Cryptography.X509Certificates.X509Certificate2(bout.ToArray());
return new X509Certificate(X509CertificateStructure.GetInstance(cert2.GetRawCertData()));
}
try
{
certificate.Verify(certificate.GetPublicKey());
return null;
}
catch (Exception e)
{
}
foreach (X509Certificate candidate in extraCertificates)
{
try
{
certificate.Verify(candidate.GetPublicKey());
return candidate;
}
catch (Exception e)
{
}
}
return null;
}
static String getCACURL(X509Certificate certificate)
{
try
{
Asn1Object obj = getExtensionValue(certificate, X509Extensions.AuthorityInfoAccess.Id);
if (obj == null)
{
return null;
}
Asn1Sequence AccessDescriptions = (Asn1Sequence)obj;
for (int i = 0; i < AccessDescriptions.Count; i++)
{
Asn1Sequence AccessDescription = (Asn1Sequence)AccessDescriptions[i];
if (AccessDescription.Count != 2)
{
continue;
}
else
{
if ((AccessDescription[0] is DerObjectIdentifier) && ((DerObjectIdentifier)AccessDescription[0]).Id.Equals("1.3.6.1.5.5.7.48.2"))
{
String AccessLocation = getStringFromGeneralName((Asn1Object)AccessDescription[1]);
return AccessLocation == null ? "" : AccessLocation;
}
}
}
}
catch
{
}
return null;
}
static Asn1Object getExtensionValue(X509Certificate certificate, String oid)
{
byte[] bytes = certificate.GetExtensionValue(new DerObjectIdentifier(oid)).GetDerEncoded();
if (bytes == null) {
return null;
}
Asn1InputStream aIn = new Asn1InputStream(new MemoryStream(bytes));
Asn1OctetString octs = (Asn1OctetString)aIn.ReadObject();
aIn = new Asn1InputStream(new MemoryStream(octs.GetOctets()));
return aIn.ReadObject();
}
private static String getStringFromGeneralName(Asn1Object names)
{
Asn1TaggedObject taggedObject = (Asn1TaggedObject) names;
return Encoding.GetEncoding(1252).GetString(Asn1OctetString.GetInstance(taggedObject, false).GetOctets());
}
//
// inner class
//
class ValidationData
{
public IList<byte[]> crls = new List<byte[]>();
public IList<byte[]> ocsps = new List<byte[]>();
public IList<byte[]> certs = new List<byte[]>();
}
//
// member variables
//
PdfStamper pdfStamper;
ISet<X509Certificate> seenCertificates = new HashSet<X509Certificate>();
IDictionary<PdfName, ValidationData> validated = new Dictionary<PdfName, ValidationData>();
public static List<X509Certificate> extraCertificates = new List<X509Certificate>();
}
You generally use that class like this
PdfReader reader = new PdfReader(signedDocument);
FileStream os = new FileStream(ENABLED_PDF, FileMode.Create);
PdfStamper pdfStamper = new PdfStamper(reader, os, (char)0, true);
AdobeLtvEnabling adobeLtvEnabling = new AdobeLtvEnabling(pdfStamper);
IOcspClient ocsp = new OcspClientBouncyCastle();
ICrlClient crl = new CrlClientOnline();
adobeLtvEnabling.enable(ocsp, crl);
pdfStamper.Close();
The original Java class has the limitation that
the complete certificate chains are assumed to be buildable using AIA entries.
This means that every (non-root) certificate in question contains an URL for downloading its issuer certificate.
This is not always the case. To get around that limitation here, I added a public static List extraCertificates into which one can put additional certificates which are tested as issuer certificate candidates if there is no issuer certificate URL in some certificates. Before calling enable you have to add those extra certificates as BouncyCastle X509Certificate objects.
I am trying to sign a PDF Document using two web services in two servers. But it is showing "Document has been altered or corrupt since it was signed" in Adobe Reader. Can anybody suggest what is wrong in following code.
PROCEDURE
1. Web service (WS) on Server A, Generate hash from PDF and sent to WS on Server B for signing.
2. WS on Server B signs hash.
3. WS on Server A receives signed hash and Embed in PDF document.
CODE
GENERATE HASH
private PDFHashData generateHash(byte[] content, string userName)
{
PdfReader reader = new PdfReader(content);
MemoryStream ms = new MemoryStream();
PdfStamper stamper = PdfStamper.CreateSignature(reader, ms, '\0');
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
appearance.SetVisibleSignature(new Rectangle(500, 150, 400, 200), 1, signatureFieldName);
appearance.SignDate = DateTime.Now;
appearance.Reason = Reason;
appearance.Location = Location;
appearance.Contact = Contact;
StringBuilder buf = new StringBuilder();
buf.Append("Digitally signed by");
buf.Append("\n");
buf.Append(userName);
buf.Append("\n");
buf.Append("Date: " + appearance.SignDate);
appearance.Layer2Text = buf.ToString();
appearance.Acro6Layers = true;
appearance.CertificationLevel = 0;
IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
MakeSignature.SignExternalContainer(appearance, external, 8192);
byte[] hash = SHA256Managed.Create().ComputeHash(appearance.GetRangeStream());
StringBuilder hex = new StringBuilder(hash.Length * 2);
foreach (byte b in hash)
hex.AppendFormat("{0:x2}", b);
PDFHashData phData= new PDFHashData();
phData.Hash = hex.ToString();
phData.Content = Convert.ToBase64String(ms.ToArray());
return phData;
}
SIGN HASH
byte[] StringToByteArray(string hex)
{
return Enumerable.Range(0, hex.Length)
.Where(x => x % 2 == 0)
.Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
.ToArray();
}
private Stream getCertificate()
{
// Base 64 byte - PFX file with private key
return new MemoryStream(Convert.FromBase64String("..................................AgIEAA=="));
}
protected void Page_Load(object sender, EventArgs e)
{
Stream stream = Request.InputStream;
byte[] buffer = new byte[stream.Length];
stream.Read(buffer, 0, buffer.Length);
byte[] hash = StringToByteArray(Encoding.UTF8.GetString(buffer));
Pkcs12Store store = new Pkcs12Store(getCertificate(), "*******".ToCharArray());
String alias = "";
foreach (string al in store.Aliases)
if (store.IsKeyEntry(al) && store.GetKey(al).Key.IsPrivate)
{
alias = al;
break;
}
AsymmetricKeyEntry pk = store.GetKey(alias);
X509CertificateEntry[] chain = store.GetCertificateChain(alias);
List<Org.BouncyCastle.X509.X509Certificate> c = new List<Org.BouncyCastle.X509.X509Certificate>();
foreach (X509CertificateEntry en in chain)
{
c.Add(en.Certificate);
}
PrivateKeySignature signature = new PrivateKeySignature(pk.Key, "SHA1");
String hashAlgorithm = signature.GetHashAlgorithm();
PdfPKCS7 sgn = new PdfPKCS7(null, c, hashAlgorithm, false);
DateTime signingTime = DateTime.Now;
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);
byte[] extSignature = signature.Sign(sh);
sgn.SetExternalDigest(extSignature, null, signature.GetEncryptionAlgorithm());
Response.Write(Convert.ToBase64String(sgn.GetEncodedPKCS7(hash, null, null, null, CryptoStandard.CMS)));
}
EMBED SIGNATURE TO PDF
private byte[] signPDF(byte[] content, string userName, byte[] pk)
{
PdfReader reader = new PdfReader(content);
MemoryStream os = new MemoryStream();
IExternalSignatureContainer external = new MyExternalSignatureContainer(pk);
MakeSignature.SignDeferred(reader, signatureFieldName, os, external);
return os.ToArray();
}
For those who are interested I am posting the answer.
I ended up using itextsharp 5.5.10. Code is given below,
Initialize PDF object
public PDFSigning(byte[] Content, string UserName)
{
content = Content;
reader = new PdfReader(content);
ms = new MemoryStream();
stamper = PdfStamper.CreateSignature(reader, ms, '\0');
appearance = stamper.SignatureAppearance;
userName = UserName;
}
private Stream getCertificate()
{
return new MemoryStream(Convert.FromBase64String("************************=="));
}
Generate hash
private string generateHash()
{
appearance.SetVisibleSignature(new Rectangle(500, 150, 400, 200), 1, signatureFieldName);
appearance.SignDate = DateTime.Now;
appearance.Reason = Reason;
appearance.Location = Location;
appearance.Contact = Contact;
StringBuilder buf = new StringBuilder();
buf.Append("Digitally signed by");
buf.Append("\n");
buf.Append(userName);
buf.Append("\n");
buf.Append("Date: " + appearance.SignDate);
appearance.Layer2Text = buf.ToString();
appearance.Acro6Layers = true;
appearance.CertificationLevel = 0;
PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED)
{
Date = new PdfDate(appearance.SignDate),
Name = userName
};
dic.Reason = appearance.Reason;
dic.Location = appearance.Location;
dic.Contact = appearance.Contact;
appearance.CryptoDictionary = dic;
Dictionary<PdfName, int> exclusionSizes = new Dictionary<PdfName, int>();
exclusionSizes.Add(PdfName.CONTENTS, (csize * 2) + 2);
appearance.PreClose(exclusionSizes);
HashAlgorithm sha = new SHA256CryptoServiceProvider();
Stream s = appearance.GetRangeStream();
int read = 0;
byte[] buff = new byte[0x2000];
while ((read = s.Read(buff, 0, 0x2000)) > 0)
{
sha.TransformBlock(buff, 0, read, buff, 0);
}
sha.TransformFinalBlock(buff, 0, 0);
StringBuilder hex = new StringBuilder(sha.Hash.Length * 2);
foreach (byte b in sha.Hash)
hex.AppendFormat("{0:x2}", b);
return hex.ToString();
}
Sign hash
public byte[] SignMsg(string hexhash)
{
byte[] hash = hexToByteArray(hexhash);
Pkcs12Store store = new Pkcs12Store(getCertificate(), "*********".ToCharArray());
String alias = "";
foreach (string al in store.Aliases)
if (store.IsKeyEntry(al) && store.GetKey(al).Key.IsPrivate)
{
alias = al;
break;
}
AsymmetricKeyEntry pk = store.GetKey(alias);
X509CertificateEntry[] chain = store.GetCertificateChain(alias);
List<Org.BouncyCastle.X509.X509Certificate> c = new List<Org.BouncyCastle.X509.X509Certificate>();
foreach (X509CertificateEntry en in chain)
{
c.Add(en.Certificate);
}
PrivateKeySignature signature = new PrivateKeySignature(pk.Key, "SHA256");
String hashAlgorithm = signature.GetHashAlgorithm();
PdfPKCS7 sgn = new PdfPKCS7(null, c, hashAlgorithm, false);
DateTime signingTime = DateTime.Now;
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);
byte[] extSignature = signature.Sign(sh);
sgn.SetExternalDigest(extSignature, null, signature.GetEncryptionAlgorithm());
return sgn.GetEncodedPKCS7(hash, null, null, null, CryptoStandard.CMS);
}
Sign PDF
private byte[] signPDF(byte[] pk)
{
byte[] paddedSig = new byte[csize];
System.Array.Copy(pk, 0, paddedSig, 0, pk.Length);
PdfDictionary dic2 = new PdfDictionary();
dic2.Put(PdfName.CONTENTS, new PdfString(paddedSig).SetHexWriting(true));
appearance.Close(dic2);
//System.IO.File.WriteAllBytes(System.Web.HttpContext.Current.Server.MapPath("~/temp.pdf"), ms.ToArray());
return ms.ToArray();
}