Discuss this help topic in SecureBlackbox Forum

DC add-on: Distributed signing, overview and general guide

DC (for 'Distributed Crypto') is a SecureBlackbox subsystem which provides means of executing the cryptographic signing operations remotely. The primary goal of DC is to allow signing of documents residing on one computer with private keys residing on another without the need to transfer either of them across the network.

Design-wise, DC works either as an add-on to higher-level protocol implementations offered by SecureBlackbox, such as CAdES, PDF or XML signatures, or as a standalone component set which can be integrated into a custom signing protocol.

DC can be used in a variety of scenarios. While it is nearly impossible to categorize all possible use cases, the following three are the most typical:

In-browser document signing is often employed where there is a web server that needs to get some form of authorization from clients residing in a variety of remote locations. A common scenario involves users signing PDF documents generated on the web server with certificates residing in their local system stores or on hardware cryptographic devices. DC addresses this scenario with a Java applet capable of performing the signing operation in-browser, along with a server-side ASP.NET module.

The model of dedicated signing servers is gaining increased popularity in corporate environments seeking to improve their security characteristics. The model assumes that all sensitive cryptographic operations within the organization are delegated to a few highly protected servers, which also implement robust access control and journaling features. DC can come handy with building such kind of environments, as it makes it possible to move any sensitive signing operations involving private keys to a protected dedicated box, while leaving the higher-level signing logic on generic workstations.

Password-based user authentication is often not secure enough to consider it an adequate protection for sensitive assets. DC offers an option to extend or replace password authentication with strong asymmetric authentication scheme.

DC subsystem consists of two logical elements: the client module and the server modules. The terms 'client' and 'server' are viewed from the DC perspective here, with the client being a party willing to create a signature, and the server being responsible for actual signing.

The client module is organically integrated into higher-level SecureBlackbox components, such as those implementing CMS (TElSignedCMSMessage), PDF (TElPDFPublicKeySecurityHandler) and XML (TElXMLSigner) signatures. The client module is responsible for two functions: pre-signing the document by calculating its hash and creating a skeleton of the future signature, and completing the signing process by insertion of the signature value returned by the server module to the pre-signed document.

The server modules, which are implemented for different platforms and technologies, are responsible for actually signing the hash obtained from the client module with the private key at their disposal. The modules are designed to be implemented as thin clients, only relying on the capabilities of platforms they are supposed to run on (and not dependant on SecureBlackbox), which allows to implement them as native software modules for any particular platform.

The whole DC protocol is designed to work in asynchronous way. This means that the client DC module doesn't have to stop in the middle of the signing operation waiting for the server module to complete the operation. Instead, the client module pre-signs the data and terminates, returning the pre-signed document and a 'signature request' which is then passed to the server module. Once the signature is received back from the server, the client module reincarnates and finalizes the signing operation by inserting the signature into the pre-signed document.

Security-wise, DC provides built-in protection against common types of attacks, such as man-in-the-middle and replay attacks. It also provides support for authenticated requests, allowing the server to make sure that the request originates from a legitimate client.

Following the modular design, a typical application consuming DC consists of the following logical parts. While the exact interface exported by a particular client component may be subject to its own specifics (which is caused by differences in the higher-level protocols), the procedures are very similar for all supported client components. The sample code below is based on CMS signing components, yet the same approach will be used for PDF, XML and Office document signing.

On the first step, we create a pre-signed document (which is essentially a skeleton of the signed document with a signature placeholder) and a signature request (called 'async state') to be passed to the DC server:

C#:


// Creating a variable for async state.
TElDCAsyncState requestState = null;

// Creating a CMS message object.
TElSignedCMSMessage cms = new TElSignedCMSMessage();
try
{
    // Initializing the CMS object with data to be signed.
    cms.CreateNew(data, 0, data.Length);

    // Adding and configuring a signature object.
    TElCMSSignature sig = cms.get_Signatures(cms.AddSignature());
    sig.DigestAlgorithm = SBConstants.Unit.SB_ALGORITHM_DGST_SHA256;
    sig.FingerprintAlgorithm = sig.DigestAlgorithm;

    // Initiating the distributed signing operation.
    // Note: cert does not need to have an associated private key.
    sig.InitiateAsyncSign(cert, certStorage, ref requestState);

    // Saving the pre-signed skeleton to a local file.
    FileStream preSignedStream = new FileStream("presigned.p7b",
        FileMode.Create);
    try
    {
        cms.Save(preSignedStream);
    }
    finally
    {
        preSignedStream.Close();
    }
}
finally
{
    // Disposing of the CMS object, we don't need it any more.
    cms.Dispose();
}

