PKI SDK quick reference
The essentials of PKI SDK on one page, with links to the full articles.
What it is for
PKI SDK is a .NET library for public key infrastructure operations: decoding and validating X.509 certificates, and creating and validating CMS/CAdES, PDF/PAdES and XmlDSig/XAdES signatures. It runs inside your .NET application, on .NET Framework or, through .NET Standard 2.0, on .NET Core (including Linux servers); for web signatures, Web PKI does the browser part and PKI SDK the server part.
| Choose instead | When it fits better |
|---|---|
| PKI Express | Your backend is not .NET: PKI Express creates CAdES, PAdES and XAdES signatures from virtually any language, hosted on your own Windows or Linux servers. |
| Rest PKI Core | You prefer a REST API to a library: use it as a service at core.pki.rest or on your own instance, and it hosts the page where users sign. |
| Web PKI | You need the browser side: Web PKI lists the user's certificates and signs with them; it complements PKI SDK in web remote signatures. |
| Signer | You want a ready-made document and signature manager, as SaaS or on-premises, rather than building the signing flow yourself. |
Install
| Language | Package | Current major | Registry |
|---|---|---|---|
| C# / .NET (NuGet) | Lacuna.Pki | 2 (latest 2.22.7) | https://www.nuget.org/packages/Lacuna.Pki |
| C# / .NET, cloud certificates (optional) | Lacuna.Pki.BrazilTrustServices | 1 (latest 1.7.4) | https://www.nuget.org/packages/Lacuna.Pki.BrazilTrustServices |
Add Lacuna.Pki with the NuGet Package Manager (package Lacuna PKI SDK); version 2.22.7 targets .NET Standard 2.0 and .NET Framework 4.5, 4.6 and 4.7 (.NET Standard / .NET Core Support).
Optional packages add storage, logging and nonce-store integrations: Lacuna.Pki.AzureConnector, Lacuna.Pki.AmazonConnector, Lacuna.Pki.EntityFrameworkConnector and Lacuna.Pki.NLogConnector; see Optional Nuget packages and the Changelog.
Configuration and authentication
PKI SDK runs in your application's process and is enabled by a license, a LacunaPKILicense.config file or a Base64 string, loaded at application startup.
Load it in one of three ways (Licensing):
- Web applications (recommended): copy
LacunaPKILicense.confignext toLacuna.Pki.dll, for instance with Build Action set to Content and Copy to Output Directory set to Copy Always. Web.configorApp.config: theappSettingskeyLacunaPKI.LicensePath(path to the file) orLacunaPKI.BinaryLicense(Base64 license).- Desktop applications (recommended):
PkiConfig.LoadLicense(byte[])with the decoded Base64 license;PkiConfig.LoadLicense(string)takes the file path instead.
byte[] binLicense = Convert.FromBase64String("[given Base64 License]");
PkiConfig.LoadLicense(binLicense);
Other settings are properties of PkiConfig, such as CrlDownloadTimeout, TimestampRequestTimeout, TempPath, PreemptiveCrlDownload and WebProxy (Configuration).
Every validation takes an ITrustArbitrator that decides which roots to trust: TrustArbitrators.PkiBrazil, TrustArbitrators.Windows, TrustedRoots, or several combined in a LinkedTrustArbitrator.
There is no base URL to configure: the SDK downloads CA certificates, CRLs and OCSP responses online while building and validating chains, and policies with a timestamp need a TimestampRequester built with your timestamp authority's URI.
Core operations
The examples are in C#, trimmed from the docs and from the ASP.NET MVC project in PkiSuiteSamples, where Util.GetTrustArbitrator() returns the trust arbitrator (see Test certificates and credentials).
Decode and validate a certificate
Load a certificate in BER, DER, Base64 or PEM encoding and validate it against the roots you trust: PKCertificate.Decode and PKCertificate.Validate. See Decoding certificates and Validating certificates.
var cert = PKCertificate.Decode(pem);
var vr = cert.Validate(TrustArbitrators.PkiBrazil);
if (vr.IsValid) {
// the certificate is valid
}
Console.WriteLine(vr);
Sign a PDF with Web PKI
The user's key stays in the browser: the server prepares the bytes to sign with PadesSigner.GetToSignBytes, Web PKI signs their hash (readCertificate, then signHash), and the server completes with PadesSigner.SetPreComputedSignature, keeping the transfer data between the two requests. See Web remote signatures and PAdES signatures.
// Start: the certificate encoding was read in the browser by Web PKI
var padesSigner = new PadesSigner();
padesSigner.SetPdfToSign(userfilePath);
padesSigner.SetSigningCertificate(PKCertificate.Decode(model.CertContent));
padesSigner.SetPolicy(PadesPoliciesForGeneration.GetPadesBasic(Util.GetTrustArbitrator()));
toSignBytes = padesSigner.GetToSignBytes(out signatureAlg, out transferData);
var toSignHash = signatureAlg.DigestAlgorithm.ComputeHash(toSignBytes); // signed in the browser
// Complete: model.Signature is the signHash() output
padesSigner = new PadesSigner();
padesSigner.SetPolicy(PadesPoliciesForGeneration.GetPadesBasic(Util.GetTrustArbitrator()));
padesSigner.SetPreComputedSignature(model.Signature, transferDataContent);
padesSigner.ComputeSignature();
var signedPdf = padesSigner.GetPadesSignature();
Sign a PDF with a server key
PKI SDK is the recommended way to implement server signatures in .NET: load a PKCertificateWithKey (from a PKCS#12 file with Pkcs12CertificateStore.Load, or from the Windows store with WindowsCertificateStore) and call PadesSigner.ComputeSignature directly. See Certificates with private key.
var store = Pkcs12CertificateStore.Load(certContent, "1234");
var certWithKey = store.GetCertificatesWithKey().First();
var padesSigner = new PadesSigner();
padesSigner.SetPdfToSign(userfileContent);
padesSigner.SetSigningCertificate(certWithKey);
padesSigner.SetPolicy(PadesPoliciesForGeneration.GetPadesBasic(Util.GetTrustArbitrator()));
padesSigner.ComputeSignature();
var signedPdf = padesSigner.GetPadesSignature();
Create a CAdES signature
CAdES signs any binary file with CadesSigner; GetSignature returns the CMS package (a .p7s file in the samples). For signing in the browser, use GenerateToSignBytes and SetPrecomputedSignature; to finish later, for instance in a background worker, use SetFinishSignature(false) and CadesSignatureFinisher. See CAdES signatures and Asynchronous signature finishing.
var cadesSigner = new CadesSigner();
cadesSigner.SetDataToSign(userfileContent);
cadesSigner.SetSigningCertificate(certWithKey);
cadesSigner.SetPolicy(CadesPoliciesForGeneration.GetPkiBrazilAdrBasica());
cadesSigner.ComputeSignature();
var signatureContent = cadesSigner.GetSignature();
Sign an XML document
FullXmlSigner signs a whole XML document (by default, the signature becomes a child of the root element); XmlElementSigner with SetToSignElementId signs one element, which needs a unique ID attribute. Policies include XmlPolicySpec.GetXadesBasic, BrazilXmlPolicySpec.GetAdrBasicaV22 and BrazilXmlPolicySpec.GetNFePadraoNacional; with Web PKI, use GenerateToSignHash and SetPrecomputedSignature. See XML signatures.
var signer = new FullXmlSigner();
signer.SetXml(xmlContent);
signer.SetSigningCertificate(certWithKey);
signer.SetPolicy(BrazilXmlPolicySpec.GetNFePadraoNacional());
signer.ComputeSignature();
var signedXml = signer.GetSignedXml();
Open and validate a signature
Open a signed file and validate each signer against a policy: PadesSignature.Open and ValidateSignature for PDF, CadesSignature.Open for CAdES, and XmlSignatureLocator.GetSignatures for XML. Each validation returns a ValidationResults. See CAdES signatures.
var signature = PadesSignature.Open(userfileContent);
var policyMapper = PadesPoliciesForValidation.GetPadesBasic(Util.GetTrustArbitrator());
foreach (var signerInfo in signature.Signers) {
ValidationResults vr = signature.ValidateSignature(signerInfo, policyMapper);
Console.WriteLine(vr);
}
Authenticate a user with a certificate
PKCertificateAuthentication.Start issues a nonce, the browser signs it with the user's certificate, and Complete checks the signature and validates the certificate. Nonces go to an INonceStore: the sample uses FileSystemNonceStore, and the Entity Framework connector offers EntityFrameworkNonceStore. See Certificate authentication.
// GET: issue the nonce to be signed in the browser
var certAuth = new PKCertificateAuthentication(Util.GetNonceStore());
var nonce = certAuth.Start();
// POST: check the nonce signature and the certificate
PKCertificate certificate;
var vr = certAuth.Complete(model.Nonce, model.Certificate, model.Signature, Util.GetTrustArbitrator(), out certificate);
if (!vr.IsValid) {
// authentication failed: show vr.ToString()
}
Generate a printer-friendly version
Stamp a signed PDF with a verification code and a summary of its signers: AlphaCode.Generate creates the code, and PdfMarker.WriteMarks writes the marks; the signatures are removed from the marked copy. In the sample, allPagesMessage names the signers of PadesSignature.Open(pdfContent) and shows AlphaCode.Format(verificationCode). See Printer-friendly version and Generating alphanumeric codes.
var verificationCode = AlphaCode.Generate(); // store it with the document
var pdfMarker = new PdfMarker();
pdfMarker.AddMark(new PdfMark() {
PageOption = PdfMarkPageOptions.AllPages,
Container = new PadesVisualRectangle() { Height = 2, Bottom = 0, Left = 1.5, Right = 3.5 },
Elements = new List<PdfMarkElement>() {
new PdfMarkText() { Texts = new List<PdfTextSection>() { new PdfTextSection() { Style = PdfTextStyle.Normal, Text = allPagesMessage } } }
}
});
pdfMarker.ThrowIfSignedPdf = false;
var pfvContent = pdfMarker.WriteMarks(pdfContent);
Errors
Errors come as exceptions from signers and as ValidationResults from validations:
- Signers throw
ValidationExceptionwhen a check fails, for instance an unreadable certificate encoding or an expired or revoked certificate; itsValidationResultsproperty says why, and the samples showex.ValidationResults.ToString()to the user. - Validations do not throw:
PKCertificate.Validate,ValidateSignatureandPKCertificateAuthentication.Completereturn aValidationResultswithIsValid,Errors,WarningsandPassedChecks; a failed authentication is normal for an expired or revoked certificate. - To treat a check as a warning instead of an error, set its level on
CertificateValidationOptions, for instanceSetFailureLevel(ValidationItemTypes.CertificateRevocationStatusUnknown, ValidationFailureLevels.Warning). - With a policy that requires a timestamp, such as AD-RT,
ComputeSignaturethrowsTimestampExceptionif the timestamp cannot be obtained;TimestampRequestException, which identifies errors when requesting a timestamp, derives from it. PdfMarkerthrowsSignedPdfToMarkExceptionon a PDF that has any signature, unlessThrowIfSignedPdfisfalse.
TODO(docs): no article says when LicenseException, InvalidPdfException, PasswordProtectedPdfException or PkiWebException are thrown, for instance on a missing or expired license; the API reference lists them without a description.
Test certificates and credentials
Use the test certificates: PKCS #12 files with password 1234, issued by the Lacuna Test PKI, which must never be trusted in production.
With PKI SDK, trust them by adding the Lacuna test root as TrustedRoots to a LinkedTrustArbitrator, only in debug builds, as Util.GetTrustArbitrator() does in the samples. See PKI SDK on the test certificates page.
ICP-Brasil policies such as CadesPoliciesForGeneration.GetPkiBrazilAdrBasica() and BrazilXmlPolicySpec.GetNFePadraoNacional() trust only the ICP-Brasil roots, so in debug builds the samples replace their trust arbitrator with Util.GetTrustArbitrator(): ClearTrustArbitrators and AddTrustArbitrator on an XmlPolicySpec, ClearTrustArbitrator and AddTrustArbitrator on a CadesPolicySpec.
For a trial license, contact pki.lacunasoftware.com; the samples project ships its own LacunaPkiLicense.config, copied to the output directory.
Try it
Online demos and the PKI SDK samples for ASP.NET MVC and ASP.NET Core with Angular: