sphereon-signatures-sdk
1.2.3
dotnet add package sphereon-signatures-sdk --version 1.2.3
NuGet\Install-Package sphereon-signatures-sdk -Version 1.2.3
<PackageReference Include="sphereon-signatures-sdk" Version="1.2.3" />
<PackageVersion Include="sphereon-signatures-sdk" Version="1.2.3" />
<PackageReference Include="sphereon-signatures-sdk" />
paket add sphereon-signatures-sdk --version 1.2.3
#r "nuget: sphereon-signatures-sdk, 1.2.3"
#:package sphereon-signatures-sdk@1.2.3
#addin nuget:?package=sphereon-signatures-sdk&version=1.2.3
#tool nuget:?package=sphereon-signatures-sdk&version=1.2.3
eIDAS Advanced Electronic Signature Client for .NET
The eIDAS Advanced Electronic Signature (AdES) client, allows to sign documents and digests (hashes), using CAdES (CMS, binary data), JAdES (JSON), PAdES (PDF), XAdES (XML) signatures, as defined by the European Telecommunications Standards Institute (ETSI). These signatures are part of the eIDAS legal framework in the European Union.
The purpose of this client is to easily create and verify eIDAS compliant signatures for documents and input data, using certificates which are stored in keystore files (PKCS#12) or using hardware (PKCS#11). Documents can be signed by providing the full document or by generating a hash/digest of the document first. Especially with remote signing REST APIs part of the Sphereon VDX platform, we suggest to create the digest first and then use the signature to merge with the original document. This means you are not sending the full document across the wire, which obviously is better from a privacy and security perspective.
.NET library
This is a C# implementation of the eidas-signature-client. The target framework is .NET Standard 2.0. For this SDK to work, a server component is required which is hosted by Sphereon as a service and can be purchased to run on-premise. Please contact sales@sphereon.com for more information.
Prerequisites
Required Software
- .NET SDK: .NET Core 2.0 or later (library targets
netstandard2.0) - Development Environment:
- Visual Studio 2019 or later, or
- Visual Studio Code with C# extension, or
- JetBrains Rider
- Testing: .NET 8.0 SDK (for running tests in
net8.0framework)
Required Services and Licenses
- Sphereon Signature Service: This SDK requires access to the Sphereon eIDAS Signature Service
- Hosted service: Available through Sphereon's cloud platform
- On-premise deployment: Available for enterprise customers
- License: A valid agreement with Sphereon International B.V. is required
- Contact: sales@sphereon.com
Required Accounts and Credentials
- OpenID Connect Provider: Access to an authentication server
- Supported providers: Azure AD (Microsoft Entra ID), Auth0, or any OIDC-compliant provider
- Required credentials:
- OpenID Endpoint URL
- Client ID
- Client Secret (for client credentials flow)
- Sphereon Service Endpoint: URL to your Sphereon signature service instance
Certificate Requirements
Choose one of the following options for certificate storage:
- PKCS#12 Keystore:
.p12or.pfxfile with private key and certificate chain - PKCS#11 Hardware: Hardware Security Module (HSM) or USB cryptographic token
- Azure Key Vault: Azure-hosted certificate and key storage
- Requires Azure subscription and Key Vault instance
- Application registration with appropriate Key Vault permissions
Optional Components
- Time Stamp Authority (TSA): For creating LT (Long Term) and LTA (Long Term Archive) signatures
- Example:
http://timestamp.sectigo.com/(free TSA service)
- Example:
- Certificate Authority (CA): For issuing qualified certificates (if not already obtained)
Signature flow
Creating a signed AdES document comprises several steps. It starts with the Original Data/Document, for which we first need to determine the Sign
Input. The SignInput typically either is the full document, or a part of the document (PDF for instance). The determineSignInput method which
requires the input document together with the signature type and configuration as parameters, automatically determines the Sign Input. The
determineSignInput can be run locally without the need to use a REST API for instance.
Next there are two options. Directly signing the SignInput object using the createSignature method, resulting in a signature, or creating a
Digest (Hash) of the SignInput. Since the createSignature method could be using a remote REST service or remote Hardware Security Module for
instance, it is advices to use the Digest method in most cases. The Digest method can be run locally, so even if the createSignature method needs to
access remote resources, no information from the orig data/document would be sent across the wire. The digest method accepts a SignInput object as
parameter and results in another SignInput object, with its sign method set to DIGEST instead of the original method of DOCUMENT.
The createSignature method accepts the SignInput object, which the signMode either being DOCUMENT or DIGEST, depending on which method was
chosen. It is using the supplied 'KeyEntry' to sign the input object. This can either be done locally or remotely depending on the CertProvider
implementation. The end result is a Signature object.
Lastly the Signature object needs to be merged with the original Document. It really depends on the type of signature being used how this is
achieved. The document could for instance become part of the signature (ENVELOPING), the signature could become part of the document (ENVELOPED), or
the signature could be detached (DETACHED)
The picture below gives a schematic overview of the process
OpenAPI authentication
This library connects to one or more signature services that either run in the cloud or on the LAN. These services required authentication in form of a bearer token. This token must be obtained on one of the OpenID authentication servers that is registered with the signature services. The .NET SDK has a helper class for OpenAPI called AuthnApi. It has two ways to log in:
- LoginFromDesktop() - this can be used when the SDK is implemented in a desktop application. The application will open a browser window/tab hosting the sign in page of the authentication server.
- CreateAuthorizeState() - this can be used when the SDK is implemented in a web environment. Details on how to implement this can be found here. The end goal is to retrieve a field called LoginResult.IdentityToken which is needed as input for class ApiFactory.
var authnApi = new AuthnApi(signaturesSdkConfig);
this.loginResult = authnApi.LoginFromDesktop().GetAwaiter().GetResult();
if (loginResult.IsError)
{
throw new Exception(loginResult.Error);
}
More information on how to use the OpenID library can be found on https://identitymodel.readthedocs.io
Troubleshooting
- Transient HTTP failures (timeouts, 408, 429, 5xx) are retried with exponential backoff.
- Network errors include the endpoint, HTTP status, and a short response summary.
- TLS errors (expired/invalid certificates) are reported explicitly during token or service calls.
Customizing Retry Behavior
The SDK applies automatic retry logic for transient failures using Polly. The default policy:
- Retries up to 3 times with exponential backoff (500ms, 1s, 2s)
- Applies to all REST API calls globally
- Handles timeouts, 408, 429, and 5xx status codes
Most SDK operations (signature verification, configuration retrieval) are inherently idempotent. For potentially non-idempotent operations or to customize the behavior:
Disable retries globally:
Sphereon.SDK.Signatures.Client.RetryConfiguration.RetryPolicy = null;
Sphereon.SDK.Signatures.Client.RetryConfiguration.AsyncRetryPolicy = null;
Set a custom retry policy:
using Polly;
using RestSharp;
// Custom policy with different retry count
var customPolicy = Policy<IRestResponse>
.Handle<Exception>()
.OrResult(r => (int)r.StatusCode >= 500)
.WaitAndRetryAsync(5, attempt => TimeSpan.FromSeconds(attempt));
Sphereon.SDK.Signatures.Client.RetryConfiguration.AsyncRetryPolicy = customPolicy;
Add jitter for high-concurrency scenarios:
// Requires: Polly.Contrib.WaitAndRetry NuGet package
var jitteredPolicy = Policy<IRestResponse>
.Handle<Exception>()
.OrResult(r => (int)r.StatusCode >= 500)
.WaitAndRetryAsync(Polly.Contrib.WaitAndRetry.Backoff.DecorrelatedJitterBackoffV2(
medianFirstRetryDelay: TimeSpan.FromMilliseconds(500),
retryCount: 3
));
Sphereon.SDK.Signatures.Client.RetryConfiguration.AsyncRetryPolicy = jitteredPolicy;
APIs
API instances can be obtained by creating an instance of ApiFactory.
this.apiFactory = new ApiFactory(signaturesSdkConfig, loginResult.IdentityToken, signaturesSdkConfig.ServiceEndpoint);
var keyProviderApi = apiFactory.KeyProviderApi;
var keysApi = apiFactory.KeysApi;
var signatureConfigApi = apiFactory.SignatureConfigApi;
var signingApi = apiFactory.SigningApi;
SDK configuration
Both the authentication helper and ApiFactory need a configuration object of class SignaturesSdkConfig. This object can be constructed manually, or can be created from the system environment variables:
var signaturesSdkConfig = SignaturesSdkConfig.FromEnvironment();
It will take the following variables:
| C# variable | Env variable | Description |
|-------------------|-------------------------------------|---------------------------------------------------------------------------------------------------------|
| ServiceEndpoint | SIGNATURES_SDK_SERVICE_ENDPOINT | The EIDAS signature-service cloud or LAN endpoint |
| OpenIdEndpoint | SIGNATURES_SDK_OPENID_ENDPOINT | The URL of the OpenID server ie. https://login.microsoftonline.com/ffffffff-0000-ffff-0000-ffffffffffff |
| OpenIdClientId | SIGNATURES_SDK_OPENID_CLIENT_ID | The OpenID application client ID |
| OpenIdClientSecret| SIGNATURES_SDK_OPENID_CLIENT_SECRET | The OpenID client secret |
| OpenIdScope | - | Optional OAuth scope for client credentials flow (defaults to Azure AD pattern: api://{ClientId}/.default) |
| RedirectUrl | - | The URL where the authentication page has to return to (not needed when using LoginFromDesktop) |
Additional environment variables used by tests and integrations: | Env variable | Description | |---------------------------------|------------------------------------------------------------| | SIGNATURES_SDK_KEY_PROVIDER_ID | The ID of a pre-configured key provider | | SIGNATURES_SDK_KEY_ID | The certificate alias / key ID within the key provider | | SIGNATURES_SDK_AZURE_CLIENT_ID | Azure AD application client ID (for Azure Key Vault) | | SIGNATURES_SDK_AZURE_CLIENT_SECRET | Azure AD application client secret (for Azure Key Vault)|
Key Provider Service
The Key Provider Service allows to manage public/private keys and Certificates using either a PKCS#12 keystore file as byte array or filepath. It also has support for PKCS#11 hardware (HSM and USB cards). Given the wide range of supported import/creation methods, this library does not create or import certificates. Please use your method of choice (see below for some pointers)
Initialize Key Provider Service
The below example in C# sets up a key provider service using a PKCS#12 keystore.
var createKeyProviderResponse = apiFactory.KeyProviderApi.CreateKeyProvider(
new CreateKeyProvider(type: KeyProviderType.PKCS12)
);
When using an Azure keyvault, the configuration looks like this
var createKeyProviderResponse = apiFactory.KeyProviderApi.CreateKeyProvider(new CreateKeyProvider(
cacheEnabled: false,
cacheTTLInSeconds: 24 * 60 * 60,
type: KeyProviderType.AZUREKEYVAULT,
azureKeyvaultSettings: new AzureKeyvaultSetting(
keyvaultUrl: "https://my-certs.vault.azure.net/",
tenantId: "ffffffff-0000-ffff-0000-ffffffffffff",
applicationId: "my-app",
credentialOpts: GetAzureCredetials()
)
));
Use existing tooling to create a certificate and keystore
How to generate and/or import X.509 certificates and PKCS#12 keystores is out of scope of this project, but we provide some hints below. There are numerous resources on the internet to create X.509 certificates and PKCS#12 keystores.
Creating a keystore using OpenSSL
The private key and certificate must be in Privacy Enhanced Mail (PEM) format (for example, base64-encoded
with ----BEGIN CERTIFICATE---- and ----END CERTIFICATE---- headers and footers).
Use the following OpenSSL commands to create a PKCS#12 file from your private key and certificate. If you have one certificate, use the CA root certificate.
openssl pkcs12 -export -in <signed_cert_filename> -inkey <private_key_filename> -name ‘tomcat’ -out keystore.p12
If you have a chain of certificates, combine the certificates into a single file and use it for the input file, as shown below. The order of certificates must be from server certificate to the CA root certificate.
See RFC 2246 section 7.4.2 for more information about this order.
cat <signed_cert_filename> <intermediate.cert> [<intermediate2.cert>] > cert-chain.txt
openssl pkcs12 -export -in cert-chain.txt -inkey <private_key_filename> -name ‘tomcat’ -out keystore.p12
When prompted, provide a password for the new keystore.
Multiple services
When using multiple services, please note that when a digest is signed on a remote service and the signature is applied on a local service, the local service needs the public key of the certificate as part of its configuration.
Signature Configuration
Before the signature service can be used, a configuration record needs te be created. This record contains the parameters that the signing process needs and it will be stored in the service's database. Example:
// Create a signature configuration
SignatureFormParameters formParams = new(new PadesSignatureFormParameters(
contactInfo: "Sphereon",
reason: "For testing",
location: "Maarssen, NL",
signerName: "The Don",
mode: PdfSignatureMode.CERTIFICATION
));
var restrictions = new AccessRestrictions
{
RoleRestrictions = new List<RoleRestriction>
{
new RoleRestriction("pdf-sign", RolePermission.USE)
},
AccessLevel = AccessLevel.PUBLIC
};
var configResponse = apiFactory.SignatureConfigApi.CreateConfig(new SignatureConfig(
signatureLevel: SignatureLevel.PAdESBASELINEB,
signatureFormParameters: formParams,
accessRestrictions: restrictions,
digestAlgorithm: DigestAlgorithm.SHA256));
Signature Service
The Signature Service allows you to create and verify signatures, as well as creating a hash/digest of input data
Initialize the Signature Service
The Signature service requires a certificate provider. If you want to use multiple certificate providers you will have to instantiate multiple signature services.
Determine Sign Input
Determines the bytes that will serve as input for the digest or createSignature methods.
Since multiple signature types are supported the configuration and key are required te determine the appropriate mode of extraction. For instance
Pades signatures do not need a simple digest of the full file contents, depending on whether the PDF document already contains signatures. This method
should be called first when creating a signature.
var origData = ApiUtils.CreateOrigData(new FileInfo(InputFilePath));
var determineSignInput = new DetermineSignInput(
origData: origData,
signMode: SignMode.DOCUMENT,
new ConfigKeyBinding(CertificateAlias, signatureConfigId, keyProviderId)
);
var signInputResponse = apiFactory.SigningApi.DetermineSignInput(determineSignInput);
The below SignInput object could be used directly for the createSignature method or a digest can be created first, so that the input data will never traverse a network if a remote Sign REST API or remote Hardware Security Module is being used.
{
"input": "MYHeMBgGCSqG....TAkxVAgEK",
"signMode": "DOCUMENT",
"digestAlgorithm": "SHA256",
"name": "input.pdf"
}
Create a hash digest for additional privacy and security
The digest method creates a hash digest out of the SignInput. The hash digest is a one way function that creates the fingerprint of the file. From
the digest you cannot get back to the original input data/document. This means any Personally Identifiable Data or Data which needs to stay private
will not be available to methods which need access to a remote REST API or remote Hardware Security Module. This obviously is preferable from a
privacy and security perspective. It allows users of the library to execute all methods on premise and then depending on the chosen Certificate
Provider sign the data either on premise or remotely. In no circumstance will the input data leave the premise.
var createDigest = new Digest(signInputResponse.SignInput);
var digestResponse = apiFactory.SigningApi.Digest(createDigest);
Notice that the below SignInput object is different from the passed in SignInput. The input value is shorter as it now is a hash digest. The signMode
moved from DOCUMENT to DIGEST so that the createSignature method knows not to create a hash digest out of the input anymore.
{
"input": "fSx6BzHxJ8p3Mn9E52DJ1eNrchfcMa1ZHaSjAi9D5z8=",
"signMode": "DIGEST",
"digestAlgorithm": "SHA256",
"name": "input.pdf"
}
Create the signature
Depending on the certificate provider settings this method could be traversing the network as it might call a signature REST API, or use a network/cloud based Hardware Security Module containing the private key to sign. As such we advise to create the digest beforehand so original documents/data is not being sent. Only the hash digest will traverse the network.
var createSignature = new CreateSignature(digestResponse.SignInput);
var signatureResponse = apiFactory.SigningApi.CreateSignature(createSignature);
{
// The actual signature
"value": "SoSsp+Mut3....XEDqEVw==",
"algorithm": "RSA_SHA256",
"signMode": "DIGEST",
// The certificate used during signing
"certificate": {
"value": "MIID1D....6Q42vNaS"
},
// The certificate chain including the Certificate Authority (CA) last
"certificateChain": [
{
"value": "MIID1D....6Q42vNaS"
},
{
"value": "MIID6j....GePoU8Ug=="
},
{
"value": "MIIDVzC....PSNfsSBog=="
}
]
}
Signing the original data, merging the signature
var signData = new MergeSignature(
origData: origData,
signature: signatureResponse.Signature);
var signResponse = apiFactory.SigningApi.MergeSignature(signData);
using (var outputStream = File.OpenWrite(outputFilePath))
{
outputStream.Write(signResponse.SignOutput.Value);
}
The result of the above is a new file which is the input PDF, but now signed.
{
// The signed data/document
"value": "JVBERi0xLjYNJ....4cmVmCjc2NzUwCiUlRU9GCg==",
"signMode": "DIGEST",
"digestAlgorithm": "SHA256",
"name": "input-pades-baseline-b.pdf",
"mimeType": "application/pdf",
"signature": {
"value": "SoSsp+Mut3....XEDqEVw==",
"algorithm": "RSA_SHA256",
"signMode": "DIGEST",
"certificate": {
"value": "MIID1DCC....XxY1e6Q42vNaS"
},
"certificateChain": [
{
"value": "MIID1DCC....XxY1e6Q42vNaS"
},
{
"value": "MIID6jCC....Y+TpJGePoU8Ug=="
},
{
"value": "MIIDVzCCAj....PSNfsSBog=="
}
]
}
}
Full code sample
The following example (test class) shows how a desktop application can use the eIDAS signature client to sign a PDF with a PAdES signature. To target our demo test environment, set the following environment variables:
SIGNATURES_SDK_SERVICE_ENDPOINT= https://eidas-signature.test.ms.sphereon.comSIGNATURES_SDK_OPENID_ENDPOINT= https://login.microsoftonline.com/e2a42b2f-7460-4499-afc2-425315ef058aSIGNATURES_SDK_OPENID_CLIENT_IDandSIGNATURES_SDK_OPENID_CLIENT_SECRET(supplied by Sphereon)SIGNATURES_SDK_KEY_PROVIDER_ID(the pre-configured key provider ID)SIGNATURES_SDK_KEY_ID(the certificate alias within the key provider)
Place your test PDF in a resources folder relative to the test project.
using IdentityModel.OidcClient;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Signatures.SDK;
using Sphereon.SDK.Signatures.Model;
using System;
using System.Collections.Generic;
using System.IO;
namespace Signatures.SDK.Tests
{
[TestClass]
public class SignatureTest
{
private const string TestPdfName = "test-unsigned.pdf";
private const string SignedPdfName = "test-signed.pdf";
private readonly LoginResult loginResult;
private readonly ApiFactory apiFactory;
private string? signatureConfigId;
private string? keyProviderId;
public SignatureTest()
{
var signaturesSdkConfig = SignaturesSdkConfig.FromEnvironment();
var authnApi = new AuthnApi(signaturesSdkConfig);
this.loginResult = authnApi.LoginFromDesktop().GetAwaiter().GetResult();
if (loginResult.IsError)
{
throw new Exception(loginResult.Error);
}
this.apiFactory = new ApiFactory(signaturesSdkConfig, loginResult.IdentityToken, signaturesSdkConfig.ServiceEndpoint);
}
[TestMethod]
public void TestPades()
{
TestCreateConfigs(SignatureLevel.PAdESBASELINELT);
TestSign("pades");
}
[TestMethod]
public void TestPKCS7()
{
TestCreateConfigs(SignatureLevel.PKCS7LT);
TestSign("pkcs7");
}
public void TestCreateConfigs(SignatureLevel signatureLevel)
{
// Create a signature configuration
SignatureFormParameters formParams = new(new PadesSignatureFormParameters(
signerName: "Unit Test <test@sphereon.com>",
reason: "E-signed by Unit Test <test@sphereon.com> , Testing!!",
location: "My JVM",
mode: PdfSignatureMode.CERTIFICATION
));
var restrictions = new AccessRestrictions
{
RoleRestrictions = new List<RoleRestriction>
{
new RoleRestriction("pdf-sign", RolePermission.USE)
},
AccessLevel = AccessLevel.PUBLIC
};
var configResponse = apiFactory.SignatureConfigApi.CreateConfig(new SignatureConfig(
signatureLevel: signatureLevel,
signatureFormParameters: formParams,
accessRestrictions: restrictions,
digestAlgorithm: DigestAlgorithm.SHA256,
timestampParameters: new TimestampParameters(tsaUrl: "http://timestamp.sectigo.com/",
baselineLTAArchiveTimestampParameters: new TimestampParameterSettings(digestAlgorithm: DigestAlgorithm.SHA256,
timestampContainerForm: TimestampContainerForm.PDF))));
Assert.IsNotNull(configResponse);
this.signatureConfigId = configResponse.ConfigId;
Console.WriteLine($"signatureConfigId: {signatureConfigId}");
// Use pre-configured key provider from environment
string providerId = GetRequiredProviderId();
var keyProviderResponse = apiFactory.KeyProviderApi.GetKeyProvider(providerId: providerId);
this.keyProviderId = keyProviderResponse.ProviderId;
Console.WriteLine($"kid: {keyProviderId}");
// Test GetKey, write certificate to temp folder
string certificateAlias = GetRequiredCertificateAlias();
var key = apiFactory.KeysApi.GetKey(providerId: providerId, kid: certificateAlias);
Assert.IsNotNull(key);
string certPath = Path.Combine(Path.GetTempPath(), $"{certificateAlias}.cer");
using (var certStream = File.OpenWrite(certPath))
{
certStream.Write(key.KeyEntry.Certificate.Value);
}
}
private void TestSign(string label)
{
string inputFilePath = Path.Combine("resources", TestPdfName);
string outputFilePath = Path.Combine(Path.GetTempPath(), SignedPdfName.Replace(".pdf", label + ".pdf", StringComparison.InvariantCultureIgnoreCase));
// Create a signInput object
var origData = ApiUtils.CreateOrigData(new FileInfo(inputFilePath));
string certificateAlias = GetRequiredCertificateAlias();
var determineSignInput = new DetermineSignInput(
origData: origData,
signMode: SignMode.DOCUMENT,
new ConfigKeyBinding(certificateAlias, signatureConfigId, keyProviderId)
);
var signInputResponse = apiFactory.SigningApi.DetermineSignInput(determineSignInput);
// Create a digest of the input (the signInput object is amended)
var createDigest = new Digest(signInputResponse.SignInput);
var digestResponse = apiFactory.SigningApi.Digest(createDigest);
// Create a signature
var createSignature = new CreateSignature(digestResponse.SignInput);
var signatureResponse = apiFactory.SigningApi.CreateSignature(createSignature);
// Merge the signature onto the PDF document
var signData = new MergeSignature(
origData: origData,
signature: signatureResponse.Signature);
var signResponse = apiFactory.SigningApi.MergeSignature(signData);
// Write result PDF to temp folder
using (var outputStream = File.Create(outputFilePath))
{
outputStream.Write(signResponse.SignOutput.Value);
}
}
private static string GetRequiredProviderId()
{
string? providerId = Environment.GetEnvironmentVariable("SIGNATURES_SDK_KEY_PROVIDER_ID");
if (string.IsNullOrWhiteSpace(providerId))
{
throw new Exception("Environment variable SIGNATURES_SDK_KEY_PROVIDER_ID is required to run this test");
}
return providerId;
}
private static string GetRequiredCertificateAlias()
{
string? certificateAlias = Environment.GetEnvironmentVariable("SIGNATURES_SDK_KEY_ID");
if (string.IsNullOrWhiteSpace(certificateAlias))
{
throw new Exception("Environment variable SIGNATURES_SDK_KEY_ID is required to run this test");
}
return certificateAlias;
}
}
}
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- IdentityModel.OidcClient (>= 5.2.1)
- Microsoft.Identity.Client (>= 4.83.0)
- Newtonsoft.Json (>= 13.0.4)
- Polly (>= 7.2.3)
- RestSharp (>= 106.15.0)
- Serilog.Extensions.Logging (>= 10.0.0)
- Serilog.Sinks.Console (>= 6.1.1)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on sphereon-signatures-sdk:
| Package | Downloads |
|---|---|
|
sphereon-signatures-sdk-itext-extension
iText extension for eIDAS Advanced Electronic Signature Client for .NET |
|
|
sphereon-signatures-sdk-docotic-extension
Docotic extension for eIDAS Advanced Electronic Signature Client for .NET |
GitHub repositories
This package is not used by any popular GitHub repositories.