Signature Appliance Local API C#: Detached Signatures–Signing a Data Buffer

API Recipe source files are available on GitHub.

Embedded Signatures

For many popular data formats including PDF, Word, Excel and XML files, standards exist for digitally signing the files and then including (embedding) the digital signature as part of the revised file.

The advantage of this approach is clear: the resulting “signed” file includes both the data from the original file and its digital signature. The digital signature is included in a way that does not require any changes to the original data–it must be clear which parts of the file were signed and which parts are the digital signature(s). The various US, European, International and Commercial standards for embedding digital signatures in PDF, Word, Excel and XML documents have been harmonized.

Detached Signatures

If the original data format does not support digital signatures, a detached signature can be created. A detached digital signature is stored in its own file. There is no automatic linkage of a data set and its matching detached signature as there is for embedded signatures. The recommended standard for detached signatures is the .p7b file type–A PKCS#7 digital signature structure without the data.

A detached signature is not embedded into the original data and is typically stored “side by side”, in a separate file or database field. To validate the data, both the original data and the signature file are passed to the validation system.

Using a Detached Signature

Since a detached signature can be used with any data set, it is the right answer for signing data whose format doesn’t support embedded signatures. For example, an arbitrary data structure can be serialized, and the result can be digitally signed with a detached signature.

Example: digitally signing a data buffer

This example demonstrates how to use the Local PI with C# to digitally sign a data buffer. A buffer can be any arbitrary data set. The output of the buffer signing is a detached signature.

API Recipe source files are available on GitHub.


Signing a data file to create a detached signature file

Signing

After entering your signature appliance username (for the developer’s trial, use your email) and password, use the application to select a file (of any type) from your file system.

Click the “sign” button to produce a detached digital signature. The example will prompt to save the signature as a .p7b file.

Verifying a Detached Signature

Use the “Verify Signature” tab to switch to validation mode

Verifying a Detached SignatureTo validate the signature, select both the original file and the matching .p7b signature file.

Click “Verify Signature” button. The “Signature Details” section will be filled in with the results:

The “Valid” checkbox indicates whether the file was changed since it was signed.

The other fields provide information on the signer’s identity from the signature file.

A link to the signer’s certificate (included in the p7b signature file) will also be shown.

Validating with other software

Since the p7b signature file format is specified by the digital signature standards, the signed file/buffer and its signature file can also be validated using digital signature software from other vendors.

The Code: Using a Wrapper with the Local API

This example wraps the calls to the Local API to minimize the code needed for the app’s button actions.

The wrapper includes functions for initializing SAPI and managing SAPI handles. It initializes SAPI by using a thread-safe check to prevent re-initializing SAPI. Re-initializing SAPI will cause resource leaks and can make the system unstable. See SAPIWrapper.cs in the source download.

This excerpt creates the detached signature.

//Checks if SAPI is initialized or not.
private static bool isSAPIInitialized(SAPICrypt SAPI)
{
    SESHandle hSes;
    int rc;
    if ((rc = SAPI.HandleAcquire(out hSes)) != 0)
        return false;
 
    SAPI.HandleRelease(hSes);
    return true;
}
 
