eDocLib is a .NET 8 library for the Latvian eDoc electronic document container: ASiC-E packaging, XML-DSig / XAdES signing and validation, optional PKIX, OCSP/CRL (embedded and online), trusted lists (TSL) integration, and RFC 3161 timestamps. Behaviour follows public standards and EU tooling (Normative references).
| Target | net8.0 (src/eDocLib/eDocLib.csproj) |
| License | MIT |
| Tests | dotnet test — test/eDocLib.Tests |
eDocLib is under active development. Until we ship a 1.0 release with an explicit stability statement, the public API may change between versions, including breaking changes to types, members, defaults, and validation behaviour, as we tighten alignment with published specs and real-world documents.
- Downstream apps should pin a Git commit or an exact package / pre-release version; avoid floating
*ranges. - After 1.0, we plan to treat SemVer as the contract for the documented public surface, with any intentional breaks called out in release notes.
- MIME type
application/vnd.etsi.asic-e+zip, first ZIP entrymimetype(stored). - OASIS
META-INF/manifest.xmlread/write and consistency checks with payload. - Signatures under
META-INF/signatures*.xml(multiple parallel signatures supported). - Format probe (
AsicContainerFormatProbe) for seekable streams. - Large payload handling: spill directory and in-memory threshold live on
EdocLibConfig; open withEdoc.Open(EdocLibConfig, stream)orEdoc.Open(EdocLibConfig, path). For the same defaults asEdocLibConfig.Default, you can also build a snapshot withEdocLibConfigBuilder.CreateDefault().Build()and adjust TSL relay, TSP routes, spill paths, or online-revocation hints before callingBuild().
- XAdES-BES baseline: detached
Referencedigests,SignedProperties,SigningTime, signer cert inKeyInfo. - Algorithms: RSA-SHA256 (default), optional RSA-SHA384; ECDSA P-256 / P-384 / P-521 with matching digest URIs (
XadesSigningProfile,XadesSignatureAlgorithms). - Exclusive canonicalization for
SignedInfo/SignedProperties(XmlDsigCanonicalization,DetachedSignatureVerifier). - ESS
SigningCertificate: CertDigest + IssuerSerial verification (SigningCertificateDigestVerifier); optional policies (RequireXadesSigningCertificate,RestrictSigningCertificateDigestToSha256). - XAdES-T: RFC 3161
SignatureTimeStamp(SignWithTimestampAsync,AppendSignatureTimestampAsync,Rfc3161HttpTimestampProvider); imprint vsSignatureValue(SignatureTimestampVerifier,SignatureTimestampImprintPolicy). - XAdES-LT-style material:
certificateValues,revocationValues, PKCS#7 certificate bags where applicable (EdocLongTermSigningJob,AppendUnsigned*helpers). - XAdES-LTA / archive timestamps:
AppendArchiveTimeStamp,EdocArchiveSigningJob, CMS / chain / imprint validation flags onSignatureTrustPolicy(full national LTA profile rules remain host- and policy-dependent). - Signer roles & production place: parse and emit; optional validation constraints on claimed roles.
- Reference digests and RSA/ECDSA signature verification over canonical
SignedInfo. - Optional PKIX chain build (
SignatureTrustPolicy.ValidateCertificateChain,CustomTrustAnchors, OS trust store,ExtraChainCertificates). - Revocation: unsigned
RevocationValuesverification (EmbeddedRevocationVerifier); application-controlled HTTP OCSP/CRL after chain build viaApplicationOnlineRevocationandSignatureTrustPolicy.RevocationHttpClientwhen online revocation is enabled; optional disk cache for DER (DirectoryRevocationDerCache); optional delta CRL after base CRL whenRevocationFetchDeltaCrlViaFreshestCdpis set (Freshest CRL extension, RFC 5280). - TSL / qualification:
TrustedListReader,TrustedListServiceIndex,TrustedListQualificationResolver, mapping options (TslQualificationMappingOptions,TslQualificationMappingDefaults), XML signature verification for published lists (TrustedListXmlSignatureVerifier). - Reporting: hierarchical
ValidationResultNodetrees,DocumentValidationReport, localization (IValidationReportLocalizer, RESX), optional validation reports on open (BuildValidationReport).
TrustAnchorLoader: PEM, PKCS#12/PFX, JKS (Java keystore) forSignatureTrustPolicy.CustomTrustAnchors.
EdocLibConfig.Default,EdocLibConfigBuilder.CreateDefault(),Edoc.Open/Edoc.OpenAndValidateAsync,TimestampResponderRegistry.RegisterFrom,EdocLibConfig.ResolveFetchDeltaCrlViaFreshestCdp. Map host configuration into a builder or your own snapshot.
Implementation work is traced to public artefacts such as:
| Area | Typical references |
|---|---|
| Container | ETSI EN 319 162 (ASiC) |
| XAdES | ETSI EN 319 132, baseline AdES EN 319 102-1 / TS 119 102-2 |
| TSL | ETSI TS 119 612, EU LOTL tooling |
| PKIX / CRL | ITU-T X.509, RFC 5280 (incl. Freshest CRL / delta CRL distribution) |
| OCSP | RFC 6960 |
| Timestamps | RFC 3161 |
| National profile | EDOC 2.0 (Latvia / LVRTC-published materials) |
Requirements coverage is reflected in test/eDocLib.Tests and in comments on individual checks where helpful.
PDF signing, smart cards / PKCS#11, legacy EDOC 1.0, batch APIs, and a mandatory global properties singleton are excluded unless added explicitly in a future release.
src/eDocLib/ # Single NuGet-style project (eDocLib)
test/eDocLib.Tests/ # xUnit tests
tools/ # Optional tooling (e.g. fixtures), not required at runtime
| Folder / area | Role |
|---|---|
eDocLib (project root — shared eDocLib namespace) |
Edoc (incl. CreateNew, Open, OpenAndValidateAsync), EdocValidation, Configuration.EdocLibConfig, Configuration.EdocLibConfigBuilder, EdocBasicSigningJob, EdocLongTermSigningJob, EdocArchiveSigningJob, DataFile, IContainer, IValidatableDocument, EdocException / EdocFailureKind, material profiles |
Asic/ |
ZIP ASiC-E reader/writer, manifest, format probe, payload spill helpers |
Asic/Xades/ (namespace eDocLib.Asic.Xades) |
XadesBesSigner, XadesSignature, XadesBesPreparedSignature, XadesSigningProfile / XadesRsaDigestPreference / XadesKeyKind, CertificateValuesWireFormat, C14N, algorithms, LT/LTA unsigned properties (types internal except the public signing surface above) |
Validation/ |
SignatureValidator, SignatureTrustPolicy, PKIX diagnostics, TSL reader/index/qualification, archive timestamp verification, DetachedSignatureVerifier |
Validation/Reporting/ |
ValidationReportFactory, DocumentValidationReport, ValidationResultNode, localization, text formatter |
Revocation/ |
Public policy/caching/artifacts: IRevocationDerCache, DirectoryRevocationDerCache, EmbeddedRevocationVerificationOptions, RevocationArtifact* — subfolders: Online/ (HTTP fetch, URI discovery, CertificateRevocationMaterialFetcher), Protocols/Ocsp, Protocols/Crl, Protocols/Der (shared certificate/CRL DER reads), Verify/ (embedded/online verifiers), Cache/, Xades/ (unsigned RevocationValues helper) |
Timestamp/ |
Rfc3161HttpTimestampProvider, TimestampResponderRegistry |
Configuration/ |
EdocLibConfig, EdocLibConfigBuilder (incl. CreateDefault()); ASiC read fields on EdocLibConfig |
Trust/ |
TrustAnchorLoader (PEM / PKCS#12 / JKS anchors for SignatureTrustPolicy) |
Trust/Tsl/ |
HttpTslTrustedListProvider, CachingTslTrustedListProvider, NullTrustedListProvider, ITrustedListProvider, TrustedListManager, TslPublicationUris, EuLotlPointerLocator |
Dependencies: BouncyCastle.Cryptography, SharpZipLib, System.Security.Cryptography.Xml (see eDocLib.csproj).
Typical namespaces:
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using eDocLib;
using eDocLib.Configuration;
using eDocLib.Validation;
using eDocLib.Validation.Reporting;
using eDocLib.Timestamp;using var stream = File.OpenRead("candidate.zip");
if (!Edoc.TryDetectContainer(stream, out var probe))
{
// Not a plausible ASiC-E shell (mimetype / manifest).
return;
}
// probe.IsLikelyAsicE — quick positive signal; open with Edoc.Open to parse fully.
stream.Position = 0;// From file — uses spill / memory limits from EdocLibConfig.Default
using var edoc = Edoc.Open(EdocLibConfig.Default, @"C:\incoming\package.edoc");
// From stream — same defaults; stream should be seekable for typical ZIP layout
await using var stream = File.OpenRead("package.edoc");
using var fromStream = Edoc.Open(EdocLibConfig.Default, stream);Custom read profile (large entries, spill directory):
var config = EdocLibConfigBuilder.CreateDefault()
.WithPayloadSpillTempDirectory(Path.Combine(Path.GetTempPath(), "edoc-spill"))
.WithPayloadMemoryThresholdBytes(64L * 1024 * 1024)
.Build();
using var edoc = Edoc.Open(config, "large.edoc");foreach (var file in edoc.DataFiles)
{
Console.WriteLine($"{file.Name} ({file.MimeType})");
using var copy = new MemoryStream();
await file.Stream.CopyToAsync(copy);
}One step — open with default read settings and validate:
await using var stream = File.OpenRead("document.edoc");
var result = await EdocValidation.OpenAndValidateAsync(stream, SignatureTrustPolicy.CryptographyOnly);
// result.Edoc — loaded container; dispose when finished
// result.AllSignaturesValid — false if there are no signatures
using (result.Edoc)
{
Console.WriteLine(result.AllSignaturesValid ? "OK" : "Invalid");
}Open then validate (same as Edoc.OpenAndValidateAsync when you split the steps):
using var edoc = Edoc.Open(EdocLibConfig.Default, "document.edoc");
var result = await edoc.ValidateAsync(SignatureTrustPolicy.CryptographyOnly);
// or: await EdocValidation.ValidateSignaturesAsync(edoc, policy);Open + validate with explicit config:
await using var stream = File.OpenRead("document.edoc");
var result = await Edoc.OpenAndValidateAsync(EdocLibConfig.Default, stream, SignatureTrustPolicy.CryptographyOnly);Signatures that are not XadesSignature fail verification with an explanatory error.
using var stream = File.OpenRead("document.edoc");
var result = await Edoc.OpenAndValidateAsync(EdocLibConfig.Default, stream, SignatureTrustPolicy.CryptographyOnly);
using (result.Edoc)
{
var policy = SignatureTrustPolicy.CryptographyOnly;
var report = result.BuildValidationReport(policy);
Console.WriteLine(report.ToPlainText()); // optional IValidationReportLocalizer
// report.Root — ValidationResultNode tree for custom UI
}using var pkg = Edoc.CreateNew();
await using (var doc = File.OpenRead("contract.pdf"))
pkg.AddDataFile(doc, "contract.pdf", "application/pdf");
await using var outStream = File.Create("unsigned.edoc");
pkg.Save(outStream);Prepare returns XadesBesPreparedSignature. Sign GetSignableBytes() with the private key for SigningCertificate, then Complete.
using var pkg = Edoc.CreateNew();
await using (var doc = File.OpenRead("contract.pdf"))
pkg.AddDataFile(doc, "contract.pdf", "application/pdf");
using var rsa = RSA.Create(2048);
var req = new CertificateRequest("CN=signer", rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
using var cert = req.CreateSelfSigned(DateTimeOffset.UtcNow.AddDays(-1), DateTimeOffset.UtcNow.AddYears(1));
var job = new EdocBasicSigningJob(pkg, cert, DateTimeOffset.UtcNow);
var prepared = job.Prepare();
var signatureOctets = cert.GetRSAPrivateKey()!.SignData(
prepared.GetSignableBytes(),
HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1);
job.Complete(prepared, signatureOctets);
await using var outStream = File.Create("signed.edoc");
pkg.Save(outStream);Embeds CertificateValues / RevocationValues after signing. Supply DER OCSP responses and CRLs from your PKI; optional CA certificates for the chain.
using var pkg = Edoc.CreateNew();
await using (var doc = File.OpenRead("contract.pdf"))
pkg.AddDataFile(doc, "contract.pdf", "application/pdf");
var ltJob = new EdocLongTermSigningJob(pkg, cert, DateTimeOffset.UtcNow)
{
CaCertificatesToEmbed = Array.Empty<X509Certificate2>(), // optional: new[] { issuerCert }
// From your infrastructure, e.g. File.ReadAllBytes("signer.ocsp.der"):
OcspDerBlobs = Array.Empty<byte[]>(),
CrlDerBlobs = Array.Empty<byte[]>(),
};
var prep = ltJob.Prepare();
var sigBytes = cert.GetRSAPrivateKey()!.SignData(
prep.GetSignableBytes(),
HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1);
ltJob.CompleteWithEmbeddedMaterial(prep, sigBytes);Same setup as the long-term job; use CompleteWithEmbeddedMaterialAndTimestampAsync instead of CompleteWithEmbeddedMaterial.
var prep = ltJob.Prepare();
var sigBytes = cert.GetRSAPrivateKey()!.SignData(
prep.GetSignableBytes(),
HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1);
await using var tsp = new Rfc3161HttpTimestampProvider(new Uri("https://tsa.example/rfc3161"));
await ltJob.CompleteWithEmbeddedMaterialAndTimestampAsync(prep, sigBytes, tsp);Or resolve the TSA URL from TimestampResponderRegistry (e.g. after registry.RegisterFrom(EdocLibConfig.Default)):
var registry = new TimestampResponderRegistry();
registry.RegisterFrom(EdocLibConfig.Default);
using var http = new HttpClient();
await ltJob.CompleteWithEmbeddedMaterialAndTimestampAsync(prep, sigBytes, registry, httpClient: http);Appends xades:ArchiveTimeStamp to an existing signature (index into Signatures). Requires network access to your TSA.
// pkg already contains at least one signature (see BES / LT examples above)
var archiveJob = new EdocArchiveSigningJob(pkg);
await using var atsTsp = new Rfc3161HttpTimestampProvider(new Uri("https://tsa.example/rfc3161"));
await archiveJob.AppendArchiveTimeStampAsync(signatureIndex: 0, atsTsp);
await using var outStream = File.Create("with-archive-ts.edoc");
pkg.Save(outStream);var trust = SignatureTrustPolicy.CryptographyOnly;
using var pkg = Edoc.Open(EdocLibConfig.Default, "signed.edoc");
await using var outStream = File.Create("copy.edoc");
pkg.Save(outStream, validateSignaturesFirst: true, trustPolicyForValidation: trust);using var http = new HttpClient();
var policy = SignatureTrustPolicy.SystemAnchorsNoRevocation with
{
RevocationMode = X509RevocationMode.Online,
RevocationHttpClient = http,
};
policy.ValidateRevocationFetchConfiguration();
using var edoc = Edoc.Open(EdocLibConfig.Default, "document.edoc");
var result = await edoc.ValidateAsync(policy);Combine with TrustAnchorLoader (PEM / PFX / JKS), optional TSL-backed SignatureTrustPolicy fields, DirectoryRevocationDerCache, and TimestampResponderRegistry.RegisterFrom(config) for production deployments.
dotnet build src/eDocLib/eDocLib.csproj
dotnet test test/eDocLib.Tests/eDocLib.Tests.csprojOptional HTTP smoke for RFC 3161 (CI): environment variable EDOC_TEST_HTTP_TSP_URL (see tests).
See CONTRIBUTING.md for language and process expectations.
This project is licensed under the MIT License — see LICENSE.