// Saving the async request to stream and sending it to DC server.
MemoryStream requestStream = new MemoryStream();
requestState.SaveToStream(requestStream, SBDCXMLEnc.Unit.DCXMLEncoding());

// We use DCXMLEncoding() object here to encode the request in XML format,
// which is widely supported and is processible on the majority of platforms.

The content of requestStream, which includes the hash of the original document, can now be sent to the DC server module, which will sign the hash with its private key and compose the response state. The server module may reside in an environment totally different to the client one, and any desired means of transport can be used to transmit the request state to it. You can use one of the server objects provided with SecureBlackbox (such as the Java applet, the ActiveX control, or the 'main' SecureBlackbox components themselves), or you can implement your own. The code below illustrates the use of SecureBlackbox-based TElDCStandardServer component to build the server module:

C#:


// Creating the main DC server component.
TElDCStandardServer server = new TElDCStandardServer();
try
{
    // Creating a signature handler. Different signature handlers
    // serve different types of signatures within the same server.
    // X.509 handler is capable of creating PKCS#1-compatible signatures
    // with RSA private keys bound to X.509 certificates.
    TElDCX509SignOperationHandler handler = new TElDCX509SignOperationHandler();
    try
    {
        // Configuring the handler. CertStorage is expected to contain
        // the signing certificate (with the associated private key),
        // plus an optional chain.
        handler.CertStorage = certStorage;

        // Registering the handler with the server.
        server.AddOperationHandler(handler);

        // Processing the signature request. Using two DCXMLEncoding()
        // parameters to indicate that XML encoding should be used to
        // both decode the request and encode the response.
        server.Process(requestStream, responseStream,
            SBDCXMLEnc.Unit.DCXMLEncoding(),
            SBDCXMLEnc.Unit.DCXMLEncoding()
        );

        // responseStream contains the signature over the submitted hash,
        // which can now be sent back to the DC client.
    }
    finally
    {
        handler.Dispose();
    }
}
finally
{
    server.Dispose();
}

Having received the contents of responseStream, the DC client can complete the signing process:

C#:


