Direct e-Sealing XAdES reference-digest Request

Create an enveloped XAdES Baseline-LT e-seal while keeping the original XML in your system. Your integration calculates the XML document digest, sends it to eID Easy, and inserts the returned signature into the original XML.

Use the same endpoint, client setup and HMAC authentication as the other Cloud Direct e-Sealing requests:

POST https://id.eideasy.com/api/signatures/e-seal

For testing, use https://test.eideasy.com/api/signatures/e-seal.

Flow

  1. Integrator: canonicalize the unsigned XML and calculate its SHA-256 document digest.
  2. eID Easy: create the XAdES Baseline-LT signature, including the signature timestamp and validation material, and return a standalone XML signature fragment.
  3. Integrator: insert the fragment into the original XML and validate the completed document.
XAdES inputRequest fieldResponse signatures[].fileContent
Full XML filefileContent: Base64 original XMLBase64 complete signed XML
Document reference digestdocumentDigest: Base64 SHA-256 digest of canonicalized XMLBase64 standalone ds:Signature fragment

Both modes use XAdES_BASELINE_LT and ENVELOPED. The input field selects the mode; packaging describes the final signature structure.

Request body

{
  "client_id": "client-id",
  "files": [
    {
      "fileName": "document.xml",
      "documentDigest": "<Base64 SHA-256 digest of canonicalized XML>",
      "mimeType": "application/xml"
    }
  ],
  "signature_form": "XAdES",
  "signature_profile": "XAdES_BASELINE_LT",
  "signature_packaging": "ENVELOPED"
}

Replace the digest placeholder with the value calculated from your XML.

PropertyTypeDescription
client_idstringRequired. Your eID Easy API client ID.
filesarrayRequired. Between 1 and 30 files, all using documentDigest.
files[].fileNamestringRequired. Original file name, at least 3 characters, ending in .xml (case-insensitive). Unique within the request.
files[].documentDigeststringRequired. Standard padded Base64 encoding of the 32-byte SHA-256 document digest: exactly 44 characters.
files[].mimeTypestringRequired. application/xml.
signature_formstringRequired. XAdES.
signature_profilestringRequired. XAdES_BASELINE_LT.
signature_packagingstringRequired. ENVELOPED.

Each file must contain exactly one of documentDigest or fileContent. A request cannot mix digest input and full-file input. documentDigest is supported only for XAdES. Do not send digest_profile; the canonicalization and digest algorithm are fixed.

Response example

{
  "status": "OK",
  "signatures": [
    {
      "fileName": "document.xml",
      "mimeType": "application/xml",
      "fileContent": "<Base64 standalone ds:Signature XML fragment>"
    }
  ]
}

fileContent contains the complete XML signature element, including its XAdES properties and LT material. It does not contain the original XML document. Insert the entire decoded element; do not extract only SignatureValue.

Digest calculation

The digest is the value for the signature's document reference (URI=""). It is not a hash of the raw XML file bytes and is not a SignedInfo digest.

The document reference uses these transforms, in order:

  1. Enveloped signature: http://www.w3.org/2000/09/xmldsig#enveloped-signature.
  2. Exclusive XML Canonicalization 1.0 without comments: http://www.w3.org/2001/10/xml-exc-c14n#, with no inclusive namespace prefix list.

Start with unsigned XML, so there is no signature to remove at preparation time. Canonicalize the whole document, including processing instructions outside the root element. Hash the resulting UTF-8 bytes with SHA-256, then Base64-encode the binary digest.

Keep the original XML for the insertion step. Changes to text, whitespace, namespace prefixes or document structure after calculating the digest can invalidate the signature.

JavaScript example

This server-side Node.js example uses libxml2-wasmopen in new window for whole-document canonicalization and @xmldom/xmldomopen in new window for signature insertion. Run it with Node.js 22 or later.

npm install libxml2-wasm@0.7 @xmldom/xmldom@0.9

The example assumes a validated, unsigned UTF-8 XML 1.0 document, without DTD/entity declarations or XInclude. If your document format has an XML schema, use it to validate the XML structure and determine the permitted signature location.

1. Prepare the document digest

Save the following snippets together as seal-xml.mjs. Place the original XML in document.xml.

import { readFile, writeFile } from 'node:fs/promises';
import { createHash, createHmac } from 'node:crypto';
import { XmlDocument, XmlC14NMode } from 'libxml2-wasm';
import { DOMParser, XMLSerializer } from '@xmldom/xmldom';

