Digital Signing and E-Sign
Module: suredms-desktop-client-signing
Source root: SC/suredms-desktop-client-signing/src/main/java/com/sureclinical/suredms/esign/
This module manages the complete digital signing and electronic signature stack for the desktop client. It provides a mode-routing proxy, a remote Nuxeo signing service, an offline stub, custom PDF signature appearance management, signing wizards, and signing-related report generators.
Purpose
suredms-desktop-client-signing lets the rest of the desktop application trigger signings without knowing whether a live Nuxeo server is available. ESignServiceProxy makes this decision at construction time by reading the active EndPointsType. All callers depend on the ESignService interface — they never reference a concrete signing class.
Package Structure
| Package | Contents |
|---|---|
com.sureclinical.suredms.esign | ESignServiceProxy, ClientCustomSignatureAppearanceProvider |
com.sureclinical.suredms.esign.impl | RemoteESignService, RemoteESignServiceOfflineStub |
com.sureclinical.suredms.esign.report | ReportSignatureValidation, ReportSigningHistoryAuditTrail |
com.sureclinical.suredms.esign.ui | DigitalIdVerificationDialog (referenced from the shell's security package) |
com.sureclinical.suredms.esign.ui.settings | Signing settings UI components |
The SignLocalFileWizardBuilder (3-step local file signing wizard) lives in the shell module at com.sureclinical.suredms.ui.wizard.sign.
ESignService (interface)
SC/suredms-common/...services/esign/ESignService.java
The contract all callers use. Key methods:
| Method | Description |
|---|---|
isSigningAllowed() | Whether the current user has the user:signer role |
getCertificate(String username) | Returns a CertificateDescription or null if none active |
signFile(NamedFile, ESignSettings) | Signs a local file and returns the signed NamedFile |
signDocument(Document, ESignSettings) | Signs a Nuxeo-tracked document |
isPasswordRequired() | Whether a PIN/password is required for signing |
showCertificateSetupDialog() | Opens the certificate enrollment flow |
initSigningProcess(String documentId) | Server-side signing initiation |
requiresVerificationCodeAndPasswordForSigning(String documentId) | Returns a Tuple<Boolean, Boolean> — (needsCode, needsPassword) |
ESignServiceProxy
SC/suredms-desktop-client-signing/src/main/java/com/sureclinical/suredms/esign/ESignServiceProxy.java
Extends AbstractClientService. Implements ESignService. Constructed at login time.
public ESignServiceProxy() {
User user = DesktopClient.getInstance().getUser();
remoteEnabled = user.isRemoteSigner();
if (EndPoints.getCurrentEndPoints().getEndPointsType() == EndPointsType.EP_REMOTE) {
this.service = new RemoteESignService();
} else {
this.service = new RemoteESignServiceOfflineStub();
}
}
Key behavior:
remoteEnabledis set at construction and never changes during a session. It reflectsuser.isRemoteSigner()which checks theuser:signerNuxeo property.isSigningAllowed()returnsremoteEnabled. Iffalse, the ribbon signing actions are disabled regardless of connection state.- All
ESignServiceinterface method calls are delegated directly to the innerservice(eitherRemoteESignServiceorRemoteESignServiceOfflineStub). - The proxy cannot switch the inner service mid-session. Each login creates a new proxy.
RemoteESignService
SC/suredms-desktop-client-signing/src/main/java/com/sureclinical/suredms/esign/impl/RemoteESignService.java
Extends AbstractClientService. Implements ESignService and RemoteCertificateService.
Certificate Caching
Uses a Guava LoadingCache for certificate lookups:
CacheBuilder.newBuilder()
.expireAfterWrite(...)
.build(new CacheLoader<...>() { ... })
getCertificate(username):
- Checks
getCertificateStatus(username). - Returns
new CertificateDescription("cert")if status isCertificateStatus.ACTIVE. - Returns
nullotherwise (no certificate configured or not active).
Signing Request Parameters
All signing Nuxeo operation requests use these parameter keys:
PASSWORD_REQUEST_PARAM = "password"VERIFICATION_CODE_REQUEST_PARAM = "verificationCode"USERNAME_REQUEST_PARAM = "username"
Priority
getServicePriority() returns PRIORITY_HIGH when EndPointsType.EP_REMOTE, PRIORITY_LOW otherwise. Used by ServiceProvider to order competing service implementations.
User Resolution
getCurrentUser() first tries EndPoints.getCurrentEndPoints().getAuthSvc().getUserInfo(); if that fails, falls back to DesktopClient.getInstance().getUser(). This handles the case where signing is triggered before the full session is established.
Exception Types
RemoteESignService throws specific signing exceptions from com.sureclinical.suredms.common.sign.exception:
ESignException— general signing failureESignPasswordException— incorrect or missing password/PINESignProtectedDocumentException— document is locked against signingCertificateStatuschecks gate all signing attempts.
RemoteESignServiceOfflineStub
SC/suredms-desktop-client-signing/src/main/java/com/sureclinical/suredms/esign/impl/RemoteESignServiceOfflineStub.java
Offline fallback used when EndPointsType is not EP_REMOTE. Returns safe no-op or stub results for all ESignService methods. Signing operations that strictly require remote connectivity are either queued or return a meaningful error to the caller.
ClientCustomSignatureAppearanceProvider
SC/suredms-desktop-client-signing/src/main/java/com/sureclinical/suredms/esign/ClientCustomSignatureAppearanceProvider.java
Extends AbstractClientService. Implements CustomSignatureAppearanceProvider.
Manages the library of named custom signature appearances (image-based signature blocks stored server-side). All operations are no-ops when endPointsType != EP_REMOTE.
| Method | Nuxeo Operation | Description |
|---|---|---|
addAppearance(appearance) | SureOperations.OP_APPEARANCE_CREATE | Uploads an appearance image blob with a name |
deleteAppearance(username, name) | SureOperations.OP_APPEARANCE_DELETE | Removes an appearance by name |
getAppearances(username) | SureOperations.OP_APPEARANCE_QUERY | Returns all appearances for the user |
getAppearance(username, style) | (iterates getAppearances) | Returns a single appearance by style name |
Appearances are sent to the server as ByteArrayInputStream blobs via client.asInput(...).
getAppearances() checks NuxeoClientPool.isInitialized() before attempting a query; during account verification the user is not yet logged in, so it returns an empty list safely.
Signing Wizards
The local-file signing wizard lives in the shell module:
SignLocalFileWizardBuilder
SC/suredms-desktop-client/src/main/java/com/sureclinical/suredms/ui/wizard/sign/SignLocalFileWizardBuilder.java
Extends WizardBuilder. Dialog size: 1024 × 768, resizable. Three steps:
| Step | Class | Description |
|---|---|---|
| 1 | SelectFileWizardStep | User picks a local PDF file to sign |
| 2 | SignLocalFileWizardStep | Signs the file using ESignService; shows progress |
| 3 | DownloadSignedFileWizardStep | User saves the signed PDF to disk |
performWizardAction() is a no-op — the signing itself happens inside Step 2.
SignLocalFileWizardContext carries the selected file and the signed result between steps.
Signing Reports
| Class | Purpose |
|---|---|
ReportSignatureValidation | Generates a report validating all signatures on a document |
ReportSigningHistoryAuditTrail | Generates a chronological signing-history report for an archive or document |
Both integrate with ReportService from the shell module and are accessible from the quality module's report actions panel.
Full Signing Flow
Document signing (remote)
Caller (ribbon action or quality view)
└── ESignServiceProxy.signDocument(document, settings)
└── RemoteESignService.signDocument(...)
├── initSigningProcess(documentId) [Nuxeo]
├── requiresVerificationCodeAndPassword [Nuxeo → Tuple<Boolean,Boolean>]
├── (if required) DigitalIdVerificationDialog → user enters PIN/code
└── sign Nuxeo operation with {username, password, verificationCode}
└── update Document entity signing fields
Signature appearance management
User opens Signing Settings dialog
└── ClientCustomSignatureAppearanceProvider.getAppearances(username)
└── SureOperations.OP_APPEARANCE_QUERY [Nuxeo]
└── User uploads new image
└── ClientCustomSignatureAppearanceProvider.addAppearance(...)
└── SureOperations.OP_APPEARANCE_CREATE [Nuxeo blob upload]
Local file signing
Ribbon: Sign Local File
└── SignLocalFileWizardBuilder (3 steps)
├── Step 1: SelectFileWizardStep → pick PDF
├── Step 2: SignLocalFileWizardStep → ESignService.signFile(file, settings)
└── Step 3: DownloadSignedFileWizardStep → save to disk
Certificate Handling
CertificateStatus.ACTIVEis required before any signing request proceeds.- Certificate status is fetched via Nuxeo and cached in
RemoteESignService's GuavaLoadingCache. showCertificateSetupDialog()opens the enrollment flow (e.g.,DigitalIdSignupDialogin the shell's security package).- Certificate chain validation and trust verification happen server-side.
DigitalIdVerificationDialogprompts only whenrequiresVerificationCodeAndPasswordForSigning()returnstruefor the relevant flag.
ESignSettings — Signing Parameters Object
ESignSettings is a cloneable parameter object that carries all configuration for a single signing operation. It is passed from the wizard or ribbon action to ESignServiceProxy.signDocument() / signFile().
Located at:
suredms-desktop-client-connector/src/main/java/com/sureclinical/suredms/services/esign/ESignSettings.java
Fields
| Field | Type | Description |
|---|---|---|
verificationCode | String | MFA or OTP verification code (if required) |
certificationLevel | int | CertificationLevel value: NOT_CERTIFIED or CERTIFIED_NO_CHANGES_ALLOWED |
ownerPassword | String | PDF owner password (for certification) |
signatureLocation | Rectangle | Pixel rectangle for the visual signature placement |
imageFile | File | Image to use as the signature appearance |
page | Integer | Page number (1-based) where the signature is placed |
reason | String | Signing reason text embedded in the signature |
password | String | User's signing password / PIN |
location | String | Physical location string embedded in the signature |
appearanceId | String | Named custom appearance ID from ClientCustomSignatureAppearanceProvider |
fixedAppearance | boolean | If true, the user may not change the appearance |
allowSelectLocation | boolean | If false, the location field is read-only in UI (default true) |
allowSelectAppearance | boolean | If false, the appearance picker is hidden (default true) |
allowSelectReason | boolean | If false, the reason drop-down is hidden (default true) |
fixedSignatureLocation | boolean | If true, the signature rectangle is pre-set and cannot be moved |
certifyNotAllowed | boolean | Prevents certifying this document via UI toggle |
workflowTaskId | String | Associated workflow task ID for task-gated signing scenarios |
Key helpers
setCertifyRequired(boolean)— setscertificationLeveltoCERTIFIED_NO_CHANGES_ALLOWEDorNOT_CERTIFIED.isInitial()— returnstrueifappearanceIdequalsSignatureAppearanceProvider.INITIALS_APPEARANCE_ID.clone()—ESignSettingsisCloneable; wizards clone the settings before mutating them per-step.
Dependencies
| Module | Role |
|---|---|
suredms-desktop-client | Shell hosting, ribbon, SignLocalFileWizardBuilder, DigitalIdVerificationDialog |
suredms-desktop-client-data | Document, User (including isRemoteSigner()) |
suredms-desktop-client-connector | EndPoints, NuxeoClientPool, NuxeoClientImpl, SureOperationRequest |
Constraints and Notes
ESignServiceProxy.remoteEnabledis evaluated once at construction. Revoking theuser:signerrole mid-session has no effect until the next login.ClientCustomSignatureAppearanceProvidercapturesendPointsTypeat construction; if the endpoint changes mid-session the appearance provider will use the old type.RemoteESignServiceOfflineStubis a true stub — it does not queue signing operations for later delivery.- Signing credentials (password, verification code) are passed as request parameters and are not retained in memory after the signing call returns.
NuxeoClientPool.isInitialized()must be checked before calling anyNuxeoClientPoolmethod during the early login phase.