Skip to main content
Version: v0.25.0 (Latest)

eIDAS Signature Client

The eIDAS signature client provides a command-based API for signing, validating, and timestamping documents. The client integrates with the IDK's key management system for automatic key resolution.

Commands

CommandDescription
SignDocumentCommandSign a document in one step
CreateDigestCommandCreate digest for remote signing (step 1)
CompleteSignatureCommandComplete remote signature (step 2)
ValidateSignatureCommandValidate existing signatures
TimestampCommandRequest RFC 3161 timestamps

SignDocumentCommand

Signs a document using a key from the configured KMS provider.

Arguments

data class SignDocumentArgs(
val input: SignInput,
val keyInfo: KeyInfoType<*>,
val signatureLevel: SignatureLevel,
val signaturePackaging: SignaturePackaging = SignaturePackaging.ENVELOPED,
val parameters: SignatureParameters? = null,
val timestampParameters: TimestampParameters? = null
)

SignInput

Factory methods for creating input documents:

// PDF document
val pdfInput = SignInput.pdf(pdfBytes)

// XML document
val xmlInput = SignInput.xml(xmlBytes)

// JSON document
val jsonInput = SignInput.json(jsonBytes)

// Binary data
val binaryInput = SignInput.binary(dataBytes)

// Pre-computed digest (for special cases)
val digestInput = SignInput.digest(hashBytes)

// Custom MIME type
val customInput = SignInput(
data = fileBytes,
name = "document.docx",
mimeType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
)

SignOutput

data class SignOutput(
val signedData: ByteArray, // Signed document bytes
val signatureLevel: SignatureLevel, // Applied signature level
val signingTime: Instant, // When signature was created
val signatureId: String?, // Signature identifier
val certificateInfo: CertificateInfo?,
val timestampInfo: TimestampInfo?,
val name: String?,
val mimeType: String?
)

data class CertificateInfo(
val subjectDN: String, // "CN=John Doe, O=Company"
val issuerDN: String, // "CN=CA, O=Authority"
val serialNumber: String?,
val notBefore: Instant?,
val notAfter: Instant?,
val fingerprint: String? // SHA-256 fingerprint
)

data class TimestampInfo(
val timestampTime: Instant,
val timestampAuthority: String?,
val policyOid: String?,
val serialNumber: String?
)

Signature Parameters

CAdES Parameters (Binary Data)

data class CAdESParameters(
val signatureLevel: SignatureLevel = SignatureLevel.CAdES_BASELINE_B,
val signaturePackaging: SignaturePackaging = SignaturePackaging.ENVELOPING,
val digestAlgorithm: DigestAlg = DigestAlg.SHA256,
val signWithExpiredCertificate: Boolean = false,
val generateTBSWithoutCertificate: Boolean = false
)

// Usage
val params = CAdESParameters(
signatureLevel = SignatureLevel.CAdES_BASELINE_LT,
digestAlgorithm = DigestAlg.SHA384
)

PAdES Parameters (PDF)

data class PAdESParameters(
val signatureLevel: SignatureLevel = SignatureLevel.PAdES_BASELINE_B,
val signaturePackaging: SignaturePackaging = SignaturePackaging.ENVELOPED,
val digestAlgorithm: DigestAlg = DigestAlg.SHA256,
val visualSignatureParameters: VisualSignatureParameters? = null,
val signatureFieldId: String? = null, // Use existing signature field
val reason: String? = null, // Signing reason
val location: String? = null, // Signing location
val contactInfo: String? = null, // Contact information
val permission: PdfPermission = PdfPermission.NO_CHANGES_PERMITTED
)

enum class PdfPermission {
NO_CHANGES_PERMITTED, // Lock document after signing
MINIMAL_CHANGES_PERMITTED, // Allow form filling
CHANGES_PERMITTED // Allow annotations
}

Visual Signature (PDF)

data class VisualSignatureParameters(
val fieldParameters: SignatureFieldParameters? = null,
val imageParameters: SignatureImageParameters? = null,
val textParameters: SignatureTextParameters? = null,
val rotation: VisualSignatureRotation = VisualSignatureRotation.NONE,
val zoom: Int = 100
)

data class SignatureFieldParameters(
val page: Int = 1, // Page number (1-based)
val originX: Float = 0f, // X position from left
val originY: Float = 0f, // Y position from bottom
val width: Float = 200f, // Field width
val height: Float = 50f, // Field height
val fieldId: String? = null // Optional field identifier
)