function createDocumentDigest(unsignedXml) {
  const document = XmlDocument.fromString(unsignedXml);

  try {
    const canonicalXml = document.canonicalizeToString({
      mode: XmlC14NMode.XML_C14N_EXCLUSIVE_1_0,
      withComments: false,
    });

    return createHash('sha256').update(canonicalXml, 'utf8').digest('base64');
  } finally {
    document.dispose();
  }
}

const fileName = 'document.xml';
const unsignedXml = await readFile(fileName, 'utf8');
const documentDigest = createDocumentDigest(unsignedXml);

2. Request the signature

Provide EIDEASY_CLIENT_ID and EIDEASY_ESEAL_HMAC_KEY through your server's environment or secret manager. Use the e-seal HMAC key, not the OAuth client secret. Keep it server-side.

const endpoint = new URL('https://test.eideasy.com/api/signatures/e-seal');
const clientId = process.env.EIDEASY_CLIENT_ID;
const hmacKey = process.env.EIDEASY_ESEAL_HMAC_KEY;

if (!clientId || !hmacKey) {
  throw new Error('Missing eID Easy client ID or e-seal HMAC key');
}

const body = JSON.stringify({
  client_id: clientId,
  files: [{ fileName, documentDigest, mimeType: 'application/xml' }],
  signature_form: 'XAdES',
  signature_profile: 'XAdES_BASELINE_LT',
  signature_packaging: 'ENVELOPED',
});
const timestamp = String(Math.floor(Date.now() / 1000));
const hmac = createHmac('sha256', hmacKey)
  .update(`POST${endpoint.pathname}${timestamp}${body}`, 'utf8')
  .digest('base64');

const response = await fetch(endpoint, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Timestamp': timestamp,
    'X-HMAC-Signature': hmac,
  },
  body,
});

if (!response.ok) {
  throw new Error(`e-Sealing request failed with HTTP ${response.status}`);
}

const result = await response.json();
const signature = result.signatures?.find(item => item.fileName === fileName);

if (result.status !== 'OK' || !signature?.fileContent) {
  throw new Error('No signature returned for the requested file');
}

The HMAC is calculated over the exact JSON string sent as the request body. For batches, retain each original XML document and match each response by fileName.

3. Insert the signature

function insertSignature(unsignedXml, signatureBase64) {
  const parser = new DOMParser({
    onError() { throw new Error('Invalid XML'); },
    normalizeLineEndings(text) { return text.replace(/\r\n?/g, '\n'); },
  });
  const document = parser.parseFromString(unsignedXml, 'application/xml');
  const signatureXml = Buffer.from(signatureBase64, 'base64').toString('utf8');
  const fragment = parser.parseFromString(signatureXml, 'application/xml');
  const signatureNode = document.importNode(fragment.documentElement, true);

  document.documentElement.appendChild(signatureNode);

  return new XMLSerializer()
    .serializeToString(document)
    .replace(/\r/g, '&#13;');
}

const signedXml = insertSignature(unsignedXml, signature.fileContent);
await writeFile('document-signed.xml', signedXml, 'utf8');

Run with node seal-xml.mjs after setting the environment variables. This example appends the signature as the root's last child. To prepend it instead, replace the appendChild line with:

document.documentElement.insertBefore(signatureNode, document.documentElement.firstChild);

Choose the position permitted by your XML schema. If a signature container is required, it must already exist when you calculate the digest. Insert only the returned signature into that container; do not add surrounding whitespace or pretty-print the completed XML. The parser option above preserves XML 1.0 line-ending behavior. The serialization step escapes carriage returns as &#13; so character references in the original XML remain intact when the signed document is parsed again. Without this escape, a literal carriage return is normalized to a line feed on parsing, changing the document digest and invalidating the signature.

Validate the completed XML

The example prepares the digest and inserts the signature. After insertion, validate the signature in the complete signed XML document using a validator that supports XAdES Baseline-LT, such as the European Commission's Digital Signature Service (DSS)open in new window or another compatible signature validation tool or service.

Validation should check document-reference integrity, the cryptographic signature, certificate trust and revocation status, the signature timestamp, and LT validation material under your chosen validation policy. Validating only the returned fragment cannot establish that the original XML matches the signed digest. eID Easy cannot verify that match because it receives only the digest.

If your document format requires an XML schema, also validate the completed XML against that schema. Schema validation checks document structure; it does not replace signature validation.

If document-reference validation fails, check the canonicalization mode, that you hashed the whole document rather than raw bytes, and that insertion preserved the original XML content and whitespace.

Last Updated: