Signature Appliance Local API, C: Sign Existing and New PDF Signature Fields

This recipe signs a PDF using a set name and password. The function includes many parameters to control the signing process.

API Recipe source files are available on GitHub.

The signatureFieldName is key. If null, then the function will create a new signature field in the document and sign it. If not null, then the PDF will be searched for an existing digital signature field of that name, and the field will be signed.

Since digital signatures are an open standard, a signature field can be created by any vendor’s software and then signed with the DocuSign Signature Appliance.

If a new signature field is created, then the graphical signature block will have a specific size and placement on the PDF’s first page.

SAPI_sign_file

The main method of the example is SAPI_sign_file. The method is thread-safe and can be called by multiple threads simultaneously.

public static void SAPI_sign_file(string FileName, string FieldName, string User,
  string Password, string SignPassword, int page, int x, int y, int height, int width,
  bool Invisible, string Reason, int AppearanceMask, string NewFieldName, string GraphImgName)

Function Parameters

Parameter Meaning
FileName PDF file name
FieldName If present, the name of an existing digital signature field in the PDF. If null, then a new signature field with name NewFieldName will be created using parameters page, x, y, height, width, Invisible. If FieldName is present, then those parameters will be ignored
User User name for authenticating with the appliance.
Password The user’s password
page PDF page number for a new signature field
x x co-ordinate for a new field.
y y co-ordinate for a new field.
height height for a new field.
width width for a new field.
Invisible If true, then the signature will not include a graphical representation of the digital signature
Reason Optional “Signing Reason” included with the signature.
AppearanceMask Appearance Mask specifying which components (Date, Signer’s Title, Reason) will be included with the graphical representation of the digital signature.
NewFieldName The name for the new signature field if an existing field was not used.
GraphImgName The name of the user’s graphical signature image within the signature appliance. Eg, a user may have one or more signature images, including signature images that include engineering seals from different jurisdictions.

Instantiate and Init SAPI

The Class responsible for the actual signing process is FileSign. It implements a static constructor to make sure SAPI is always initialized:

static FileSign()
{
  SAPICrypt SAPI = new SAPICryptClass();
  int rc = SAPI.Init(); //SAPI.Init() should be called once per process
  if (rc != 0) throw new Exception("Failed to initialize SAPI! (" + rc.ToString("X") + ")");
}

HandleAcquire and HandleRelease

HandleAcquire must be called before any other function that uses the SAPI session handle. HandleAcquire begins a session that is terminated only by calling HandleRelease with the session handle as a parameter.

An application can have more than one SAPI session handle opened with SAPI, and is usually used by server applications that serve multiple users in parallel.

A typical single threaded SAPI application starts by calling Init followed by HandleAcquire. It ends by callingHandleRelease followed by Finalize.

if ((rc = SAPI.HandleAcquire(out SesHandle)) != 0) 
   throw new Exception("Failed in SAPIHandleAcquire() with rc = " + rc.ToString("X"));

Logon

The Logon function performs a login to the appliance using the provided credentials. This function is called in either of the following two scenarios:

  • In an environment in which the signature appliance automatically displays a login dialog for a new session, but the application wants to avoid this popup, either because it has its own popup or because the operation is not interactive.
  • When the application runs under certain credentials but wants to login to the appliance under different credentials. For example, a web server application that serves many different users.
//Logon
if ((rc = SAPI.Logon(SesHandle, User, null, Password)) != 0)
{
   SAPI.HandleRelease(SesHandle);
   throw new Exception("Failed to authenticate user with rc = " + rc.ToString("X"));
}

Searching for a graphical image by name

The GraphicSigImageEnumInit function initializes the graphical images enumeration operation. This operation is used in environments where there may be more than one available image. A call to the GraphicSigImageEnumInitfunction is followed by calls to GraphicSigImageEnumCont function until all the images are retrieved, or until the required graphical image object is found. To retrieve more information about a specific graphical image, call theGraphicSigImageInfoGet function.

SAPIContext ctxGraphImg = new SAPIContextClass();
//Start Graphical Images Enumeration
if ((rc = SAPI.GraphicSigImageEnumInit(SesHandle, ctxGraphImg, AR_GR_SIG_DATA_FORMAT_ALL, 0)) != 0)
{
    SAPI.Logoff(SesHandle);
    SAPI.HandleRelease(SesHandle);
    throw new Exception("Failed to enumerate graphical signatures with rc = " + rc.ToString());
}
 
GraphicImageHandle hGrImg = null;
bool isFound = false;
while (((uint)(rc = SAPI.GraphicSigImageEnumCont(SesHandle, ctxGraphImg, out hGrImg))) != SAPI_ERROR_NO_MORE_ITEMS)
{
    if (rc != 0)
    {
        SAPI.ContextRelease(ctxGraphImg);
        SAPI.Logoff(SesHandle);
        SAPI.HandleRelease(SesHandle);
        throw new Exception("Failed to retrieve next graphical signature with rc = " + rc.ToString());
    }
 
    //Get Graphical Signature Info
    GraphicImageInfo giInfo = null;
    if ((rc = SAPI.GraphicSigImageInfoGet(SesHandle, hGrImg, out giInfo,
        SAPI_ENUM_GRAPHIC_IMAGE_FORMAT.SAPI_ENUM_GRAPHIC_IMAGE_NONE, 0)) != 0)
    {
        SAPI.ContextRelease(ctxGraphImg);
        SAPI.HandleRelease(hGrImg);
        SAPI.Logoff(SesHandle);
        SAPI.HandleRelease(SesHandle);
        throw new Exception("Failed to retrieve graphical signature info with rc = " + rc.ToString());
    }
 
    //Check if required Graphical image has been found
    if (giInfo.Name.Trim().ToLower() == GraphImgName.Trim().ToLower())
    {
 
        //If found - set is as default Graph. Image
        if ((rc = SAPI.GraphicSigImageSetDefault(SesHandle, hGrImg)) != 0)
        {
            SAPI.ContextRelease(ctxGraphImg);
            SAPI.HandleRelease(hGrImg);
            SAPI.Logoff(SesHandle);
            SAPI.HandleRelease(SesHandle);
            throw new Exception("Failed to define default graphical signature with rc = " + rc.ToString());
        }
 
        isFound = true;
        SAPI.ContextRelease(ctxGraphImg);
        SAPI.HandleRelease(hGrImg);
        break;
    }
 
    SAPI.HandleRelease(hGrImg);
}
 
SAPI.ContextRelease(ctxGraphImg);

Creating a new signature field

The SignatureFieldCreate function creates a single signature field in the specified file. The signature field is created with attributes according to the value of NewSignatureFieldStruct. The function optionally returns a handle to the newly created signature field.

//Create new signature field
if (FieldName == null)
{
    SigFieldSettingsClass SFS = new SigFieldSettingsClass();
    TimeFormatClass TF = new TimeFormatClass();
    int Flags = 0;
 
    //Define name of the new signature field
    if (NewFieldName.Length > 0)
    {
        SFS.Name = NewFieldName;
        Flags |= AR_PDF_FLAG_FIELD_NAME_SET;
    }
 
if (Invisible)
    {
        SFS.Invisible = 1;
        SFS.Page = -1;
    }
    else
    {
        // VISIBLE:
        SFS.Invisible = 0;
        // location:
        SFS.Page = page;
        SFS.X = x;
        SFS.Y = y;
        SFS.Height = height;
        SFS.Width = width;
        // appearance:
        SFS.AppearanceMask = AppearanceMask;
        SFS.LabelsMask = AppearanceMask;  
        SFS.DependencyMode = SAPI_ENUM_DEPENDENCY_MODE.SAPI_ENUM_DEPENDENCY_MODE_INDEPENDENT;
        SFS.SignatureType = SAPI_ENUM_SIGNATURE_TYPE.SAPI_ENUM_SIGNATURE_DIGITAL;
        SFS.Flags = 0;
        // time:
        TF.DateFormat = "dd MMM yyyy";
        TF.TimeFormat = "hh:mm:ss";
        TF.ExtTimeFormat = SAPI_ENUM_EXTENDED_TIME_FORMAT.SAPI_ENUM_EXTENDED_TIME_FORMAT_NONE;
        SFS.TimeFormat = TF;
    }
 
     //Create the Field
    if ((rc = SAPI.SignatureFieldCreate(SesHandle, SAPI_ENUM_FILE_TYPE.SAPI_ENUM_FILE_ADOBE,
        FileName, SFS, Flags, out sf)) != 0)
    {
        SAPI.Logoff(SesHandle);
        SAPI.HandleRelease(SesHandle);
        throw new Exception("Failed to create new signature field with rc = " + rc.ToString("X"));
    }
}

Using an existing signature field

The SignatureFieldEnumInit function initializes the continuous operation of retrieving all the signature fields in a file. The SignatureFieldEnumCont function continues the Signature Field Enumeration operation and returns a pointer to a single signature field handle. Every call returns a new Signature Field handle until the end of the list is reached.