data class SignatureImageParameters(
val image: ByteArray, // PNG, JPEG image bytes
val dpi: Int = 96,
val alignmentHorizontal: VisualSignatureAlignmentHorizontal = LEFT,
val alignmentVertical: VisualSignatureAlignmentVertical = TOP
)

data class SignatureTextParameters(
val text: String? = null,
val font: String = "Helvetica",
val size: Float = 10f,
val textColor: String = "#000000",
val backgroundColor: String? = null,
val signerNamePosition: SignerTextPosition = SignerTextPosition.TOP,
val textWrapping: TextWrapping = TextWrapping.FILL_BOX
)

JAdES Parameters (JSON)

data class JAdESParameters(
val signatureLevel: SignatureLevel = SignatureLevel.JAdES_BASELINE_B,
val signaturePackaging: SignaturePackaging = SignaturePackaging.ENVELOPING,
val digestAlgorithm: DigestAlg = DigestAlg.SHA256,
val jwsSerializationType: JwsSerializationType = JwsSerializationType.COMPACT_SERIALIZATION,
val sigDPolicy: String? = null,
val base64UrlEncodedPayload: Boolean = true
)

enum class JwsSerializationType {
COMPACT_SERIALIZATION, // Three-part dot-separated
JSON_SERIALIZATION, // Full JSON structure
FLATTENED_JSON_SERIALIZATION
}

XAdES Parameters (XML)

data class XAdESParameters(
val signatureLevel: SignatureLevel = SignatureLevel.XAdES_BASELINE_B,
val signaturePackaging: SignaturePackaging = SignaturePackaging.ENVELOPED,
val digestAlgorithm: DigestAlg = DigestAlg.SHA256,
val xPathLocationString: String? = null,
val signedInfoCanonicalizationMethod: CanonicalizationMethod = CanonicalizationMethod.EXCLUSIVE,
val signedPropertiesCanonicalizationMethod: CanonicalizationMethod = CanonicalizationMethod.EXCLUSIVE
)

enum class CanonicalizationMethod {
INCLUSIVE, // http://www.w3.org/TR/2001/REC-xml-c14n-20010315
INCLUSIVE_WITH_COMMENTS,
EXCLUSIVE, // http://www.w3.org/2001/10/xml-exc-c14n#
EXCLUSIVE_WITH_COMMENTS
}

Timestamp Parameters

data class TimestampParameters(
val tsaUrl: String, // TSA endpoint
val tsaPolicyOid: String? = null, // Timestamp policy
val digestAlgorithm: DigestAlg = DigestAlg.SHA256,
val includeNonce: Boolean = true,
val includeCertificates: Boolean = true,
val username: String? = null, // TSA authentication
val password: String? = null
) {
companion object {
fun simple(url: String) = TimestampParameters(tsaUrl = url)

fun withAuth(url: String, username: String, password: String) =
TimestampParameters(
tsaUrl = url,
username = username,
password = password
)
}
}

Validation

ValidateSignatureArgs

data class ValidateSignatureArgs(
val signedDocument: ByteArray,
val originalDocument: ByteArray? = null, // For detached signatures
val signatureForm: SignatureForm? = null, // Auto-detect if null
val validationTime: Instant? = null, // Validate at specific time
val checkCertificateRevocation: Boolean = true
)

ValidationResult

data class ValidationResult(
val isValid: Boolean,
val signatures: List<SignatureValidation>,
val documentName: String? = null,
val signatureForm: SignatureForm? = null
)

data class SignatureValidation(
val signatureId: String,
val isValid: Boolean,
val indication: ValidationIndication,
val subIndication: ValidationSubIndication? = null,
val signatureLevel: SignatureLevel? = null,
val signingTime: Instant? = null,
val certificateInfo: CertificateInfo? = null,
val timestampInfo: TimestampInfo? = null,
val errors: List<String> = emptyList(),
val warnings: List<String> = emptyList()
)

enum class ValidationIndication {
TOTAL_PASSED, // Signature valid
TOTAL_FAILED, // Signature invalid
INDETERMINATE // Cannot determine validity
}

Complete Examples

Sign with Visual Signature

val visualParams = VisualSignatureParameters(
fieldParameters = SignatureFieldParameters(
page = 1,
originX = 400f,
originY = 100f,
width = 150f,
height = 60f
),
imageParameters = SignatureImageParameters(
image = logoBytes,
dpi = 150
),
textParameters = SignatureTextParameters(
text = "Digitally signed by\n${signerName}\n${LocalDate.now()}",
font = "Courier",
size = 8f
)
)