// Loading the response state into a TElDCAsyncState object.
TElDCAsyncState responseState = new TElDCAsyncState();
responseState.LoadFromStream(responseStream,
    SBDCXMLEnc.Unit.DCXMLEncoding();

// Creating another TElSignedCMSMessage object to complete
// the signing process.
TElSignedCMSMessage cms = new TElSignedCMSMessage();
try
{
    // Loading the pre-signed document created on first step.
    FileStream preSignedStream = new FileStream("presigned.p7b",
        FileMode.Open);
    try
    {
        cms.Open(preSignedStream, null);

        // Finalizing the signature, passing the returned
        // response state to the CompleteAsyncSign() method
        // of the pre-created signature object.
        cms.get_Signatures(0).CompleteAsyncSign(responseState);

        // Saving the signed document.
        FileStream signedStream = new FileStream("signed.p7b",
            FileMode.Create);
        try
        {
            cms.Save(signedStream);
        }
        finally
        {
            signedStream.Close();
        }
    }
    finally
    {
        preSignedStream.Close();
    }
}
finally
{
    cms.Dispose();
}

That's pretty much it. The sample above is fairly trivial and only illustrates the basics of DC components, keeping aside more sophisticated aspects, such as request authentication and parameterized requests. Please use other articles in this section to find out more about features offered by DC subsystem.

Using parameterized DC requests

Often DC clients face the need to pass parameters to the DC server together with their signature requests. A few typical examples include some identifier of the document being signed, an ID of the private key that should be employed, or an URL to which the resulting signature should be submitted by the DC server. While the parameters can be successfully passed outside of the DC scheme (e.g. as a separate parameter to the HTML applet object), passing them within the DC request offers several benefits. The most important benefit is that the parameters passed within the DC request are covered by the request signature, if DC request authentication is used, which protects them from being forged by an adversary.

To provide your parameters on the pre-signing stage, pass a customized TElDCParameters object to an InitiateAsyncSign() / InitiateAsyncOperation() overload that accepts a TElDCParameters parameter (called Pars in most cases). Custom parameters are provided via TElDCParameters.Parameters object, which takes them as (ID, value) byte array pairs. Note that if you used to provide the AsyncSignMethod parameter explicitly in your existing code, you will now need to assign it to TElDCParameters.AsyncSignMethod property.

C#:


TElDCParameters pars = new TElDCParameters();

// Use AsyncSignMethod property to adjust the signing method.
pars.AsyncSignMethod = TSBDCAsyncSignMethod.asmPKCS7;

// Parameters are provided as <name, value> byte array pairs,
// so you need to convert any string parameters to byte arrays.
pars.Parameters.Add(Encoding.UTF8.GetBytes("URL"),
    Encoding.UTF8.GetBytes(https://dc.server.com));
pars.Parameters.Add(Encoding.UTF8.GetBytes("ID"),
    Encoding.UTF8.GetBytes("13298310"));

// Pass your TElDCParameters object to the appropriate
// InitiateAsyncSign() overload.
sig.InitiateAsyncSign(cert, certStorage, pars, ref requestState);

DC server side supports two different approaches to parameter processing. Which one to use mainly depends on what stage of the request processing routine you need to use the provided parameters, and to what development paradigm you are sticking.

The first approach is to use two-step request processing. In this case you use TElDCStandardServer's BeginProcess() and EndProcess() methods to handle the request. The parameters can be read after the BeginProcess() call returns, which allows you to configure the server component in accordance with the parameter values before performing the actual processing in EndProcess():

C#:


TElDCStandardServer server = new TElDCStandardServer();

// Initiating request processing.
TElDCServerRequest req = server.BeginProcess(requestStream,
    SBDCXMLEnc.Unit.DCXMLEncoding());

// Extracting parameters
string id = "";
string url = "";
for (int i = 0; i < req.Parameters.Count; i++)
{
    if (Encoding.UTF8.GetString(req.Parameters.get_OIDs(i)) == "")
    {
        id = Encoding.UTF8.GetString(req.Parameters.get_Values(i));
    }
    else if (Encoding.UTF8.GetString(req.Parameters.get_OIDs(i)) == "")
    {
        url = Encoding.UTF8.GetString(req.Parameters.get_Values(i));
    }
}

// Creating and configuring the operation handler.
TElDCX509SignOperationHandler handler = new TElDCX509SignOperationHandler();
handler.CertStorage = certStorage;

// Registering the handler.
server.AddOperationHandler(handler);

// Signing the received digest.
server.EndProcess(req, responseStream, SBDCXMLEnc.Unit.DCXMLEncoding());

The second approach is to use synchronous Process() function and capture the parameters via the server's OnParametersReceived event. You can create and register your operation handlers before calling Process(), or from within the event handler.

C#:


TElDCStandardServer server = new TElDCStandardServer();

server.OnParametersReceived += new TSBDCServerParametersReceivedEvent(server_OnParametersReceived);

server.Process(requestStream, responseStream, SBDCXMLEnc.Unit.DCXMLEncoding(), SBDCXMLEnc.Unit.DCXMLEncoding());

...

void server_OnParametersReceived(object sender, SBRDN.TelRelativeDistinguishedName pars)
{
    // Extracting parameters
    string id = "";
    string url = "";
    for (int i = 0; i < req.Parameters.Count; i++)
    {
        if (Encoding.UTF8.GetString(req.Parameters.get_OIDs(i)) == "")
        {
            id = Encoding.UTF8.GetString(req.Parameters.get_Values(i));
        }
        else if (Encoding.UTF8.GetString(req.Parameters.get_OIDs(i)) == "")
        {
            url = Encoding.UTF8.GetString(req.Parameters.get_Values(i));
        }
    }

    // Creating and configuring the operation handler.
    TElDCX509SignOperationHandler handler = new TElDCX509SignOperationHandler();
    handler.CertStorage = certStorage;

    // Registering the handler.
    ((TElDCStandardServer)sender).AddOperationHandler(handler);
}

Using authenticated requests

DC is capable of including authentication records to DC signature requests to provide authenticity of the data included in the request. While request authentication is an optional feature, we highly recommend activating it in your DC-powered environment if no other request origin and integrity checks are performed.

Those our customers who use DC in a web environment by sending signature requests together with the DC Java applet for in-browser signing by clients, please note that HTTPS authentication normally doesn't replace DC request authentication. This is because HTTPS only authenticates the origin of the Java applet. If an attacker manages to embed your legitimate DC applet into their fake web page along with a fabricated Data parameter (containing a forged signature request), they will be able to force the clients into creating valid signatures over forged data. As the applet will in this case be taken from its original location with no modifications, HTTPS won't help to detect the breach, and all the communications would look genuine.

DC authentication record is essentially a digital signature made by the DC client component (e.g. TElCMSSignature) over the signature requests it creates. The signature is made with a dedicated authentication certificate. The private key of the certificate resides on the DC client (e.g. a web server), and the public certificate is distributed to prospective DC servers beforehand. Once a DC server receives an authenticated signature request from the client, it verifies its signature with its copy of the authentication certificate and decides whether to go ahead with the request.

The use of X.509 certificates for request authentication allows easy integration of flexible key management procedures into a DC-driven scheme via the means provided by standard PKI infrastructure.

DC supports two request authentication schemes, PKCS#1 and PKCS#7/CMS (these should not be confused with DC signature methods; while the same security technologies are used for signing and request authentication, they are used independently of each other - so you can easily use PKCS#7-based signing requests together with PKCS#1-based request authentication mechanism and the other way round). PKCS#7-based scheme requires a PKIBlackbox license to be available.

To enable request authentication on DC client side, use the following code. These settings should be applied before calling the InitiateAsyncSign()/InitiateAsyncOperation() routine.

C#:


// Creating an authentication handler object. The following handlers
// are available in SecureBlackbox 15:
// - TElDCClientPKCS1RequestSignatureHandler
// - TElDCClientCMSRequestSignatureHandler
TElDCClientRequestSignatureHandler authHandler = new TElDCClientPKCS1RequestSignatureHandler();

// Loading the authentication certificate.
// The availability of the private key is required.
TElX509Certificate authCert = new TElX509Certificate();
int r = authCert.LoadFromFileAuto("authcert.pfx", "password");
if (r != 0)
{
    raise Exception.Create("Failed to load certificate");
}

// Adding the authentication certificate to the storage.
// Certificates comprising the chain can also be added.
TElMemoryCertStorage authCertStorage = new TElMemoryCertStorage();
authCertStorage.Add(authCert, true);

// Configuring request authentication settings:

// - enabling request authentication
SBDCDef.Unit. DefaultDCRequestFactory().DefaultParameters.SecurityParameters.UseAuthenticatedRequests = true;

// - providing security handler to use for request signing
SBDCDef.Unit. DefaultDCRequestFactory().DefaultParameters.SecurityParameters.SignatureHandler = authHandler;

// - adjusting hash algorithm to apply
SBDCDef.Unit. DefaultDCRequestFactory().DefaultParameters.SecurityParameters.HashAlgorithm = SBConstants.Unit.SB_ALGORITHM_DGST_SHA512;

// - telling to include the signing chain to the signature
SBDCDef.Unit. DefaultDCRequestFactory().DefaultParameters.SecurityParameters.IncludeCertificates = true;

// - providing the signing certificate (and optionally its chain)
SBDCDef.Unit. DefaultDCRequestFactory().DefaultParameters.SecurityParameters.CertStorage = authCertStorage;

That's it. Any signature request created after execution of the above code will be signed with the provided certificate. The settings are global and can only be provided once.

Now let's proceed to the DC server side. By default, all server-side DC components attempt to validate incoming authenticated requests automatically. In some cases this implies the need for the server to be properly configured in order to be able to validate signed requests properly. The configuration mainly concerns the tune-up of authentication certificate validation routine, as the integrity of request signatures is validated by the components automatically.

If you are using 'main' SecureBlackbox components with your DC server implementation, you will need to configure your TElDCStandardServer in the following way to make it work with authenticated requests:

C#:


// Creating an authentication handler object. The handler is responsible
// for processing of authentication signatures of a particular type.
// The type of the handler should match the one used on the DC client side.
TElDCServerPKCS1RequestSignatureHandler authHandler = new TElDCServerPKCS1RequestSignatureHandler();

// Handling the OnCertificateValidate event, which will be invoked
// when the handler needs the user to validate the authentication certificate.
authHandler.OnCertificateValidate += handler_OnCertificateValidate;

// Creating the server object.
TElDCStandardServer server = new TElDCStandardServer();

// Telling the server that we require all the incoming requests
// to be signed. Unsigned requests will not be processed,
// and the exception will be thrown.
Server.SecurityParameters.RequireSignature = true;

// Registering the handler with the DC server.
// Note that you can register as many handlers as you want,
// allowing your server to process requests signed
// with different authentication methods.
Server. AddRequestSignatureHandler(authHandler);


// Configuring the server for signing.
...

// Processing the request.
Server.Process(...)

...

// Implementing the certificate validation event. The event is invoked
// when the handler needs the user to establish trust to
// the certificate that signed the signature request.
void handler_OnCertificateValidate(object sender, TElX509Certificate certificate, TElMemoryCertStorage otherCertificates, ref TSBBoolean valid)
{
    // Performing proper validation of the signing certificate,
    // e.g. by validating its chain, or by looking for it
    // in the list of explicitly trusted certificates
    // ...
    Valid = true (false);
}

The arrangements will be slightly different when using the standalone DC server components, such as the Java applet or the ActiveX control, yet, their main idea of request authentication is quite similar.

Including user data in DC requests

In some cases it might be useful to include custom data in DC requests sent by the DC client to DC server, and have that data mirrored back to the DC client together with the signature. This might be the case where the user wants to associate some non-sensitive state-specific information with the request - such as an ID of the document in the database - and does not want to keep it locally.

Most of high-level DC-capable components publish InitiateAsyncSign() / InitiateAsyncOperation() overloads accepting the byte[] AdditionalData parameter. Use that parameter to include your data in the request:

C#:


byte[] data = Encoding.UTF8.GetBytes("223818239284741");
Sig.InitiateAsyncSign(cert, chain, null, TSBDCAsyncSignMethod.asmPKCS1,
    data, ref requestState);

To extract your data after receiving the response from the DC server, use the static ExtractAdditionalDataFromAsyncState() method of the type you've used to create the signature request:


byte[] data = TElCMSSignature.ExtractAdditionalDataFromAsyncState(responseState);

The static nature of the extraction method gives you the ability to extract your data before proceeding to the signature finalization stage, allowing to tune-up the components in accordance with the state information included in the data. It is important that you use the extraction method published by the same class that you used to initiate the request.

Note: the difference between user data described here and request parameters is that the user data is opaque to the DC protocol and is simply mirrored back by the DC server without exposing it to the user code, while request parameters are processed by the server and passed on to the user code. User data is therefore an instrument of saving state-specific information in the DC requests to avoid keeping it in the DC client environment between the pre-signing and signature finalization operations (e.g. where there is a need for the scheme to be stateless).

We suggest to use the MDC facility where there is a need to send over some user data, to prevent the data from altering by a man-in-the-middle or a malicious DC server.

Supported signing methods and differences between them

A signing method within the DC context indicates the standardized approach of DC server-based signature creation. Depending on your circumstances - such as technologies available on the server side or the need to be compliant to a particular signature standard - you might be required to use a specific approach for the scheme to work correctly.

As of SecureBlackbox version 15, DC supports two signing methods: PKCS#1 and PKCS#7.

PKCS#1 is a simple signature method which takes a hash of the data and applies a cryptographic signing permutation to it. As a result you obtain a so-called raw signature which is basically a very long integer value. This signature carries no information about its creator or any circumstances of its creation, and can only be validated by an entity which knows which exactly certificate was used to create it.

PKCS#7 is a more complicated signature method which creates so-called enveloping signatures. In contrast to PKCS#1 signatures, PKCS#7 signatures are complex data records which include a lot of additional signature-specific information, such as their creation time, the signer's certificate identifier, certificates comprising the signer's chain, the content type of the signed data, and many more. At the same time, PKCS#7 signatures rely on PKCS#1 signatures internally.

Most of high level signature formats (CMS, PAdES, XAdES) rely on PKCS#7 signatures internally. Due to this fact, SecureBlackbox and DC support two possible options here: (1) a higher-level component, such as a PDF security handler, generates a PKCS#7 record by itself and only asks DC server to create its internal PKCS#1 signature value, or (2) a higher-level component asks DC server to create the whole PKCS#7 record, which then goes to the higher-level signature.

DC Java applet and ActiveX control, as well as 'main' SecureBlackbox (security library) support both PKCS#1 and PKCS#7 signing methods; the Flex component, however, only supports PKCS#1 (due to limitations of the platform). Still, in some scenarios you must stick to PKCS#7 to be able to create valid signatures. One of common examples of such scenarios is where you need to create an AdES signature (CAdES, PAdES or XAdES), and you don't have a public certificate of the signer on the client DC side on the pre-signing stage. This is because the compliant AdES signature must cover the signer's certificate, which is not possible with PKCS#1 as its hash value is always calculated on the pre-signing stage. PKCS#7 method allows to postpone the calculation of the to-be-signed hash up until the signing stage on the DC server, allowing to work around the certificate unavailability issue.

To summarise the above, if you need to create an AdES signature and you don't have the signer's certificate on the pre-signing stage, you must use the PKCS#7 sign method. In all other cases, you can use either of PKCS#1 and PKCS#7 methods.

Managing pre-signed documents

Most of DC-capable components (such as TElCMSSignature, TElMessageSigner, TElPDFDocument) require that an interim 'pre-signed' document is stored somewhere on the DC client between DC initialization (InitiateAsyncSign()) and finalization (CompleteAsyncSign()) stages. A typical pre-signed document is basically a skeleton of the matching signed document, but with a placeholder in place of a signature.

Correct management of pre-signed documents may require certain arrangements to be implemented on the DC client side. If your DC client is implemented in stateless way, one of the problems that might need addressing is a capability of establishing a correspondence between response states returned by the DC server and the relevant pre-signed documents cached locally on the initialization stage. There are several ways of dealing with that problem, which you can choose from depending on your circumstances.

The two most common methods of keeping interim versions of the documents on the DC client are to use a local temporary directory, or a database. Each option has its pros and cons and should be chosen basing on the specifics of your environment.

You can include a reference to the pre-signed document in the signature request that you send to the DC server, and have it returned back to you with the server's response. Such reference may contain an ID of the pre-signed document in a database, or a temporary name it was given in a local file system.

Pass the identifier of your document via the AdditionalData parameter. There are no specific requirements for the format of provided additional data, you can use any kind of ID you like. While no length limitations are imposed, please keep in mind that the value you pass will be included in both request and response states, and may cause extra pressure on the DC server and the network.

The identifier can be read back from the response state returned by the DC server with ExtractAdditionalDataFromAsyncState() method published by higher-level classes alongside InitiateAsyncSign() and CompleteAsyncSign(). The method is declared as static, meaning you can extract the data before proceeding to the signature finalization stage.

As in all scenarious where inclusion and mirroring of custom data takes place, we recommend enabling the MDC feature to guarantee its integrity.

With some components you can also choose to use the built-in pre-signed document caching features DC offers with some higher-level components. Those features are available via DC state storage component hierarchy. The hierarchy starts with an abstract TElCustomDCStateStorage class, which defines a standardized interface for internal DC pre-signed data cache functionality. SecureBlackbox includes one well-defined TElCustomDCStateStorage descendant, TElFileDCStateStorage, which implements data caching in a local temporary directory. You are free to implement your own descendant of TElCustomDCStateStorage class which will use an alternative storage means, such as a relational database.

To employ the state storage functionality, first of all you need to make sure that the component you use does support it. Such components typically publish an InitiateAsyncSign() overload which doesn't require you to provide a buffer or stream for the pre-signed data. Consider the following TElMessageSigner component's calls:

C#:


// Full method - you get preSignedStream back.
signer.InitiateAsyncSign(srcStream, preSignedStream, false,
        TSBDCASyncSignMethod.asmPKCS1, ref state);

// State-storage-based method - no output pre-signed data parameter.
signer.InitiateAsyncSign(SrcStream, false,
        TSBDCASyncSignMethod.asmPKCS1, ref state);

The state storage component to use should be assigned to the default request creation factory, from where it will be picked up by the consuming component:

C#:


// Creating and configuring state storage.
TElFileDCStateStorage stateStorage = new TElFileDCStateStorage();
stateStorage.StorageRoot = @"C:\Temp\DC\states";

// Assigning the created component to the factory.
SBDCDef.Unit.DefaultDCRequestFactory.StateStorage = stateStorage;

You don't need to pass document identifiers via additional data parameters when using state storage functionality, this will be done for you automatically.

How To articles about Distributed Cryptography add-on.

Discuss this help topic in SecureBlackbox Forum