The SAPISignatureFieldInfoGet function returns all the information related to a specific signature field. The information is returned in two structures:

  • SignatureFieldSettingStruct holds data related to the non-cryptographic information, such as appearance.
  • SignedFieldInfoStruct holds crypto data for signed fields.
//Find an existing signature field by name
else
{
   SAPIContext ctxField = new SAPIContextClass();
    int NumOfFields = 0;
 
    //Initiate the Signature Fields enumeration process
    if ((rc = SAPI.SignatureFieldEnumInit(SesHandle, ctxField, SAPI_ENUM_FILE_TYPE.SAPI_ENUM_FILE_ADOBE,
        FileName, 0, ref NumOfFields)) != 0)
    {
        SAPI.Logoff(SesHandle);
        SAPI.HandleRelease(SesHandle);
        throw new Exception("Failed to start signature field enumeration with rc = " + rc.ToString("X"));
    }
 
    bool isFound = false;
    for (int i = 0; i < NumOfFields; i++)
    {
        //Get Next field's handle
        if ((rc = SAPI.SignatureFieldEnumCont(SesHandle, ctxField, out sf)) != 0)
        {
            SAPI.ContextRelease(ctxField);
            SAPI.Logoff(SesHandle);
            SAPI.HandleRelease(SesHandle);
            throw new Exception("Failed in signature fields enumeration with rc = " + rc.ToString("X"));
        }
 
        //Retrieve Signature Field's info
        SigFieldSettings sfs = new SigFieldSettingsClass();
        SigFieldInfo sfi = new SigFieldInfoClass();
        if ((rc = SAPI.SignatureFieldInfoGet(SesHandle, sf, sfs, sfi)) != 0)
        {
            SAPI.HandleRelease(sf); 
            SAPI.ContextRelease(ctxField);
            SAPI.Logoff(SesHandle);
            SAPI.HandleRelease(SesHandle);
            throw new Exception("Failed to retrieve signature field details with rc = " + rc.ToString("X"));
        }
 
        //Check that the field we've found is not signed. If Signed - just skip it.
        if (sfi.IsSigned != 0) continue;
 
        if (sfs.Name == FieldName)
        {
            SAPI.ContextRelease(ctxField);
            isFound = true;
            break;
        }
 
        //Release handle of irrelevant signature field
        SAPI.HandleRelease(sf);
    }
 
    if (!isFound)
    {
        SAPI.ContextRelease(ctxField);
        SAPI.Logoff(SesHandle);
        SAPI.HandleRelease(SesHandle);
        throw new Exception("The file doesn't contain any signature field named: " + FieldName);
    }
}

Defining a Reason for Signing

The ConfigurationValueSet function sets one of the configuration values that are defined inSAPI_ENUM_CONF_ID. All subsequent calls with the same SAPI session handle are influenced by the new value set.

if ((rc = SAPI.ConfigurationValueSet(SesHandle, SAPI_ENUM_CONF_ID.SAPI_ENUM_CONF_ID_REASON,
    SAPI_ENUM_DATA_TYPE.SAPI_ENUM_DATA_TYPE_STR, Reason, 1)) != 0)
{
    SAPI.HandleRelease(sf);
    SAPI.Logoff(SesHandle);
    SAPI.HandleRelease(SesHandle);
    throw new Exception("Failed in SAPIConfigurationValueSet with rc = " + rc.ToString("X"));
}

Signing

The SignatureFieldSign function signs a specific signature field in a specific file. The file name and type, as well as the attributes for the signing operation, are all derived from the SignatureFieldHandle. When prompt for sign feature is enabled, SignatureFieldSignEx is used instead.

if (string.IsNullOrEmpty(SignPassword))
{
    //Prompt-for-sign mode is disabled
    rc = SAPI.SignatureFieldSign(SesHandle, sf, 0);
}
else
{
    //Prompt-for-sign mode is enabled
    rc = SAPI.SignatureFieldSignEx(SesHandle, sf, 0, SignPassword);
}
if (string.IsNullOrEmpty(SignPassword))
{
    //Prompt-for-sign mode is disabled
    rc = SAPI.SignatureFieldSign(SesHandle, sf, 0);
}
else
{
    //Prompt-for-sign mode is enabled
    rc = SAPI.SignatureFieldSignEx(SesHandle, sf, 0, SignPassword);
}

Cleaning Up

SAPI.Logoff(SesHandle);
SAPI.HandleRelease(sf);
SAPI.HandleRelease(SesHandle);