val result = signDocumentCommand.execute(
SignDocumentArgs(
input = SignInput.pdf(pdfBytes),
keyInfo = keyInfo,
signatureLevel = SignatureLevel.PAdES_BASELINE_LT,
parameters = PAdESParameters(
visualSignatureParameters = visualParams,
reason = "Document approval",
location = "Amsterdam"
),
timestampParameters = TimestampParameters.simple(tsaUrl)
),
sessionContext
)

Sign JSON with JAdES

val jsonData = """{"contract": "Agreement #123", "amount": 50000}"""

val result = signDocumentCommand.execute(
SignDocumentArgs(
input = SignInput.json(jsonData.encodeToByteArray()),
keyInfo = keyInfo,
signatureLevel = SignatureLevel.JAdES_BASELINE_T,
parameters = JAdESParameters(
jwsSerializationType = JwsSerializationType.JSON_SERIALIZATION,
base64UrlEncodedPayload = true
),
timestampParameters = TimestampParameters.simple(tsaUrl)
),
sessionContext
)

Validate Multiple Signatures

val result = validateSignatureCommand.execute(
ValidateSignatureArgs(
signedDocument = signedPdfBytes,
checkCertificateRevocation = true
),
sessionContext
)

result.fold(
success = { validation ->
println("Document has ${validation.signatures.size} signature(s)")
println("Overall valid: ${validation.isValid}")

validation.signatures.forEachIndexed { index, sig ->
println("\nSignature ${index + 1}:")
println(" ID: ${sig.signatureId}")
println(" Valid: ${sig.isValid}")
println(" Indication: ${sig.indication}")
sig.subIndication?.let { println(" Sub-indication: $it") }
println(" Signer: ${sig.certificateInfo?.subjectDN}")
println(" Signed at: ${sig.signingTime}")
sig.timestampInfo?.let {
println(" Timestamped at: ${it.timestampTime}")
println(" TSA: ${it.timestampAuthority}")
}
if (sig.errors.isNotEmpty()) {
println(" Errors: ${sig.errors.joinToString()}")
}
if (sig.warnings.isNotEmpty()) {
println(" Warnings: ${sig.warnings.joinToString()}")
}
}
},
failure = { error ->
println("Validation failed: ${error.message}")
}
)

Signature Providers

The EDK offers two SignatureProvider implementations that share the same interface:

DSS Provider (Local)

For in-process signing using the EU DSS library (JVM only):

dependencies {
// Core signing client (multiplatform)
implementation("com.sphereon.edk:lib-eidas-signature-client-impl:0.13.0")

// JVM provider for full eIDAS compliance
implementation("com.sphereon.edk:lib-eidas-signature-client-dss:0.13.0")
}

The DSS provider supports all signature formats (CAdES, PAdES, JAdES, XAdES), all baseline levels (B, T, LT, LTA), timestamping, and validation with revocation checking.

REST Client Provider (Remote)

For delegating signing to a remote eIDAS server:

dependencies {
// Core signing client
implementation("com.sphereon.edk:lib-eidas-signature-client-impl:0.13.0")

// REST client provider
implementation("com.sphereon.edk:lib-eidas-signature-rest-client:0.13.0")
}

The REST client implements the same SignatureProvider interface, allowing switching between local and remote signing:

// Configuration for REST client
@Inject
@SingleIn(AppScope::class)
@ContributesBinding(AppScope::class)
class MyRestSignatureConfig : RestSignatureClientConfig {
override val baseUrl: String = "https://signature-server.example.com/api/v1"
override val defaultConfigId: String = "pdf-lt-config"
}

Once configured, use the same commands as with local signing:

// The RestSignatureProvider is automatically used when DSS is not on classpath
val result = signDocumentCommand.execute(
SignDocumentArgs(
input = SignInput.pdf(pdfBytes),
keyInfo = keyInfo,
signatureLevel = SignatureLevel.PAdES_BASELINE_LT
),
sessionContext
)

Hybrid Signing

For maximum flexibility, use both providers:

  1. Local digest creation - Document never leaves your infrastructure
  2. Remote signing - Only the hash is sent to the server
  3. Local completion - Combine signature with document locally
// Step 1: Create digest locally (document stays on-premise)
val dssProvider: DssSignatureProvider = session.component.dssSignatureProvider
val digest = dssProvider.createDigest(input, keyInfo, params, sessionContext)

// Step 2: Sign hash remotely (only hash leaves your network)
val restProvider: RestSignatureProvider = session.component.restSignatureProvider
val signatureValue = restProvider.signDigest(digest.value.digestToSign)

// Step 3: Complete locally (final document created on-premise)
val signed = dssProvider.completeSignature(
signatureValue,
digest.value.signatureDocumentBytes,
keyInfo,
params,
sessionContext
)