I was signing the document after flattening all fields. While using the NOT_CERTIFIED option, I was able to sign the document with no issue, but my objective is to certify the document with the option CERTIFIED_NO_CHANGES_ALLOWED and when I do so I got invalid signatures due to document alteration.
original pdf
& signed pdf
PdfReader reader = new PdfReader(SRC);
reader.setUnethicalReading(true);
PdfSigner pdfSigner = new PdfSigner(reader, new FileOutputStream(DEST), new StampingProperties());
pdfSigner.setFieldName("signature");
pdfSigner.setCertificationLevel(PdfSigner.CERTIFIED_NO_CHANGES_ALLOWED);
PdfAcroForm.getAcroForm(pdfSigner.getDocument(), false).flattenFields();
IExternalSignature pks =
new PrivateKeySignature(pk, DigestAlgorithms.SHA256, provider.getName());
IExternalDigest digest = new BouncyCastleDigest();
pdfSigner.signDetached(digest, pks, chain, null, null, null, 0, CryptoStandard.CMS);
Related
We use Adobe Acrobat to add fields to a PDF. We want to be able to access these fields and reference their exact location so we can stamp content in their location after getting the PDF back from DocuSign. We do this by manipulating the PDF's bytes using the C# ITextSharp Text PDF library. Unfortunately, opening the PDF in Adobe Acrobat once it's returned reveals that all fields are removed from our document.
Our C# populates most of these fields with text data from our database. 2 fields are left blank, because we want to use C# to superimpose a static image of someone's signature over their location after the document has been signed via DocuSign.
This is okay for what's populated before DocuSign gets our document, because removing the field doesn't visually omit data previously entered into these fields. This is NOT fine when it comes time for us to stamp our static signature image into the document. Our usage of ITextSharp relies on finding a field with a particular Adobe Acrobat ID, getting its location, and "stamping" a static image in that location.
Is there a way to tell DocuSign that we want to maintain all of our PDF fields, their locations, and their IDs?
public byte[] StampStaticSignature(byte[] documentBytes)
{
var signatureContainer = new SignatureContainer();
var signatureBytes = signatureContainer.GetSignatureBytes();
var reader = new PdfReader(documentBytes);
var updatedForm = new byte[] { };
using var stream = new MemoryStream();
var stamper = new PdfStamper(reader, stream);
var pdfFormFields = stamper.AcroFields;
var signatureImage = GetImageFromStream(signatureBytes);
//_adobeSignatureFields is a list of strings used to
//identify the signature fields by their ID set in Adobe Acrobat.
foreach (var signatureField in _adobeSignatureFields)
{
var sigPosition = pdfFormFields.GetFieldPositions(signatureField);
var page = sigPosition[0];
var x1 = sigPosition[1];
var y1 = sigPosition[2];
var x2 = sigPosition[3];
var y2 = sigPosition[4];
var contentBytes = stamper.GetOverContent((int)page);
var signatureFieldHeight = y2 - y1;
var signatureFieldWidth = x2 - x1;
signatureImage.ScaleToFit(signatureFieldWidth, signatureFieldHeight);
signatureImage.SetAbsolutePosition(x1, y1);
contentBytes.AddImage(signatureImage);
}
stamper.FormFlattening = false;
stamper.Close();
reader.Close();
updatedForm = stream.ToArray();
stream.Dispose();
return updatedForm;
}
Hank, as Inbar mentioned already we will need to examine your code and your API logs to see what could be going wrong. It is very hard to say what went wrong, especially when using 3rd party software like ITextSharp. I'd suggest opening a case with us by visiting our support center and adding a reference to case 09033616
Is there a way I could control the order of the documents presented to a signer when using composite templates?
If I have, for example, 3 templates that I want to send and I want the signer to see (not necessarily sign) a specific one first how would I set that.
All templates only have one document.
As far as I understand the sequence property for server and inline templates has more to do with the order of documents and tabs in specific composites not for the overall envelope.
Here is the c# code I have for now (not perfect, kind of a first draft). I have a few template Id's stored in a list of string and I'm looping through them assigning almost random sequencing values because I only care if the inline template sequence value is higher than the server template.
What I'm asking is if I have a template document that I would like to show first is there a way to do that? It doesn't have to implement looping. I just want to know if there is a way
List<CompositeTemplate> compositeTemplates = new List<CompositeTemplate>();
Recipients recipientsServerTemplate = new Recipients();
List<Signer> signers = new List<Signer>();
List<CarbonCopy> carbonCopies = new List<CarbonCopy>();
List<Text> textControlsInTemplate = SetTemplateFields();
Tabs tabs = new Tabs
{
TextTabs = textControlsInTemplate
};
Signer signer1 = new Signer();
signer1.Email = signerEmail;
signer1.Name = signerName;
signer1.RoleName = "signer";
signer1.RecipientId = "1";
signer1.Tabs = tabs;
signers.Add(signer1);
CarbonCopy cc1 = new CarbonCopy();
cc1.Email = ccEmail;
cc1.Name = ccName;
cc1.RoleName = "cc";
cc1.RecipientId = "2";
carbonCopies.Add(cc1);
recipientsServerTemplate.Signers = signers;
recipientsServerTemplate.CarbonCopies = carbonCopies;
int i = 1;
foreach (string templateId in templateIds)
{
List<ServerTemplate> ServerTemplates = new List<ServerTemplate>();
List<InlineTemplate> InlineTemplates = new List<InlineTemplate>();
CompositeTemplate CT = new CompositeTemplate
{
CompositeTemplateId = i.ToString()
};
ServerTemplate ST = new ServerTemplate
{
Sequence = i.ToString(),
TemplateId = templateId
};
InlineTemplate IT = new InlineTemplate
{
Recipients = recipientsServerTemplate,
Sequence = (i+1).ToString()
};
InlineTemplates.Add(IT);
ServerTemplates.Add(ST);
CT.ServerTemplates = ServerTemplates;
CT.InlineTemplates = InlineTemplates;
compositeTemplates.Add(CT);
i++;
}
EnvelopeDefinition env = new EnvelopeDefinition
{
Status = "sent",
CompositeTemplates = compositeTemplates
};
Assuming each server template contains one document, the composite template with the lowest sequence number should show its template's document first.
If the sequence number doesn't work, then change the order of the composite template items within the array. Note that the envelope definition's compositeTemplates attribute takes an array of composite template objects.
I suggest that you first use the API Request Builder to try out the sequencing. The tool can then be used to generate the matching C# code.
Here's a live example with one server template used twice in an envelope. You can easily modify the diagram to use two different server templates.
Blog post about composite templates
Each envelope type will have its own email subject/body that is static for that envelope type. Here is an example of a few predetermined envelope types:
Mortgage closing documents
Mortgage loan application documents
These envelopes will contain a varied number of documents and each document is unique. I've attempted to create a template without a document, but it appears that is not allowed.
I'd like to be able to save the email body and subject for these envelope types so that when I send an envelope via the DocuSign API, I don't have to directly specify these fields. I'm hoping to avoid storing the subject/body inside of code or a configuration file. Preferably, I'd like a non-technical person to be able to change the subject/body through the DocuSign interface.
What I ended up doing:
Based on Kim Brandl's recommendations, I wrote the following code which worked as expected. A few notes:
The initial file that you added to the template will not appear in the final envelope if the ServerTemplate sequence is greater than the InlineTemplate. You can read more here.
The Recipients, signers, documents, etc., follow the same pattern as a standard EnvelopeDefinition except the data is placed under the InlineTemplate.
string docBase64 = Convert.ToBase64String(file.Stream.ReadAsBytes());
EnvelopeDefinition envDef = New EnvelopeDefinition();
envDef.Status = "created";
// The first sequence that appears will be the first document that is applied.
// If you want the uploaded documents to appear instead of the server template,
// make the server template a higher number.
envDef.CompositeTemplates = new List<CompositeTemplate>
{
new CompositeTemplate()
{
ServerTemplates = new List<ServerTemplate>()
{
new ServerTemplate("2", templateId)
},
InlineTemplates = new List<InlineTemplate>()
{
new InlineTemplate()
{
Sequence = "1",
Recipients = new Recipients()
{
Signers = new List<Signer>()
{
new Signer()
{
Email = request.Recipients[0].UserEmail,
Name = request.Recipients[0].UserName,
RoleName = request.Recipients[0].RoleName,
RecipientId = (1).ToString()
}
}
},
Documents = new List<Document>()
{
new Document()
{
DocumentBase64 = docBase64,
DocumentId = "3",
Name = "ConsentFake.pdf",
IncludeInDownload = "true"
},
new Document
{
DocumentBase64 = docBase64,
DocumentId = "9",
Name = "ConsentFake2.pdf",
IncludeInDownload = "true"
}
}
}
}
}
};
Summary: This allowed me to take advantage of a DocuSign template's email subject/body without having to use a specific template document.
You can implement a solution for the scenario that you've described by doing the following:
Using the DocuSign web UI, create a Template for each 'type of Envelope' and specify the Email Subject and Body in each Template that you create. You'll also need to upload a document to the Template (DocuSign requires that you add at least one document before you can save the Template) -- but you can just add any placeholder doc (for example, a .txt file that contains the word placeholder file). The document you add to the template won't actually be used in the Envelopes that you create from the Template -- since you'll use the API to specify documents at runtime. (Non-technical folks will be able to use the DocuSign web UI to modify/update the email info in each Template at any point in the future.)
When sending an Envelope via the API, use the compositeTemplates structure in the "Create Envelope" API request. By using the compositeTemplates structure, you can specify the serverTemplate to use (which will pull in the Email Subject and Email Body that you've defined in that Template), specify recipient and tab info by using the inlineTemplate object, and specify document info using the document object.
(There's lots of info available here on Stack Overflow about how to use compositeTemplates in the Create Envelope request. If you have trouble getting that request structure correct/working, please post a separate question.)
I need to create a digest value for the below input
<u:Timestamp u:Id="_0" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<u:Created>2015-07-28T13:35:54Z</u:Created>
<u:Expires>2015-07-28T13:40:49Z</u:Expires>
</u:Timestamp>
so am passing the above value as a doc obj to the below code
public static void main(String[] args) throws Exception {
// Create a DOM XMLSignatureFactory that will be used to generate the
// enveloped signature
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
SecretKey signingKey = new SecretKeySpec("welcome1".getBytes(), "HMAC");
// Create a Reference to the enveloped document (in this case we are
// signing the whole document, so a URI of "" signifies that) and
// also specify the SHA1 digest algorithm and the ENVELOPED Transform.
Reference ref = fac.newReference
("#_0", fac.newDigestMethod(DigestMethod.SHA1, null),
Collections.singletonList
(fac.newTransform
("http://www.w3.org/2001/10/xml-exc-c14n#", (TransformParameterSpec) null)),
null, null);
// Create the SignedInfo
SignedInfo si = fac.newSignedInfo
(fac.newCanonicalizationMethod
(CanonicalizationMethod.EXCLUSIVE,
(C14NMethodParameterSpec) null),
fac.newSignatureMethod(SignatureMethod.HMAC_SHA1, null),
Collections.singletonList(ref));
// Instantiate the document to be signed
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document doc =
dbf.newDocumentBuilder().parse(new FileInputStream("C:/Users/Signed_Encryp/timestamp.txt"));
// Create a DOMSignContext and specify the DSA PrivateKey and
// location of the resulting XMLSignature's parent element
DOMSignContext dsc = new DOMSignContext
(signingKey, doc.getDocumentElement());
// Create the XMLSignature (but don't sign it yet)
XMLSignature signature = fac.newXMLSignature(si, null);
// Marshal, generate (and sign) the enveloped signature
signature.sign(dsc);
// output the resulting document
OutputStream os;
if (args.length > 1) {
os = new FileOutputStream(args[1]);
} else {
os = System.out;
}
TransformerFactory tf = TransformerFactory.newInstance();
Transformer trans = tf.newTransformer();
trans.transform(new DOMSource(doc), new StreamResult(os));
}
which returning me a result as
<u:Timestamp xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" u:Id="_0">
<u:Created>2015-07-28T13:35:54Z</u:Created>
<u:Expires>2015-07-28T13:40:49Z</u:Expires>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#hmac-sha1"/>
<ds:Reference URI="#_0">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<ds:DigestValue>1X/3X+yCdJc0x3n8guQnlCFvX+s=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>R4ZrHvlvyGvvQYpojZWPgUgRRSw=</ds:SignatureValue>
</ds:Signature>
</u:Timestamp>
here am not confident that digest value is created using the input am passed, also signature is created inside the timestamp tag , is there any option available to create signature tag after the timestamp tag.
Please help
To check if the signature is over the data, simply change the data and use the same key. If the HMAC value changes then the hash clearly is over the data. When changing back it should return to the old value. Note that this is true for HMAC-SHA1, it may not be the case for all primitives (e.g. RSA-PSS).
Alternatively, if you're up to it, you can perform the canonicalization yourself and manually calculate the signature. Or try another implementation.
If u:Timestamp is the root element then you cannot put anything next to it, as XML only has a single root element. You have yourself indicated that the signature is placed at doc.getDocumentElement(). You can however create a detached signature. Now you know how it is called, you should be able to search for it.
I am using Monotouch, and trying to store a NsHttpCookie object in the shared storage of my simulator, to be used for my future requests.
I am using NSHttpCookieStorage.SharedStorage.SetCookie (NSHttpCookie) method.
However, after i do this, when i try to retrieve the cookies (via NSHttpCookieStorage.SharedStorage.Cookies), i see 0 cookies.
In more detail - I am converting a system cookie to NSHttpCookie. After that, i try to write this down with
NSHttpCookieStorage.SharedStorage.AcceptPolicy = NSHttpCookieAcceptPolicy.Always;
NSHttpCookieStorage.SharedStorage.SetCookie (
convertSystemCookieToNsCookie(resp.Cookies[0]));
When i try to retrieve the cookie with
NSHttpCookie[] cookies = NSHttpCookieStorage.SharedStorage.Cookies;
Console.Out.WriteLine ("Found cookies " + cookies.Length);
It says 0 cookie.
I don't see any exceptions anywhere.
This is the convertSystemCookieToNsCookie method -
private NSHttpCookie convertSystemCookieToNsCookie(Cookie cookie) {
var properties = new NSMutableDictionary ();
properties.Add (NSHttpCookie.KeyOriginURL, new NSString(cookie.Domain));
properties.Add (NSHttpCookie.KeyName, new NSString(cookie.Name));
properties.Add (NSHttpCookie.KeyValue, new NSString(cookie.Value));
properties.Add (NSHttpCookie.KeyPath, new NSString (cookie.Path));
NSDate cookieDate = NSDate.FromTimeIntervalSinceReferenceDate ((
cookie.Expires - (new DateTime (2001, 1, 1, 0, 0, 0))).TotalSeconds);
properties.Add (NSHttpCookie.KeyExpires, cookieDate);
return new NSHttpCookie (properties);
}
Any idea what could i be doing wrong?
Thanks much!
NSHttpCookie has constructor that accept a .NET System.Net.Cookie instance that can be used to avoid code like convertSystemCookieToNsCookie.
What likely happens is that an error while creating the NSHttpCookie from an NSMutableDictionary will return a empty handle (IntPtr.Zero) and that won't be added to shared storage. The helper .ctor was added to avoid such problems.