I have an InfoPath 2010 form template that I have deployed to SharePoint. The form collects four signatures, each signature is setup as independent to the others and I have my own checks coded into the form's sign event. Below is the code in my sign event handler:
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
public void FormEvents_Sign(object sender, SignEventArgs e)
{
if (!checkSignatures()) // FUNCTION I WROTE TO TRY TO CATCH THE INVALID SIGNATURE
{
MessageBoxes.ShowInvalidCertificate("Invalid Certificate", "One of the previous signatures is invalid. You will be unable to sign and submit this form until a valid certificate is used.", string.Empty, System.Drawing.SystemIcons.Error);e.SignatureWizard = false;
}
else
{
try
{
_invalidSignature = false;
string curUser = System.Environment.UserName.ToString();int isRejected = (int)CreateNavigator().SelectSingleNode("/my:AdjustmentRequest/my:WorkflowFields/my:IsRejected", NamespaceManager).ValueAsInt;
string sectionSigned = e.SignedDataBlock.Name.ToString();string myField = sectionSigned + "_Name";
string path = "/my:AdjustmentRequest/my:ApprovalSignatures/my:" + sectionSigned.ToString() + "/my:" + myField.ToString();XPathNavigator myfield = MainDataSource.CreateNavigator().SelectSingleNode(path.ToString(), this.NamespaceManager);
string signer = ActiveDirectory.GetUserProperty(LDAP://blablabla, "displayname", myfield.Value.ToString(), "samaccountname");bool isPreviousSigned = isPreviousSectionSigned(sectionSigned);
// CHECK IF CURRENT USER IS THE USER ASSIGNED TO SIGN THIS SECTIONif (curUser == signer)
{
// CHECK IF USER IS TRYING TO SIGN AFTER THE FORM HAS ALREADY BEEN REJECTEDif (isRejected.ToString() == "1")
{
MessageBox.Show("This form has already been rejected and can no longer be signed.");e.SignatureWizard = false;
}
else // SIGNATURE CODE
{
if (isPreviousSigned)
{
// SIGN SECTIONe.SignatureWizard = false;Microsoft.Office.InfoPath.Signature thisSignature = e.SignedDataBlock.Signatures.CreateSignature();
thisSignature.Sign();
if (!checkSignatures())
{
_invalidSignature = true;
MessageBoxes.ShowInvalidCertificate("Invalid Signature", "You are unable to sign this form due to an invalid signature or signing certificate.", "Please follow the link above to obtain a valid certificate. This form will be closed without saving. You will need to re-open to sign this document once you have obtained a valid certificate.", System.Drawing.SystemIcons.Error);this.Application.Quit();
}
}
else
{
MessageBox.Show("You cannot sign this form out of sequence.");e.SignatureWizard = false;
}
}
}
else
{
// NOTIFY USERMessageBox.Show("You are not the user assigned to sign this section.");e.SignatureWizard = false;
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message.ToString() + " " + ex.StackTrace.ToString() + ", " + ex.InnerException.Message.ToString());
}
}
}
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
I wrote this checkSignatures function to try and catch the invalid signature before Infopath does when it runs its own validation. It is the best I could do with what seems like very little documentation of actual working code samples online.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
public bool checkSignatures()
{
// CREATE NEW XML DOCUMENTXmlDocument xmlDoc = new XmlDocument();
xmlDoc.PreserveWhitespace =
true;
// LOAD THE XML DOCUMENTxmlDoc.LoadXml(this.MainDataSource.CreateNavigator().SelectSingleNode("/my:AdjustmentRequest", this.NamespaceManager).OuterXml.ToString());
// GET SIGNATURE LIST OFF OF FORMXmlNodeList sigList = xmlDoc.GetElementsByTagName("Signature");
//MessageBox.Show(sigList.Count.ToString() + " signatures on the form.");
// CREATE RETURN VARIABLEbool validSignature = true;
for (int i = 0; i < sigList.Count; i++) // LOOP THROUGH ALL SIGNATURES
{
// Create new SignedXML object.System.Security.Cryptography.Xml.SignedXml signedXML = new System.Security.Cryptography.Xml.SignedXml(xmlDoc);
// LOAD CURRENT SIGNATURE INTO THE SIGNED XML OBJECTsignedXML.LoadXml((XmlElement)sigList[i]);
bool hasKeyInfo = false;
// Get Key Info DataXmlNodeList sigNodes = sigList[i].ChildNodes;
XmlNodeList keyInfoNodes = null;foreach (XmlNode node in sigNodes)
{
if (node.Name.ToString() == "KeyInfo")
{
hasKeyInfo = true;
// GET SIGNATURE INFORMATION
keyInfoNodes = node.ChildNodes;
// GET CERTIFICATE INFORMATIONSystem.Security.Cryptography.X509Certificates.X509Certificate2 cert = null;
XmlElement X509Certificate = (XmlElement)keyInfoNodes[0].FirstChild;
// CREATE CERTIFICATE OBJECT FROM CERTIFICATE INFORMATIONcert = DigitalSignatures.CreateX509CertificateFromXMLElement(X509Certificate);
// UPDATE SIGNED XML OBJECT TO USE THE CERTIFICATES PUBLIC KEY
signedXML.SigningKey = cert.PublicKey.Key;
// CHECK SIGNATUREbool result = signedXML.CheckSignature(cert, false);
validSignature = result;
}
}
if (!hasKeyInfo)
{
// GET SIGNATURE INFORMATIONXPathNavigator sigNavigator = sigList[i].CreateNavigator();
sigNavigator.MoveToAttribute(
"Id", "");string signatureBlockName = sigNavigator.Value.ToString();MessageBox.Show("The following signature for: " + signatureBlockName.ToString() + " does not contain any KeyInfo");
}
}
// RETURN STATUS OF SIGNATUREreturn validSignature;
}
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
ISSUE: Ok so what is happening is that when the form is opened for the first time from SharePoint (the form is opened in the client application - it is not a web form) it submits successfully and there is no problem. The first signer goes to open it and sign, and the code executes and validates the signature and says that it is valid - user submits the form. When the form is opened by the second signer on their computer the signature that was showing as valid on the first user's computer is now showing as invalid but the controls that have been signed are locked and no changes have been made.
I also call the checkSignatures in the Form Load event because I wanted to see if I could capture the same behavior in my code that InfoPath is showing but I can't. Now what is even more confusing to me is that my code above will run and validate the signature ok that InfoPath is now showing as invalid. So this 2nd signer can sign the form and submit it ok, and if the same form is re-opened (now it has two signatures) on the first signer's computer, both signatures show as valid. Re-open it on the second signer's computer, and one of them is invalid. I can open it on the SharePoint server and it shows both signatures as valid.
So what I have done so far:
- Verified that our internal CA is issuing the proper certificates
- Verified that each signer has the same type of digital certificate
- Checked that each user has the CA listed as a trusted root authority
So why would a signature show as valid when the form is opened on one computer but invalid when opened on another? Am I missing some setup step on one of these computers?