//Thread-Safe SAPIInit()
private static void SAPIInit()
{
    SAPICrypt SAPI = new SAPICryptClass();
 
    lock (_SyncRoot)
    {
        //Do nothing if SAPI is initialized already
        if (isSAPIInitialized(SAPI)) return;
 
        //Initialize SAPI
        int rc = SAPI.Init();
        if (rc != 0)
        {
            throw new Exception(string.Format(
                "Failed to load SAPI library (#{0})", rc.ToString("X")));
        }
    }
}
 
 
public static void SignStream(
    string Username,   
    string Domain,     
    string Password,
    Stream DataToSign,     //The stream to read the data to be signed from
    Stream SignatureData   //The stream to write the signature data to
    )
{
 
    if (string.IsNullOrEmpty(Username)) throw new ArgumentNullException("Username");
    if (string.IsNullOrEmpty(Password)) throw new ArgumentNullException("Password");
    if (DataToSign == null) throw new ArgumentNullException("DataToSign");
    if (SignatureData == null) throw new ArgumentNullException("SignatureData");
 
    //Make sure the SAPI library is loaded into the current process
    SAPIInit();
 
    //Instantiate SAPI object
    SAPICrypt SAPI = new SAPICryptClass();
 
    SESHandle hSes = null;
    int rc = 0;
 
    if ((rc = SAPI.HandleAcquire (out hSes)) != 0)
    {
        throw new Exception(string.Format(
            "Memory allocation error (#{0})", rc.ToString("X")));
    }
 
    if ((rc = SAPI.Logon(hSes, Username, Domain, Password)) != 0)
    {
        SAPI.HandleRelease(hSes); 
        throw new Exception(string.Format(
            "Failed to authenticate the user(#{0})", rc.ToString("X")));
    }
 
    //Allocate new signing context
    SAPIContext ctxBuffSign = new SAPIContextClass();
 
    if ((rc = SAPI.BufferSignInit (hSes, ctxBuffSign, 0)) != 0)
    {
        SAPI.Logoff(hSes);
        SAPI.HandleRelease(hSes);
        throw new Exception(string.Format(
            "Failed to initialize buffer signing process(#{0})", rc.ToString("X")));
    }
 
    int remaining = (int)DataToSign.Length;
    
    //Check that the stream is not empty
    if ((int)DataToSign.Length < 1)
    {
        SAPI.Logoff(hSes);
        SAPI.HandleRelease(hSes);
        throw new Exception("Cannot sign empty stream!");
    }
 
    int chunkMaxSize = 1 << 20; //1MB
 
    //Calculate first chunk size
    int chunkSize = remaining < chunkMaxSize ?
        remaining : chunkMaxSize;
 
    while (remaining > 0)
    {
        Array chunk = new byte[chunkSize]; //Read in chunks of 1MB
        int read = DataToSign.Read((byte[])chunk, 0, chunkSize);
        if (read <= 0) throw new EndOfStreamException (String.Format("End of stream reached with {0} bytes left to read", remaining));
 
        //Build SAPI-Compatible bytes array
        SAPIByteArray tmpBuff = new SAPIByteArrayClass();
        tmpBuff.FromArray(ref chunk);
 
        //Add read buffer to the signature calculation
        if ((rc = SAPI.BufferSignCont(hSes, ctxBuffSign, tmpBuff))!= 0)
        {
            SAPI.ContextRelease (ctxBuffSign);
            SAPI.Logoff (hSes);
            SAPI.HandleRelease (hSes);
 
            throw new Exception(string.Format(
                "An error occured while calculating the digital signature (#{0})", rc.ToString("X")));
        }
 
        remaining -= read;
        chunkSize = Math.Min(remaining, chunkSize);
    }
 
    SAPIByteArray signature = new SAPIByteArrayClass();
    
    //Get the final signature
    if ((rc = SAPI.BufferSignEnd (hSes, ctxBuffSign, signature)) != 0)
    {
        SAPI.ContextRelease (ctxBuffSign);
        SAPI.Logoff (hSes);
        SAPI.HandleRelease (hSes);
 
        throw new Exception(string.Format(
            "Failed to sign the data (#{0})", rc.ToString("X")));
    }
 
    //Write signature data to the stream
    byte[] tmpSig = (byte[])signature.ToArray();
    SignatureData.Write (tmpSig, 0, tmpSig.Length);
 
    //Cleanup memory
    SAPI.ContextRelease (ctxBuffSign);
    SAPI.Logoff (hSes);
    SAPI.HandleRelease (hSes);
 
}

The Sign button action

Using the wrapper enables a succinct Sign button action. See source file Form1.cs

private void btnSign_Click(object sender, EventArgs e)
{
    // Check that the fields are filled in
    // ....    
    // Sign the file...
    try
    {
        using (Stream dataToSign = File.OpenRead(txtFile.Text), 
            Signature = new MemoryStream())
        {
 
            //Sign Stream data
            SAPIWrapper.SignStream(
                txtUsername.Text,
                txtDomain.Text,
                txtPassword.Text,
                dataToSign, Signature);
 
            //Close input stream
            dataToSign.Close();
 
            //Save signature data to file
            SaveFileDialog sfd = new SaveFileDialog();
            sfd.Filter = "Signature file (*.p7b)|*.p7b";
            sfd.Title = "Please select the file to save the signature to";
            sfd.FileName = Path.GetFileNameWithoutExtension(txtFile.Text) + ".p7b";
            if (sfd.ShowDialog() != DialogResult.OK)
            {
                Signature.Close();
                return;
            }
 
            File.WriteAllBytes(sfd.FileName,
                (Signature as MemoryStream).ToArray());
 
            txtSignatureFile.Text = sfd.FileName;
 
            Signature.Close();
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
        return;
    }
    MessageBox.Show("The data was signed successfully");
}